- 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
134 lines
4.2 KiB
TypeScript
134 lines
4.2 KiB
TypeScript
import { cleanup } from "@testing-library/react";
|
|
import { afterEach, vi } from "vitest";
|
|
import "@testing-library/jest-dom/vitest";
|
|
import React from "react";
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Global mocks for heavy / browser-only libraries so components render in jsdom.
|
|
// (vi.mock in a setup file applies to every test file in the component project.)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
vi.mock("framer-motion", () => {
|
|
const passthrough = (tag: string) =>
|
|
React.forwardRef(function MotionMock(
|
|
{ children, ...props }: Record<string, unknown> & { children?: React.ReactNode },
|
|
ref: React.Ref<unknown>,
|
|
) {
|
|
const domProps: Record<string, unknown> = {};
|
|
for (const [key, value] of Object.entries(props)) {
|
|
// Drop motion-only props that would warn as unknown DOM attributes.
|
|
if (
|
|
/^(initial|animate|exit|transition|variants|whileHover|whileTap|whileInView|whileFocus|whileDrag|drag|layout|layoutId|viewport|custom|onAnimationComplete|style)$/.test(
|
|
key,
|
|
)
|
|
) {
|
|
if (key === "style") domProps.style = value;
|
|
continue;
|
|
}
|
|
domProps[key] = value;
|
|
}
|
|
return React.createElement(tag, { ...domProps, ref }, children as React.ReactNode);
|
|
});
|
|
|
|
const motion = new Proxy(
|
|
{},
|
|
{
|
|
get: (_target, tag: string) => passthrough(tag),
|
|
},
|
|
);
|
|
|
|
return {
|
|
motion,
|
|
AnimatePresence: ({ children }: { children?: React.ReactNode }) =>
|
|
React.createElement(React.Fragment, null, children),
|
|
useReducedMotion: () => true,
|
|
useInView: () => true,
|
|
useScroll: () => ({ scrollYProgress: { on: () => () => {}, get: () => 0 } }),
|
|
useTransform: () => 0,
|
|
useMotionValue: (value: unknown) => ({ get: () => value, set: () => {}, on: () => () => {} }),
|
|
useAnimate: () => [React.useRef(null), () => Promise.resolve()],
|
|
};
|
|
});
|
|
|
|
vi.mock("gsap", () => {
|
|
const tween = { kill: () => {}, play: () => {}, pause: () => {}, progress: () => {} };
|
|
const gsap = {
|
|
to: () => tween,
|
|
from: () => tween,
|
|
fromTo: () => tween,
|
|
set: () => tween,
|
|
timeline: () => ({
|
|
to: () => ({}),
|
|
from: () => ({}),
|
|
fromTo: () => ({}),
|
|
add: () => ({}),
|
|
kill: () => {},
|
|
}),
|
|
registerPlugin: () => {},
|
|
context: (fn: () => void) => {
|
|
if (typeof fn === "function") fn();
|
|
return { revert: () => {}, kill: () => {} };
|
|
},
|
|
matchMedia: () => ({ add: () => {}, revert: () => {} }),
|
|
utils: { toArray: (v: unknown) => (Array.isArray(v) ? v : [v]) },
|
|
};
|
|
return { gsap, default: gsap };
|
|
});
|
|
|
|
vi.mock("@gsap/react", () => ({
|
|
useGSAP: () => ({ context: {}, contextSafe: (fn: unknown) => fn }),
|
|
}));
|
|
|
|
vi.mock("next/link", () => ({
|
|
default: React.forwardRef(function LinkMock(
|
|
{ href, children, ...props }: Record<string, unknown> & { href?: unknown; children?: React.ReactNode },
|
|
ref: React.Ref<HTMLAnchorElement>,
|
|
) {
|
|
return React.createElement("a", { href: String(href ?? ""), ref, ...props }, children as React.ReactNode);
|
|
}),
|
|
}));
|
|
|
|
vi.mock("next/image", () => ({
|
|
default: ({ src, alt, ...props }: Record<string, unknown> & { src?: unknown; alt?: string }) =>
|
|
React.createElement("img", { src: String(src ?? ""), alt: alt ?? "", ...props }),
|
|
}));
|
|
|
|
vi.mock("next/navigation", () => ({
|
|
usePathname: () => "/",
|
|
useRouter: () => ({
|
|
push: vi.fn(),
|
|
replace: vi.fn(),
|
|
refresh: vi.fn(),
|
|
back: vi.fn(),
|
|
forward: vi.fn(),
|
|
prefetch: vi.fn(),
|
|
}),
|
|
useSearchParams: () => new URLSearchParams(),
|
|
useParams: () => ({}),
|
|
redirect: vi.fn(),
|
|
notFound: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("next-intl", () => ({
|
|
useTranslations: () => {
|
|
const t = (key: string) => key;
|
|
t.rich = (key: string) => key;
|
|
t.markup = (key: string) => key;
|
|
t.raw = (key: string) => key;
|
|
return t;
|
|
},
|
|
useLocale: () => "de",
|
|
useFormatter: () => ({
|
|
dateTime: (v: Date) => v.toISOString(),
|
|
number: (v: number) => String(v),
|
|
relativeTime: (v: unknown) => String(v),
|
|
}),
|
|
useMessages: () => ({}),
|
|
NextIntlClientProvider: ({ children }: { children?: React.ReactNode }) =>
|
|
React.createElement(React.Fragment, null, children),
|
|
}));
|