vitest.config.ts
Rama main · réplica de lectura del repositorio original.
/**
* vitest.config.ts
*
* Configures Vitest for unit tests. Unit tests live next to the source file
* (`foo.ts` + `foo.test.ts`) per CONTRIBUTING.md § Testing.
*
* Integration tests (which require a live Postgres and a fake PDNS) use a separate
* config - `vitest.config.integration.ts` - that adds globalSetup hooks for Docker
* Compose. That file lands when the first integration test does.
*/
import { fileURLToPath } from "node:url";
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
// fileURLToPath, not `.pathname`: a checkout under a directory with a space in
// its name would otherwise alias `server-only` to a `%20` path that does not
// exist, and every server-only module would fail to load.
const root = fileURLToPath(new URL("./", import.meta.url));
const alias = {
"@/": root,
// `server-only` / `client-only` only exist to throw when imported from
// the wrong React environment. Tests have no RSC/client split, so stub
// them out. (An alias is robust even though @vitejs/plugin-react flips
// Vite to client-resolution, which defeats the react-server condition.)
"server-only": `${root}tests/noop-module.ts`,
"client-only": `${root}tests/noop-module.ts`,
};
const EXCLUDE = [
"node_modules",
".next",
"dist",
"build",
"coverage",
// Agent worktrees live here (git worktree under .claude/worktrees); their
// copies of the test files must not be collected into the main run.
".claude/**",
"tests/integration/**",
"tests/e2e/**",
];
/** Tests that render React into a document: `foo.dom.test.tsx`. */
const DOM_TESTS = "**/*.dom.test.tsx";
export default defineConfig({
// tsconfig sets `jsx: "preserve"` (Next.js transforms JSX itself), which
// Vite's esbuild can't parse. The React plugin transforms `.tsx` ahead of
// Vite's import analysis so component imports work in tests.
plugins: [react()],
test: {
// Two projects because the two kinds of unit test need opposite React
// builds. Plain tests resolve with the `react-server` condition so
// `server-only` modules load; DOM tests render through `react-dom/client`,
// which that condition replaces with a stub that throws, so they resolve
// as a browser bundle would and run in jsdom.
projects: [
{
plugins: [react()],
// `react-server` condition makes the `server-only` package resolve to
// its no-op server build instead of the throwing client guard. Mirrors
// what the `db:seed` npm script does at the process level - keeps
// `npm test` green without operators having to remember the
// NODE_OPTIONS dance.
resolve: { alias, conditions: ["react-server", "node", "import", "default"] },
test: {
name: "node",
environment: "node",
setupFiles: ["./tests/setup.ts"],
include: ["**/*.test.ts", "**/*.test.tsx"],
exclude: [...EXCLUDE, DOM_TESTS],
// Test files run in worker threads in parallel. Set to `false` if a
// test suite needs a serial DB connection.
pool: "threads",
testTimeout: 10_000,
hookTimeout: 10_000,
},
},
{
plugins: [react()],
resolve: { alias, conditions: ["browser", "import", "default"] },
test: {
name: "dom",
environment: "jsdom",
setupFiles: ["./tests/setup.ts"],
include: [DOM_TESTS],
exclude: EXCLUDE,
pool: "threads",
testTimeout: 10_000,
hookTimeout: 10_000,
},
},
],
// We don't gate global coverage - it's dominated by UI + repository code
// that's exercised by the Postgres integration suite, not the unit suite.
// Instead we floor the security-critical, DB-free modules that MUST stay
// unit-tested: auth primitives, crypto, RBAC. CI runs `npm run test`
// (= `vitest run --coverage`), so a drop below these floors fails the build.
coverage: {
provider: "v8",
reporter: ["text-summary", "html"],
include: ["lib/**", "app/**", "components/**"],
exclude: ["**/*.test.ts", "**/*.test.tsx", "**/index.ts", "**/README.md", "**/types.ts"],
thresholds: {
"lib/crypto/**": { statements: 90, branches: 80, functions: 90, lines: 90 },
"lib/rbac/**": { statements: 70, branches: 60, functions: 85, lines: 70 },
// lib/auth/** spans the unit-tested primitives (password, totp, csrf,
// rate-limit) AND the DB/network-backed providers (OIDC) covered by the
// integration suite - so this floor sits below the others and just
// guards against a sharp unit-coverage regression.
"lib/auth/**": { statements: 40, branches: 40, functions: 45, lines: 40 },
},
},
},
});