572 lines
12 KiB
Markdown
572 lines
12 KiB
Markdown
|
|
# 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
|