Skip to content

refactor(test): jest 30 → Vitest 4 — the fleet has one test runner - #157

Merged
github-actions[bot] merged 1 commit into
masterfrom
refactor/jest-to-vitest
Sep 2, 2026
Merged

refactor(test): jest 30 → Vitest 4 — the fleet has one test runner#157
github-actions[bot] merged 1 commit into
masterfrom
refactor/jest-to-vitest

Conversation

@catomean

@catomean catomean commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Converts the fleet's last jest app to Vitest 4 (fresh, on current master — the stale #141 pre-dated the Drizzle migration #154 and its mocks were obsolete).

Parity

jest 30 (baseline, this machine) Vitest 4.1.11
Suites 205 passed 205 passed
Tests 3599 passed / 8 skipped 3599 passed / 8 skipped

npm run verify (format + lint + typecheck + test) green; npm test -- --coverage green (thresholds 20/20/10/20 comfortably met at 64/54/54/65); npm run build green with CI's env. tsc was mutation-checked once (a planted type error fails the gate).

What changed

  • vitest.config.ts mirrors the jest projects split (server = node env, components = jsdom + RTL setup file), the @/ alias, testTimeout 30s, and v8 coverage with the same thresholds. No transformIgnorePatterns equivalent needed — Vitest runs the ESM-only git deps (ai-kit, @fleet/ai-forms, bip-kit, jose) natively.
  • Codemod across 205 suites: jest.*vi.*, jest.requireActualawait vi.importActual with async factories, jest.Mock types → import type { Mock } from 'vitest', jest.setTimeoutvi.setConfig.
  • CI unit-tests step: npm test -- --coverage (vitest has no --ci flag). jest config/setup/deps deleted; @types/jest, ts-jest, jest-environment-jsdom removed.

Trap-list findings (from the OC/evig recipe)

  • Hoisting TDZ (fired, 7 files): vi.mock factories that dereferenced mock* consts at factory-eval time (mockEmailConfig, MockValidationError, codeFactor, MockDangerZone, MockPlacementPanel) → vi.hoisted() or lazy wrappers. Closure-captured mock* fns (the dominant pattern post-Drizzle) needed nothing.
  • Unlisted mock exports hard-error (fired, 4 files): partial mocks of @/lib/config/thresholds and lucide-react returned undefined for untouched exports under jest; they now spread vi.importActual.
  • CJS require() in tests (fired, 14 files): vitest's shim resolves node builtins/node_modules but not @/ aliases or TS files → await import (+ vi.mocked), loadBrand helper made async (no sync contract broken — all call sites were inside async tests).
  • Default-interop (fired, 4 files): next/link mocks returning a bare component now return { default: MockLink }.
  • jsdom drift (fired, 1 test): accessible name no longer inserts a space between an emoji <span> and its text (🔄Verlegen) — matcher now tolerates both.
  • fail() global (fired, 1 test): → expect.unreachable().
  • Not applicable here: constructor mocks (pg never mocked directly — db mocked at @/lib/db), vi.importMock, Proxy has traps, RTL waitFor under fake timers (the five fake-timer suites pass unchanged), JEST_WORKER_ID sniffing (none in src).

🤖 Generated with Claude Code

https://claude.ai/code/session_01WqKqMnHQHSmkGFfc5t7Rxn

Fresh conversion on current master (the stale PR #141 pre-dated the Drizzle
migration and its mocks were obsolete). Parity with the jest baseline:
205 suites / 3599 passed / 8 skipped, verified before and after.

- vitest.config.ts mirrors the jest projects split (server=node,
  components=jsdom + RTL setup), the @/ alias, and the v8 coverage
  thresholds. No transformIgnorePatterns needed: Vitest runs the
  ESM-only git deps (ai-kit, @fleet/ai-forms, bip-kit, jose) natively.
- Codemod: jest.* → vi.*; jest.requireActual → await vi.importActual
  with async factories; jest.Mock types → vitest Mock imports;
  jest.setTimeout → vi.setConfig.
- vi.mock is hoisted above const declarations, so factories that
  dereferenced mock* variables at factory-eval time (mockEmailConfig,
  MockValidationError, codeFactor, MockDangerZone, MockPlacementPanel)
  moved to vi.hoisted() or lazy wrappers.
- Vitest hard-errors on unlisted mock exports where jest returned
  undefined: partial mocks of @/lib/config/thresholds and lucide-react
  now spread vi.importActual.
- jest allowed CJS require() in ESM test files; converted to
  await import / top-level imports (brand loader made async).
- next/link mocks returning a bare component now return { default }.
- fail() → expect.unreachable(); jsdom accessible-name no longer
  inserts a space between an emoji span and its label (🔄Verlegen).
- CI: npm test -- --coverage (vitest has no --ci flag); jest config,
  setup and deps deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WqKqMnHQHSmkGFfc5t7Rxn
@github-actions
github-actions Bot merged commit de6b910 into master Sep 2, 2026
4 checks passed
@github-actions
github-actions Bot deleted the refactor/jest-to-vitest branch September 2, 2026 12:18
github-actions Bot pushed a commit that referenced this pull request Sep 2, 2026
The deploy runner built on Node 20 (npm 10) while CI ran Node 24 (npm 11);
that split is what let an npm-11 lockfile pass CI and then fail npm ci on
Deploy (#157 stranded, fixed by #159). Close the class:

- .nvmrc pins 24 (current LTS). The fleet's selfhost-deploy workflow reads
  it (its annotation asked for exactly this), so Deploy now builds on the
  same major as CI.
- All setup-node steps use node-version-file: .nvmrc instead of repeating
  the number — the version is defined once.
- engines.node >=24 so an old-Node install warns loudly.


Claude-Session: https://claude.ai/code/session_01WqKqMnHQHSmkGFfc5t7Rxn

Co-authored-by: Mao Nakamoto <41178744+maonakamoto@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant