Summary
Migrate the test framework from Jest to Vitest for better performance, native ESM/TypeScript support, and tighter alignment with the Vite ecosystem.
Motivation
- Performance: Vitest runs significantly faster thanks to native ESM and built-in TypeScript/JSX transformation — no
ts-jest or Babel needed.
- Configuration: Vitest reuses
vite.config.ts conventions, reducing config surface (no separate jest.config.ts, jest.setup.ts, path alias duplication).
- ESM-first: Jest's ESM support is still experimental and requires workarounds (
jest.resetModules(), dynamic imports). Vitest handles ESM natively.
- Watch mode: Vitest's watch mode is instant and uses the same HMR pipeline as Vite dev, making TDD faster.
- API compatibility: Vitest is largely API-compatible with Jest (
describe, it, expect, vi.fn(), vi.mock()), so migration is mostly mechanical.
Scope
Files to update/remove
jest.config.ts → remove, replace with vitest config (can live in vite.config.ts or vitest.config.ts)
jest.setup.ts → migrate to Vitest setup file
package.json scripts: test, test:debug, test:watch
- All 41 test suites in
__tests__/ and src/**/ — update imports from @jest/globals to vitest
Key changes per test file
- Replace
import { describe, it, expect, jest, ... } from '@jest/globals' with import { describe, it, expect, vi, ... } from 'vitest'
- Replace
jest.fn() → vi.fn(), jest.mock() → vi.mock(), jest.spyOn() → vi.spyOn(), etc.
- Remove
/** @jest-environment node */ directives (Vitest uses environment in config or per-file // @vitest-environment node)
- Remove
jest.resetModules() / dynamic import() patterns where they were workarounds for Jest's module caching
Dependencies
- Remove:
jest, ts-jest, @jest/globals, jest-environment-jsdom (if present), @types/jest
- Add:
vitest, @vitest/coverage-v8 (optional)
Acceptance criteria
Summary
Migrate the test framework from Jest to Vitest for better performance, native ESM/TypeScript support, and tighter alignment with the Vite ecosystem.
Motivation
ts-jestor Babel needed.vite.config.tsconventions, reducing config surface (no separatejest.config.ts,jest.setup.ts, path alias duplication).jest.resetModules(), dynamic imports). Vitest handles ESM natively.describe,it,expect,vi.fn(),vi.mock()), so migration is mostly mechanical.Scope
Files to update/remove
jest.config.ts→ remove, replace with vitest config (can live invite.config.tsorvitest.config.ts)jest.setup.ts→ migrate to Vitest setup filepackage.jsonscripts:test,test:debug,test:watch__tests__/andsrc/**/— update imports from@jest/globalstovitestKey changes per test file
import { describe, it, expect, jest, ... } from '@jest/globals'withimport { describe, it, expect, vi, ... } from 'vitest'jest.fn()→vi.fn(),jest.mock()→vi.mock(),jest.spyOn()→vi.spyOn(), etc./** @jest-environment node */directives (Vitest usesenvironmentin config or per-file// @vitest-environment node)jest.resetModules()/ dynamicimport()patterns where they were workarounds for Jest's module cachingDependencies
jest,ts-jest,@jest/globals,jest-environment-jsdom(if present),@types/jestvitest,@vitest/coverage-v8(optional)Acceptance criteria
pnpm run test,pnpm run test:watch,pnpm run test:debugworkpackage.jsonpnpm run checkpasses cleanly