Files
gitmost/packages/git-sync/vitest.config.ts
T
claude code agent 227 baa41d66ad test(infra): coverage-gate + acceptInvitation atomicity int-spec + turn-end unit (#324)
Tail of #244. Three items:

1. Coverage-gate (main). develop had no coverage tooling at all. Added
   @vitest/coverage-v8@4.1.6 (pinned to the vitest already in use) to the three
   vitest packages — git-sync, editor-ext (which also gains its missing direct
   `vitest` devDep), apps/client — and enabled v8 coverage with per-package
   thresholds (no root vitest config exists, so per-package is the only
   meaningful scope). v8 provider is chosen deliberately: istanbul broke on the
   ESM `@docmost/editor-ext` barrel; v8 collects native runtime coverage and
   never re-parses ESM. `enabled: true` wires the gate into the plain `test`
   script, so `pnpm -r test` (the CI entrypoint) enforces it without a manual
   `--coverage`. Thresholds set ~4-5 pts below measured current coverage so the
   gate PASSES today and FAILS on regression (verified: forcing lines=95 on
   editor-ext exits 1). `all: false` — coverage counts test-touched files;
   documented in the configs (with `all: true` the many untested type/barrel
   files would sink the % and make the gate meaningless).
   Measured→threshold (S/B/F/L): git-sync 91.78/79.16/76.76/92.46 → 88/75/72/88;
   editor-ext 58.58/48.1/64.96/58.91 → 54/44/60/54; client 59.93/58/48.47/59.39
   → 55/53/44/55. All exit 0.

2. acceptInvitation atomicity int-spec. New
   apps/server/test/integration/workspace-accept-invitation-atomicity.int-spec.ts
   (+ createDefaultGroup/createInvitation seeders in test/integration/db.ts per
   its convention). Wires the real WorkspaceInvitationService with real
   User/Group/GroupUser repos against the test Kysely, stubbing only the
   post-commit collaborators. Asserts the invariant protected by
   users_email_workspace_id_unique: (a) two CONCURRENT accepts → exactly one
   fulfilled, one BadRequestException('Invitation already accepted'), membership
   count == 1, invitation consumed; (b) repeated sequential accept → still one
   membership; (c) the survivor is in the workspace default group (whole-tx, no
   torn state). Ran against real Postgres+Redis: 3/3 pass.

3. turn-end decision unit test. `decideTurnEnd` does not exist as a symbol; the
   turn-end logic lives in chat-thread.tsx's onFinish handler. Added a focused
   block to the existing chat-thread.test.tsx (matching its hoisted-mock style):
   clean finish → flush queued (continue); abort/disconnect/error → queue
   preserved (end) with the correct notice; parent notified on every terminal
   outcome. 8 passed (3 existing + 5 new).

Verified: git-sync 712, editor-ext 247, client 888 (all with the gate, exit 0);
int-spec 3/3 (real Postgres); tsc --noEmit clean for client + server;
pnpm install --frozen-lockfile consistent (lockfile additive).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 12:37:28 +03:00

60 lines
2.6 KiB
TypeScript

import { fileURLToPath } from 'node:url';
import path from 'node:path';
import { defineConfig } from 'vitest/config';
// Ported docmost-sync tests import the converter through the upstream package
// barrel specifier `docmost-client`. We vendored only the PURE half of that
// package into `src/lib`, so alias the barrel specifier to our local lib
// barrel; everything those tests use (converter, canonicalize, markdown
// envelope, markdownToProseMirror) is re-exported there.
const here = path.dirname(fileURLToPath(import.meta.url));
const libBarrel = path.resolve(here, 'src/lib/index.ts');
export default defineConfig({
resolve: {
alias: {
'docmost-client': libBarrel,
},
},
test: {
environment: 'node',
// Coverage gate (issue #324). The v8 provider is used deliberately: the
// istanbul provider instruments sources by rewriting their AST, which broke
// on the ESM `@docmost/editor-ext` barrel import; v8 collects native
// coverage from the runtime and never re-parses ESM, so it sidesteps that.
// Thresholds are calibrated a few points BELOW the level measured on
// develop so the gate passes today but fails on a real regression. Numbers
// reflect the files actually exercised by the suite (`all: false`).
coverage: {
enabled: true,
provider: 'v8',
reporter: ['text-summary', 'text'],
all: false,
thresholds: {
statements: 88,
branches: 75,
functions: 72,
lines: 88,
},
},
// Runtime suites. The `.test.ts` glob deliberately EXCLUDES the type-only
// contract file (`*.test-d.ts`), which is enforced by the typecheck pass
// below instead — so the 35 runtime suites are never typechecked.
include: ['test/**/*.test.ts'],
// Type-level contract enforcement (Finding #1). Vitest runs `tsc` over the
// `.test-d.ts` files so the `expectTypeOf`/`@ts-expect-error` guards in
// git-sync-client.contract.test-d.ts become REAL build-time assertions: a
// drift in the GitSyncClient result shapes makes `npx vitest run` FAIL with
// a type error. Scoped to `*.test-d.ts` so the runtime suites stay
// untouched, and pointed at the package tsconfig for the strict options.
typecheck: {
enabled: true,
include: ['test/**/*.test-d.ts'],
// A dedicated test-infra tsconfig (NOT the build one) that widens the file
// set to include `test/**` — the build tsconfig scopes `tsc` to `src/**`
// (rootDir ./src), so without this the type-test file is never checked.
tsconfig: './tsconfig.vitest.json',
},
},
});