diff --git a/.changeset/cold-nights-sit.md b/.changeset/cold-nights-sit.md
new file mode 100644
index 000000000..606543545
--- /dev/null
+++ b/.changeset/cold-nights-sit.md
@@ -0,0 +1,5 @@
+---
+"@easypost/easy-ui": patch
+---
+
+fix: expose test utilities as published entry point
diff --git a/easy-ui-react/src/Tooltip/Tooltip.test.tsx b/easy-ui-react/src/Tooltip/Tooltip.test.tsx
index f140ac3fb..1dd12395b 100644
--- a/easy-ui-react/src/Tooltip/Tooltip.test.tsx
+++ b/easy-ui-react/src/Tooltip/Tooltip.test.tsx
@@ -88,8 +88,9 @@ describe("", () => {
// Helpers for interacting with tooltip trigger
//
-// TODO: Look into why hover() and tab() has to be wrapped in act() since this
-// shouldn't be necessary. Perhaps something being done in Aria
+// user-event interactions wrap themselves in act(), so they must not be nested
+// inside another act() call. Only the synchronous timer flush, which drives the
+// tooltip's open/close delay state updates, needs to be wrapped.
export async function hoverOverTooltipTrigger(
user: UserEvent,
@@ -97,19 +98,19 @@ export async function hoverOverTooltipTrigger(
{ runTimers = true } = {},
) {
fireEvent.mouseMove(document.body);
- await act(async () => {
- await userHover(user, el);
- if (runTimers) {
+ await userHover(user, el);
+ if (runTimers) {
+ act(() => {
vi.runAllTimers();
- }
- });
+ });
+ }
}
async function tabToTooltipTrigger(user: UserEvent, { runTimers = true } = {}) {
- await act(async () => {
- await userTab(user);
- if (runTimers) {
+ await userTab(user);
+ if (runTimers) {
+ act(() => {
vi.runAllTimers();
- }
- });
+ });
+ }
}
diff --git a/easy-ui-react/src/utilities/test.ts b/easy-ui-react/src/utilities/test.ts
index 2ecd164f8..5190598ea 100644
--- a/easy-ui-react/src/utilities/test.ts
+++ b/easy-ui-react/src/utilities/test.ts
@@ -1,16 +1,9 @@
-import { act, render as renderWithTestingLib } from "@testing-library/react";
+import { render as renderWithTestingLib } from "@testing-library/react";
import userEvent, { UserEvent } from "@testing-library/user-event";
import { ReactElement } from "react";
import { config as reactTransitionGroupConfig } from "react-transition-group";
import { vi } from "vitest";
-declare global {
- // eslint-disable-next-line no-var
- var jest: object;
- // eslint-disable-next-line no-var
- var IS_REACT_ACT_ENVIRONMENT: boolean;
-}
-
/**
* Render a react element for testing. Passes in vitest's timers for user-event.
*/
@@ -28,16 +21,22 @@ export function render(jsx: ReactElement) {
* user-event with `vi.useFakeTimers()`
*
* See https://github.com/testing-library/react-testing-library/issues/1195
+ *
+ * Note: relies on the test runner's `beforeAll` global, so it must be called
+ * from a vitest/jest environment that exposes globals.
*/
export function installJestCompatibleFakeTimers() {
beforeAll(() => {
- const _jest = globalThis.jest;
- globalThis.jest = {
- ...globalThis.jest,
+ const globalWithJest = globalThis as typeof globalThis & {
+ jest?: Record;
+ };
+ const _jest = globalWithJest.jest;
+ globalWithJest.jest = {
+ ...globalWithJest.jest,
advanceTimersByTime: vi.advanceTimersByTime.bind(vi),
};
return () => {
- globalThis.jest = _jest;
+ globalWithJest.jest = _jest;
};
});
}
@@ -63,7 +62,11 @@ export function mockIntersectionObserver() {
}
export function installScrollToMock() {
+ const originalScrollTo = Element.prototype.scrollTo;
Element.prototype.scrollTo = () => {};
+ return () => {
+ Element.prototype.scrollTo = originalScrollTo;
+ };
}
export function mockMatchMedia({
@@ -95,27 +98,19 @@ export function silenceConsoleError() {
}
export async function userClick(user: UserEvent, element: Element) {
- await act(async () => {
- await user.click(element);
- });
+ await user.click(element);
}
export async function userKeyboard(user: UserEvent, text: string) {
- await act(async () => {
- await user.keyboard(text);
- });
+ await user.keyboard(text);
}
export async function userHover(user: UserEvent, element: Element) {
- await act(async () => {
- await user.hover(element);
- });
+ await user.hover(element);
}
export async function userTab(user: UserEvent) {
- await act(async () => {
- await user.tab();
- });
+ await user.tab();
}
export async function userType(
@@ -123,9 +118,7 @@ export async function userType(
element: Element,
text: string,
) {
- await act(async () => {
- await user.type(element, text);
- });
+ await user.type(element, text);
}
export async function selectCheckbox(user: UserEvent, el: HTMLElement) {
diff --git a/easy-ui-react/vite.config.mts b/easy-ui-react/vite.config.mts
index 36b058d8d..a66344864 100644
--- a/easy-ui-react/vite.config.mts
+++ b/easy-ui-react/vite.config.mts
@@ -45,16 +45,20 @@ export default defineConfig({
entry: buildEntryObject([
...glob.sync("src/**/index.ts"),
...glob.sync("src/utilities/*.ts", {
- ignore: ["**/test.ts", "**/*.test.ts"],
+ ignore: ["**/*.test.ts"],
}),
]),
cssFileName: "style",
},
rollupOptions: {
external: (id) =>
- ["react", "react-dom"].some(
- (pkg) => id === pkg || id.startsWith(`${pkg}/`),
- ),
+ [
+ "react",
+ "react-dom",
+ "vitest",
+ "@testing-library/react",
+ "@testing-library/user-event",
+ ].some((pkg) => id === pkg || id.startsWith(`${pkg}/`)),
output: [
{
format: "cjs",
diff --git a/easy-ui-react/vitest.setup.ts b/easy-ui-react/vitest.setup.ts
index 810e3ec15..d59d58d85 100644
--- a/easy-ui-react/vitest.setup.ts
+++ b/easy-ui-react/vitest.setup.ts
@@ -4,6 +4,11 @@ import {
installScrollToMock,
} from "./src/utilities/test";
+declare global {
+ // eslint-disable-next-line no-var
+ var IS_REACT_ACT_ENVIRONMENT: boolean;
+}
+
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
installJestCompatibleFakeTimers();