test: add comprehensive automated test coverage
- 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
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
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),
|
||||
}));
|
||||
Reference in New Issue
Block a user