refactor(test): jest 30 → Vitest 4 — the fleet has one test runner - #157
Merged
Conversation
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 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
npm run verify(format + lint + typecheck + test) green;npm test -- --coveragegreen (thresholds 20/20/10/20 comfortably met at 64/54/54/65);npm run buildgreen with CI's env. tsc was mutation-checked once (a planted type error fails the gate).What changed
projectssplit (server = node env, components = jsdom + RTL setup file), the@/alias, testTimeout 30s, and v8 coverage with the same thresholds. NotransformIgnorePatternsequivalent needed — Vitest runs the ESM-only git deps (ai-kit, @fleet/ai-forms, bip-kit, jose) natively.jest.*→vi.*,jest.requireActual→await vi.importActualwith async factories,jest.Mocktypes →import type { Mock } from 'vitest',jest.setTimeout→vi.setConfig.npm test -- --coverage(vitest has no--ciflag). jest config/setup/deps deleted;@types/jest,ts-jest,jest-environment-jsdomremoved.Trap-list findings (from the OC/evig recipe)
mock*consts at factory-eval time (mockEmailConfig,MockValidationError,codeFactor,MockDangerZone,MockPlacementPanel) →vi.hoisted()or lazy wrappers. Closure-capturedmock*fns (the dominant pattern post-Drizzle) needed nothing.@/lib/config/thresholdsandlucide-reactreturned undefined for untouched exports under jest; they now spreadvi.importActual.require()in tests (fired, 14 files): vitest's shim resolves node builtins/node_modules but not@/aliases or TS files →await import(+vi.mocked),loadBrandhelper made async (no sync contract broken — all call sites were inside async tests).next/linkmocks returning a bare component now return{ default: MockLink }.<span>and its text (🔄Verlegen) — matcher now tolerates both.fail()global (fired, 1 test): →expect.unreachable().pgnever mocked directly — db mocked at@/lib/db),vi.importMock, Proxyhastraps, RTLwaitForunder fake timers (the five fake-timer suites pass unchanged),JEST_WORKER_IDsniffing (none in src).🤖 Generated with Claude Code
https://claude.ai/code/session_01WqKqMnHQHSmkGFfc5t7Rxn