mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-08-19 09:30:21 +00:00
Co-authored-by: Pablo F.G <pablo.fernandez@prowler.com>
112 lines
3.9 KiB
TypeScript
112 lines
3.9 KiB
TypeScript
// Global stylesheet (Tailwind + design tokens) is imported by the Next.js
|
|
// layouts in the real app. Tests render the page in isolation, bypassing the
|
|
// layout, so without this import Tailwind classes resolve to nothing — the
|
|
// page collapses to unstyled HTML and stacked elements end up overlapping
|
|
// the graph nodes, blocking Playwright clicks. Pull the stylesheet directly
|
|
// so the test bundle gets the same CSS the production page receives.
|
|
import "@/styles/globals.css";
|
|
|
|
import { afterAll, afterEach, beforeAll, vi } from "vitest";
|
|
|
|
import { resetToasts } from "@/components/shadcn/toast/use-toast";
|
|
|
|
import { worker } from "./__tests__/msw/worker";
|
|
|
|
// Server Actions ("use server") are bundled by Vite as plain async functions
|
|
// — the directive is a Next.js compiler concept, not part of Vite. When the
|
|
// page invokes one, it runs in the browser and reaches `auth()` from
|
|
// next-auth, which calls `next/headers` (request-scoped AsyncLocalStorage
|
|
// only set up by Next's request handler) and throws "headers was called
|
|
// outside a request scope". That kills every action before it can hit
|
|
// MSW. Stub `auth.config` with a fake session so the action proceeds to
|
|
// `fetch()` and MSW takes over.
|
|
vi.mock("@/auth.config", () => ({
|
|
auth: vi.fn(() => Promise.resolve({ accessToken: "test-access-token" })),
|
|
signIn: vi.fn(),
|
|
signOut: vi.fn(),
|
|
handlers: {},
|
|
}));
|
|
|
|
// Server Actions call `revalidatePath`/`revalidateTag` from `next/cache` after
|
|
// mutations. Those read Next's static-generation store, which only exists
|
|
// inside a Next request scope — in the browser test they throw "Invariant:
|
|
// static generation store missing", aborting the action before the UI can
|
|
// react. Stub them as no-ops; the tests assert on UI state, not cache
|
|
// invalidation.
|
|
vi.mock("next/cache", () => ({
|
|
revalidatePath: vi.fn(),
|
|
revalidateTag: vi.fn(),
|
|
unstable_cache: <T>(fn: T) => fn,
|
|
}));
|
|
|
|
// Next.js's App Router context (`useRouter`, `useSearchParams`, `usePathname`)
|
|
// is not available in vitest browser — there's no Next runtime mounting the
|
|
// providers. We back the hooks with the real `window.location` so navigating
|
|
// via `history.replaceState` in tests is enough to drive the page.
|
|
vi.mock("next/navigation", () => {
|
|
const router = {
|
|
push: vi.fn(),
|
|
replace: vi.fn(),
|
|
back: vi.fn(),
|
|
forward: vi.fn(),
|
|
refresh: vi.fn(),
|
|
prefetch: vi.fn(() => Promise.resolve()),
|
|
};
|
|
return {
|
|
useSearchParams: () => new URLSearchParams(window.location.search),
|
|
useRouter: () => router,
|
|
usePathname: () => window.location.pathname,
|
|
useParams: () => ({}),
|
|
redirect: vi.fn(),
|
|
notFound: vi.fn(),
|
|
};
|
|
});
|
|
|
|
beforeAll(async () => {
|
|
await worker.start({
|
|
serviceWorker: { url: "/mockServiceWorker.js" },
|
|
onUnhandledRequest: "error",
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
worker.resetHandlers();
|
|
// Module-level store: a toast raised here would re-render on the next test's
|
|
// mount and intercept clicks aimed at the page.
|
|
resetToasts();
|
|
});
|
|
|
|
afterAll(() => {
|
|
worker.stop();
|
|
});
|
|
|
|
// React Flow's pan/drag handlers dispatch pointer events that access
|
|
// `event.view.document` on the node. When user-event synthesises these
|
|
// events the `view` property can be null, producing harmless
|
|
// "Cannot read properties of null (reading 'document')" errors.
|
|
// Swallow only that specific unhandled error; everything else propagates.
|
|
const isReactFlowNullViewError = (reason: unknown): boolean => {
|
|
const message =
|
|
reason instanceof Error
|
|
? reason.message
|
|
: typeof reason === "string"
|
|
? reason
|
|
: "";
|
|
return message.includes(
|
|
"Cannot read properties of null (reading 'document')",
|
|
);
|
|
};
|
|
|
|
window.addEventListener("error", (event) => {
|
|
if (isReactFlowNullViewError(event.error)) {
|
|
event.preventDefault();
|
|
event.stopImmediatePropagation();
|
|
}
|
|
});
|
|
|
|
window.addEventListener("unhandledrejection", (event) => {
|
|
if (isReactFlowNullViewError(event.reason)) {
|
|
event.preventDefault();
|
|
}
|
|
});
|