vitest.config.ts
Rama main · réplica de lectura del repositorio original.
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import { fileURLToPath } from 'node:url'
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'@gw/edition': fileURLToPath(new URL('./src/server/edition/community.ts', import.meta.url)),
// The empty catalogue, which is what a public build has. A test that
// wants rows mocks this module; one that does not gets the honest
// nothing rather than a fixture pretending to be a library.
'@gw/catalog': fileURLToPath(new URL('./src/cloud/catalog/unavailable.ts', import.meta.url)),
'@gw/billing-adapters': fileURLToPath(
new URL('./src/cloud/billing/adapters/unavailable.ts', import.meta.url),
),
'@gw/billing-conformance': fileURLToPath(
new URL('./src/cloud/billing/conformance/index.ts', import.meta.url),
),
'@gw/home': fileURLToPath(
new URL('./src/components/home/community-home.tsx', import.meta.url),
),
/**
* What the next-intl plugin does in a Next build, done by hand here.
*
* Without it `getTranslations` has no configuration and throws, so
* anything that renders a message would have to be tested against a mock
* -- which would assert that the mock was called, not that the catalog
* says what the test claims. With the alias, a test renders the real
* German or English sentence out of the real messages/*.json.
*
* Only reachable for calls that pass an explicit locale: the other branch
* of src/i18n/request.ts reads cookies() and headers(), which do not
* exist outside a request. That is the same boundary production has.
*/
'next-intl/config': fileURLToPath(new URL('./src/i18n/request.ts', import.meta.url)),
/**
* What Next resolves this to in a Server Component: the `react-server`
* export condition, which is an empty module. Vitest resolves the
* `default` one, and that is a bare `throw` -- so without this alias no
* test can import anything marked server-only, and a marker meant to
* catch a wrong import would decide what is testable.
*/
'server-only': fileURLToPath(new URL('./node_modules/server-only/empty.js', import.meta.url)),
},
},
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./vitest.setup.ts'],
// The shell scripts an operator runs are covered here too: a backup that
// fails quietly is a bug like any other, and asserting it takes a shell and
// a fake `docker`, not a browser.
include: ['src/**/*.{test,spec}.{ts,tsx}', 'scripts/**/*.test.mjs'],
// Database tests live in their own suite and their own CI job -- they need
// a real Postgres with migrations applied. See vitest.db.config.ts.
exclude: ['**/node_modules/**', 'src/**/*.db.test.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'lcov'],
// Deliberately not a global target: coverage thresholds only guard the
// zones where a bug is silent. A repo-wide number just breeds alibi tests.
include: ['src/domain/**', 'src/features/**', 'src/i18n/**'],
// Modules whose tests live in another suite. Counting them here would
// report them as untested and make the threshold measure the wrong thing
// -- and lowering the threshold to accommodate that would quietly weaken
// it for the pure logic it exists to protect.
exclude: [
// Covered by the `db` suite (*.db.test.ts) against a real Postgres,
// which is a required check in its own right. Their invariants -- RLS,
// composite FKs, compare-and-swap -- cannot be asserted without one.
'src/domain/agenda/access.ts',
'src/domain/agenda/repo.ts',
'src/domain/workshop/repo.ts',
// The library read and the folder tree: the same subject, split out of
// repo.ts when it outgrew it. Every question they answer is "which rows
// may this actor see", which is RLS plus a recursive path query -- there
// is nothing here a unit test could be wrong about on its own.
'src/domain/workshop/library.ts',
'src/domain/workshop/folders.ts',
'src/domain/workshop/collaborators.ts',
// Folder-level collaboration: the same shape one level up. Its pure
// half -- who holds what, who may hand on what -- is in
// folder-access.ts precisely so that it stays counted here, which is
// where a wrong answer silently grants or denies access.
'src/domain/workshop/folder-collaborators.ts',
// The OAuth flow's rows. What makes it safe is not in this file: the
// single-use code is a `where used_at is null` inside an UPDATE and the
// rotation is one too, so what is worth asserting is what Postgres does
// under a race -- which needs Postgres. The decisions live in
// oauth/rules.ts and are counted here.
'src/domain/oauth/repo.ts',
// The same, for guests. Its pure half -- when a link expires, what counts
// as an address -- was split into share-rules.ts precisely so that it
// stays counted here: a wrong answer from validUntil silently grants or
// denies access, which is exactly what this threshold is for.
'src/domain/workshop/share-links.ts',
// Membership crosses the auth-role boundary -- member rows behind RLS,
// e-mail addresses behind a role the application cannot join to. That
// separation is the thing worth asserting, and it does not exist
// without a database.
'src/domain/tenant/members.ts',
'src/domain/tenant/membership.ts',
'src/domain/tenant/tokens.ts',
// Type declarations: nothing to execute.
'**/types.ts',
// Browser glue, covered by Playwright: a dnd-kit coordinate getter and a
// hook wiring dnd-kit's events to a document have no meaningful unit
// surface. What use-agenda-drag actually has to get right -- the first
// delta is a position, the first collision is measured from the handle,
// `over` is one event behind -- is only observable in a real browser, and
// the five drag tests in e2e/agenda.spec.ts are where it is observed.
//
// The sentence the hook has read aloud is NOT here: describeProjection is
// pure, a wrong answer is silent to everybody who can see the screen, and
// it has its own test.
'src/features/agenda/keyboard.ts',
'src/features/agenda/use-agenda-drag.ts',
// The same category, and it only appears on this list because Vitest 4
// started counting files no test imports -- under 3 it was never
// measured at all, so this restores the intended scope rather than
// narrowing it. What the hook does is asserted through
// agenda-surface.test.tsx and by Playwright, which is where a React
// hook's behaviour is legible.
'src/features/agenda/use-local-document.ts',
// A hand-written WebSocket client with reconnect backoff, and the hook
// that drives it. What matters about them -- that an edit reaches the
// other browser -- is asserted end to end by Playwright and by the
// collaboration suite against a real server; a mocked socket here
// would only assert that the mock was called.
'src/features/collab/provider.ts',
'src/features/collab/use-collab-document.ts',
// Request-scoped wiring: reads cookies(), headers() and the session.
// The branching worth guarding lives in resolve.ts, which is pure.
'src/i18n/request.ts',
'src/i18n/catalogs.ts',
],
thresholds: {
'src/domain/**': { statements: 80, branches: 80, functions: 80, lines: 80 },
'src/features/**': { statements: 80, branches: 80, functions: 80, lines: 80 },
// Language resolution is exactly the silent branching these thresholds
// exist for: every wrong answer renders a readable page in the wrong
// language, which no other check notices.
'src/i18n/**': { statements: 80, branches: 80, functions: 80, lines: 80 },
},
},
},
})