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 { 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): Promise { 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): FormData { const formData = new FormData(); for (const [key, value] of Object.entries(fields)) { if (value !== undefined) { formData.set(key, value); } } return formData; }