- Vitest multi-project setup (unit / integration / component) - Real-Postgres integration harness via in-process PGlite (TEST_DATABASE_URL override), migrations applied per worker; production code untouched - Unit: routing, locale, validation/Zod schemas, metadata, mail, site-theme, marquee, media, portfolio helpers, plus architecture-rule tests - Integration: Prisma data layer, API routes, and all server actions - Component: UI primitives and form components (jsdom + Testing Library) - 371 tests passing
68 lines
1.9 KiB
TypeScript
68 lines
1.9 KiB
TypeScript
import { vi } from "vitest";
|
|
|
|
/**
|
|
* Shared mocks for the Next.js runtime pieces that server actions depend on.
|
|
* Action tests wire these in with `vi.mock(...)` at the top of each file, e.g.:
|
|
*
|
|
* vi.mock("next/navigation", async () => ({
|
|
* redirect: (await import("@/tests/helpers/next-mocks")).redirect,
|
|
* }));
|
|
*/
|
|
|
|
export class RedirectError extends Error {
|
|
readonly digest = "NEXT_REDIRECT";
|
|
readonly __isRedirect = true;
|
|
constructor(public url: string) {
|
|
super(`NEXT_REDIRECT:${url}`);
|
|
}
|
|
}
|
|
|
|
export function redirect(url: string): never {
|
|
throw new RedirectError(url);
|
|
}
|
|
|
|
export function isRedirectError(error: unknown): error is RedirectError {
|
|
return (
|
|
error instanceof RedirectError ||
|
|
(typeof error === "object" && error !== null && (error as { __isRedirect?: boolean }).__isRedirect === true)
|
|
);
|
|
}
|
|
|
|
export const revalidatePath = vi.fn();
|
|
|
|
export const adminAuth = { authenticated: true };
|
|
export const clearAdminSessionCookie = vi.fn(async () => {});
|
|
export async function isAdminAuthenticated(): Promise<boolean> {
|
|
return adminAuth.authenticated;
|
|
}
|
|
|
|
/** Run an action and return the URL it redirected to (or throw if it didn't). */
|
|
export async function captureRedirect(run: () => Promise<unknown>): Promise<string> {
|
|
try {
|
|
await run();
|
|
} catch (error) {
|
|
if (isRedirectError(error)) {
|
|
return error.url;
|
|
}
|
|
throw error;
|
|
}
|
|
throw new Error("Expected the action to redirect, but it returned normally.");
|
|
}
|
|
|
|
export function resetNextMocks() {
|
|
revalidatePath.mockClear();
|
|
clearAdminSessionCookie.mockClear();
|
|
adminAuth.authenticated = true;
|
|
}
|
|
|
|
/** Build a FormData from a flat record (strings and Files). */
|
|
export function formDataFrom(fields: Record<string, string | File | undefined>): FormData {
|
|
const formData = new FormData();
|
|
for (const [key, value] of Object.entries(fields)) {
|
|
if (value !== undefined) {
|
|
formData.set(key, value);
|
|
}
|
|
}
|
|
return formData;
|
|
}
|