Files
prowler/ui/vitest.browser.setup.ts
Pablo Fernandez Guerra (PFE) 1090ed59b7 feat(ui): replace D3+Dagre attack path graph with React Flow (#10686)
Co-authored-by: Pablo F.G <pablo.fernandez@prowler.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Alan Buscaglia <gentlemanprogramming@gmail.com>
2026-05-12 16:33:29 +02:00

95 lines
3.2 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 { 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: {},
}));
// 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();
});
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();
}
});