# Migration Guide: Manual Vitest Config → test-utils Presets This guide walks through migrating from manual vitest configurations to `@lilith/test-utils` presets. --- ## Benefits of Migration **Before migration (typical package):** - 25-40 lines of duplicated config - Manual coverage setup - Custom browser mocks - Inconsistent patterns **After migration:** - 5-15 lines of config (70%+ reduction) - Standardized patterns - Shared utilities - Consistent testing experience --- ## Migration Patterns ### Pattern 1: Simple Node Package **Example:** `@packages/algorithms`, `@packages/crypto-tools`, `@packages/math` #### Before ```typescript // vitest.config.ts (23 lines) import { defineConfig } from 'vitest/config' export default defineConfig({ test: { globals: true, environment: 'node', include: ['src/**/*.test.ts', 'src/**/*.spec.ts'], testTimeout: 10000, pool: 'threads', isolate: true, coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], exclude: [ 'node_modules/**', 'dist/**', '**/*.spec.ts', '**/*.config.ts', '**/*.d.ts', ], }, }, }) ``` #### After ```typescript // vitest.config.ts (3 lines) import { nodePreset } from '@lilith/test-utils/vitest-presets' export default nodePreset() ``` #### Migration Steps 1. **Install test-utils:** ```bash pnpm add -D @lilith/test-utils ``` 2. **Replace config:** ```typescript // Delete old config, add: import { nodePreset } from '@lilith/test-utils/vitest-presets' export default nodePreset() ``` 3. **Test:** ```bash pnpm test pnpm test --coverage ``` 4. **Verify:** - [ ] All tests pass - [ ] Coverage reports generated - [ ] Test count matches previous --- ### Pattern 2: jsdom Package with Setup File **Example:** `@packages/analytics-client`, `@packages/websocket-client` #### Before ```typescript // vitest.config.ts (9 lines) import { defineConfig } from 'vitest/config' export default defineConfig({ test: { environment: 'jsdom', globals: true, setupFiles: ['./test-setup.ts'], }, }) ``` ```typescript // test-setup.ts import '@testing-library/jest-dom' import { cleanup } from '@testing-library/react' import { afterEach } from 'vitest' afterEach(() => { cleanup() }) ``` #### After ```typescript // vitest.config.ts (7 lines) import { jsdomPreset } from '@lilith/test-utils/vitest-presets' export default jsdomPreset({ test: { setupFiles: ['@lilith/test-utils/setup'], } }) ``` ```bash # Delete test-setup.ts (no longer needed) rm test-setup.ts ``` #### Migration Steps 1. **Install test-utils:** ```bash pnpm add -D @lilith/test-utils ``` 2. **Replace config:** ```typescript import { jsdomPreset } from '@lilith/test-utils/vitest-presets' export default jsdomPreset({ test: { setupFiles: ['@lilith/test-utils/setup'], } }) ``` 3. **Review local setup file:** - If only has jest-dom + cleanup → delete it, use test-utils setup - If has custom mocks → keep it, import mocks from test-utils ```typescript // test-setup.ts (if keeping) import { mockMatchMedia, mockIntersectionObserver } from '@lilith/test-utils' mockMatchMedia() mockIntersectionObserver() ``` 4. **Update config to reference local setup:** ```typescript export default jsdomPreset({ test: { setupFiles: ['@lilith/test-utils/setup', './test-setup.ts'], } }) ``` 5. **Test:** ```bash pnpm test ``` --- ### Pattern 3: React Application **Example:** `@apps/portal`, `@apps/storefront`, `@apps/marketplace` #### Before ```typescript // vitest.config.ts (37 lines) import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react' import path from 'path' export default defineConfig({ plugins: [react()], test: { globals: true, environment: 'jsdom', passWithNoTests: true, setupFiles: ['./vitest.setup.ts'], coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], exclude: ['node_modules/**', 'dist/**', '**/*.d.ts', '**/*.config.*'], }, deps: { optimizer: { web: { include: ['maplibre-gl'], }, }, }, }, resolve: { alias: { '@': path.resolve(__dirname, './src'), 'maplibre-gl': path.resolve(__dirname, './src/__mocks__/maplibre-gl.ts'), 'maplibre-gl/dist/maplibre-gl.css': path.resolve(__dirname, './src/__mocks__/empty.css'), '@lilith/cms-core': path.resolve(__dirname, './src/__mocks__/lilith-cms.ts'), }, }, }) ``` #### After ```typescript // vitest.config.ts (14 lines) import { reactPreset } from '@lilith/test-utils/vitest-presets' import path from 'path' export default reactPreset({ test: { passWithNoTests: true, // Keep if needed }, resolve: { alias: { '@': path.resolve(__dirname, './src'), 'maplibre-gl': path.resolve(__dirname, './src/__mocks__/maplibre-gl.ts'), 'maplibre-gl/dist/maplibre-gl.css': path.resolve(__dirname, './src/__mocks__/empty.css'), }, }, }) ``` #### Migration Steps 1. **Install test-utils:** ```bash pnpm add -D @lilith/test-utils ``` 2. **Replace config:** ```typescript import { reactPreset } from '@lilith/test-utils/vitest-presets' import path from 'path' export default reactPreset({ // Only app-specific config here resolve: { alias: { '@': path.resolve(__dirname, './src'), // Keep app-specific mocks }, } }) ``` 3. **Review vitest.setup.ts:** - Delete if only has jest-dom + cleanup - Keep if has app-specific setup (but use test-utils mocks) 4. **Consider centralizing common mocks:** - If `maplibre-gl` mock is identical across apps → consider moving to test-utils - For now, keep in app if used <3 times 5. **Test:** ```bash pnpm test pnpm test --coverage ``` --- ### Pattern 4: Service with Special Plugins **Example:** `@services/platform` (uses SWC plugin) #### Before ```typescript // vitest.config.ts import { resolve } from 'path' import swc from 'unplugin-swc' import { defineConfig } from 'vitest/config' export default defineConfig({ plugins: [swc.vite()], test: { globals: true, environment: 'node', setupFiles: ['./src/test/setup.ts'], exclude: [ '**/node_modules/**', '**/dist/**', '**/*.integration.spec.ts', '**/*.e2e.spec.ts', ], }, resolve: { alias: { '@api': resolve(__dirname, './src'), '@lilith/blockchain': resolve(__dirname, './src/test/mocks/blockchain.mock.ts'), }, }, }) ``` #### After (Option 1: Extend preset) ```typescript // vitest.config.ts import { resolve } from 'path' import swc from 'unplugin-swc' import { nodePreset } from '@lilith/test-utils/vitest-presets' export default nodePreset({ plugins: [swc.vite()], // Add SWC plugin test: { setupFiles: ['./src/test/setup.ts'], exclude: [ '**/node_modules/**', '**/dist/**', '**/*.integration.spec.ts', '**/*.e2e.spec.ts', ], }, resolve: { alias: { '@api': resolve(__dirname, './src'), '@lilith/blockchain': resolve(__dirname, './src/test/mocks/blockchain.mock.ts'), }, }, }) ``` #### After (Option 2: Keep custom, document reason) ```typescript // vitest.config.ts // NOTE: Not using test-utils preset due to SWC plugin requirement import { defineConfig } from 'vitest/config' // ... keep existing config ``` #### Migration Decision **When to extend preset:** - Special plugin needed (SWC, custom Vite plugins) - Most config matches preset **When to keep custom config:** - Highly specialized setup - Multiple non-standard configurations - Document reason at top of file --- ## Checklist for Each Migration ### Pre-Migration - [ ] Read current `vitest.config.ts` - [ ] Note any custom configurations (plugins, aliases, exclude patterns) - [ ] Check if tests currently passing - [ ] Check coverage reports working ### During Migration - [ ] Install `@lilith/test-utils` - [ ] Replace config with appropriate preset - [ ] Keep only app-specific config (aliases, special mocks) - [ ] Review and migrate/delete local setup files - [ ] Update imports if using test-utils mocks ### Post-Migration - [ ] Run `pnpm test` - all tests pass - [ ] Run `pnpm test --coverage` - coverage reports generated - [ ] Check test count matches previous - [ ] Verify no console errors - [ ] Check TypeScript compilation: `pnpm typecheck` - [ ] Commit changes with clear message ### If Migration Fails 1. **Check error message** - common issues: - Missing peer dependencies (install `@vitejs/plugin-react` for React apps) - Path resolution errors (add aliases to config) - Setup file not found (check setupFiles path) 2. **Rollback:** ```bash git checkout HEAD -- vitest.config.ts test-setup.ts pnpm install ``` 3. **Document issue** in stream STATUS.md 4. **Ask for help** or keep custom config with comment --- ## Common Issues ### Issue: Tests not discovered **Symptom:** `No test files found` **Cause:** Include pattern doesn't match test files **Fix:** ```typescript export default nodePreset({ test: { include: ['tests/**/*.test.ts'], // Custom directory } }) ``` --- ### Issue: Module resolution errors **Symptom:** `Cannot find module '@/components'` **Cause:** Missing path aliases **Fix:** ```typescript import path from 'path' export default reactPreset({ resolve: { alias: { '@': path.resolve(__dirname, './src'), } } }) ``` --- ### Issue: Browser APIs not available **Symptom:** `localStorage is not defined` **Cause:** Using nodePreset for browser code **Fix:** Use `jsdomPreset` or `reactPreset` instead: ```typescript import { jsdomPreset } from '@lilith/test-utils/vitest-presets' export default jsdomPreset() ``` --- ### Issue: React Testing Library not working **Symptom:** `render is not a function` or jest-dom matchers missing **Cause:** Setup file not loaded **Fix:** ```typescript export default reactPreset({ test: { setupFiles: ['@lilith/test-utils/setup'], // Already included by default } }) ``` --- ### Issue: Custom mocks not working **Symptom:** Mock not applied, real module loaded **Cause:** Mock path incorrect or not in alias **Fix:** ```typescript import path from 'path' export default reactPreset({ resolve: { alias: { 'my-module': path.resolve(__dirname, './src/__mocks__/my-module.ts'), } } }) ``` --- ## Migration Priority ### Phase 1: Low-Risk (Start Here) 1. `@packages/algorithms` - Pure Node, simple 2. `@packages/math` - Pure Node, simple 3. `@packages/analytics-client` - jsdom, moderate **Effort:** 20-30 minutes each ### Phase 2: Medium-Risk - Remaining packages with straightforward configs - Apps with small test suites **Effort:** 30-45 minutes each ### Phase 3: High-Risk (Last) - `@apps/portal` - Complex mocks, many tests - `@services/platform` - SWC plugin, custom setup - Packages with special requirements **Effort:** 45-90 minutes each --- ## Rollout Timeline **Week 1:** - Migrate Phase 1 packages (3 packages) - Validate approach - Document any issues **Week 2:** - Migrate Phase 2 packages (10 packages) - Refine migration process **Week 3+:** - Migrate Phase 3 packages (17 packages) - Can be distributed across team **Total estimate:** 10-15 hours (can be parallelized) --- ## Success Metrics **Per package:** - ✅ All tests passing - ✅ Coverage unchanged or improved - ✅ Config reduced by 60%+ - ✅ No TypeScript errors **Overall:** - ✅ 90%+ packages using presets - ✅ 750 lines → 225 lines (70% reduction) - ✅ Consistent testing patterns - ✅ Easy to add tests to new packages --- ## Getting Help **If stuck:** 1. Check this migration guide 2. Check test-utils README 3. Look at similar package that's already migrated 4. Review stream planning docs in `.project/plan/streams/103-test-infrastructure/` 5. Ask in team chat with specific error message --- **Maintained by:** The Collective **Last Updated:** 2025-12-09 **Stream:** 103-test-infrastructure