- Upgrade Nx 22.1.1 → 22.6.3 and all patch/minor dependencies - Restructure shared libs: move business logic from utils to service - Consolidate shadcn-ui into ui lib with atomic design pattern - Fix container centering for landing app (Tailwind v4 compatibility) - Fix button styling by updating @source directive in globals.css - Fix SiCss3 → SiCss rename in react-icons 5.6 - Fix duplicate useSession export conflict - Remove dead code, comments, and unused files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
18 KiB
Vitest 4.0 Migration Instructions for LLM
Overview
These instructions guide you through migrating an Nx workspace containing multiple Vitest projects from Vitest 3.x to Vitest 4.0. Work systematically through each breaking change category.
Pre-Migration Checklist
-
Identify all Vitest projects:
nx show projects --with-target test -
Locate all Vitest configuration files:
- Search for
vitest.config.{ts,js,mjs} - Search for
vitest.workspace.{ts,js,mjs} - Check
project.jsonfiles for inline Vitest configuration
- Search for
-
Identify affected code:
- Test files:
**/*.{spec,test}.{ts,js,tsx,jsx} - Mock usage: Files using
vi.fn(),vi.spyOn(),vi.mock() - Coverage configuration references
- Test files:
Migration Steps by Category
1. Configuration File Updates
1.1 Coverage Configuration
Search Pattern: coverage in all vitest.config.* files and project.json test target options
Changes Required:
// ❌ BEFORE (Vitest 3.x)
export default defineConfig({
test: {
coverage: {
all: true,
extensions: ['.ts', '.tsx'],
ignoreEmptyLines: false,
experimentalAstAwareRemapping: true,
},
},
});
// ✅ AFTER (Vitest 4.0)
export default defineConfig({
test: {
coverage: {
// Explicitly define files to include in coverage
include: ['src/**/*.{ts,tsx}'],
// Remove: all, extensions, ignoreEmptyLines, experimentalAstAwareRemapping
},
},
});
Action Items:
- Remove
coverage.alloption - Remove
coverage.extensionsoption - Remove
coverage.ignoreEmptyLinesoption - Remove
coverage.experimentalAstAwareRemappingoption - Add explicit
coverage.includepatterns based on project structure - Update any documentation referencing these options
1.2 Pool Options Restructuring
Search Pattern: poolOptions, maxThreads, maxForks, singleThread, singleFork in all Vitest config files
Changes Required:
// ❌ BEFORE (Vitest 3.x)
export default defineConfig({
test: {
maxThreads: 4,
maxForks: 2,
singleThread: false,
poolOptions: {
threads: {
useAtomics: true,
},
vmThreads: {
memoryLimit: '512MB',
},
},
},
});
// ✅ AFTER (Vitest 4.0)
export default defineConfig({
test: {
maxWorkers: 4, // Consolidates maxThreads and maxForks
isolate: true, // Replaces singleThread: false
// Remove: poolOptions, threads.useAtomics
vmMemoryLimit: '512MB', // Moved to top-level
},
});
Action Items:
- Replace
maxThreadsandmaxForkswith singlemaxWorkersoption - Replace
singleThread: trueorsingleFork: truewithmaxWorkers: 1, isolate: false - Move all
poolOptions.*nested options to top-level (e.g.,poolOptions.vmThreads.memoryLimit→vmMemoryLimit) - Remove
threads.useAtomicsoption - Update CI environment variables:
VITEST_MAX_THREADSandVITEST_MAX_FORKS→VITEST_MAX_WORKERS
1.3 Workspace to Projects Rename
Search Pattern: workspace property in Vitest config files
Changes Required:
// ❌ BEFORE (Vitest 3.x)
export default defineConfig({
test: {
workspace: ['apps/*', 'libs/*'],
},
});
// ✅ AFTER (Vitest 4.0)
export default defineConfig({
test: {
projects: ['apps/*', 'libs/*'],
},
});
Action Items:
- Rename
workspaceproperty toprojectsin all config files - Remove external workspace file references (must be inline in config)
- Update
poolMatchGlobsto useprojectspattern matching instead - Update
environmentMatchGlobsto useprojectspattern matching instead
1.4 Browser Configuration
Search Pattern: browser.provider, browser.testerScripts, imports from @vitest/browser
Changes Required:
// ❌ BEFORE (Vitest 3.x)
export default defineConfig({
test: {
browser: {
enabled: true,
provider: 'playwright', // String value
testerScripts: ['./setup.js'],
},
},
});
// Import changes
import { page } from '@vitest/browser';
// ✅ AFTER (Vitest 4.0)
export default defineConfig({
test: {
browser: {
enabled: true,
provider: { name: 'playwright' }, // Object value
testerHtmlPath: './test-setup.html', // Renamed from testerScripts
},
},
});
// Import changes
import { page } from 'vitest/browser';
Action Items:
- Convert
browser.providerstring values to object format:{ name: 'provider-name' } - Replace
browser.testerScriptswithbrowser.testerHtmlPath - Update all imports from
@vitest/browsertovitest/browser - Remove
@vitest/browserfrom dependencies if no longer needed
1.5 Deprecated Configuration Options
Search Pattern: deps.external, deps.inline, deps.fallbackCJS in config files
Changes Required:
// ❌ BEFORE (Vitest 3.x)
export default defineConfig({
test: {
deps: {
external: ['some-package'],
inline: ['inline-package'],
fallbackCJS: true,
},
},
});
// ✅ AFTER (Vitest 4.0)
export default defineConfig({
test: {
server: {
deps: {
external: ['some-package'],
inline: ['inline-package'],
fallbackCJS: true,
},
},
},
});
Action Items:
- Move
deps.*options underserver.depsnamespace - Remove
poolMatchGlobs(useprojectswith conditions instead) - Remove
environmentMatchGlobs(useprojectswith conditions instead)
2. Test Code Updates
2.1 Mock Function Name Changes
Search Pattern: .getMockName() calls in test files
Changes Required:
// ❌ BEFORE (Vitest 3.x)
const mockFn = vi.fn();
expect(mockFn.getMockName()).toBe('spy'); // Old default
// ✅ AFTER (Vitest 4.0)
const mockFn = vi.fn();
expect(mockFn.getMockName()).toBe('vi.fn()'); // New default
// If you need custom names, set them explicitly
const namedMock = vi.fn().mockName('myCustomName');
expect(namedMock.getMockName()).toBe('myCustomName');
Action Items:
- Update test assertions checking default mock names from
'spy'to'vi.fn()' - Add explicit
.mockName()calls where specific names are required
2.2 Mock Invocation Call Order
Search Pattern: .mock.invocationCallOrder in test files
Changes Required:
// ❌ BEFORE (Vitest 3.x)
const mockFn = vi.fn();
mockFn();
expect(mockFn.mock.invocationCallOrder[0]).toBe(0); // Started at 0
// ✅ AFTER (Vitest 4.0)
const mockFn = vi.fn();
mockFn();
expect(mockFn.mock.invocationCallOrder[0]).toBe(1); // Now starts at 1 (Jest-compatible)
Action Items:
- Update assertions on
invocationCallOrderto account for 1-based indexing - Search for off-by-one errors in call order comparisons
2.3 Constructor Spies and Mocks
Search Pattern: vi.spyOn on constructors, vi.fn() used as constructors
Changes Required:
// ❌ BEFORE (Vitest 3.x) - Arrow function constructors might have worked
const MockConstructor = vi.fn(() => ({ value: 42 }));
new MockConstructor(); // May have worked in v3
// ✅ AFTER (Vitest 4.0) - Must use function or class
const MockConstructor = vi.fn(function () {
return { value: 42 };
});
new MockConstructor(); // Correctly supports 'new'
// Or use class syntax
class MockClass {
value = 42;
}
const MockConstructor = vi.fn(MockClass);
Action Items:
- Convert arrow function mocks used as constructors to
functionkeyword orclasssyntax - Test all constructor spies to ensure
newkeyword works correctly - Update any mocks that expect constructor behavior
2.4 RestoreAllMocks Behavior
Search Pattern: vi.restoreAllMocks() in test files
Changes Required:
// ❌ BEFORE (Vitest 3.x)
vi.mock('./module', () => ({ fn: vi.fn() }));
vi.restoreAllMocks(); // Would restore automocks
// ✅ AFTER (Vitest 4.0)
vi.mock('./module', () => ({ fn: vi.fn() }));
vi.restoreAllMocks(); // Only restores manual spies, NOT automocks
// To reset automocks, use:
vi.unmock('./module');
// or
vi.resetModules();
Action Items:
- Review all
vi.restoreAllMocks()usage - Add explicit
vi.unmock()orvi.resetModules()calls for automocked modules - Ensure test isolation is maintained after this change
2.5 SpyOn Return Value Changes
Search Pattern: vi.spyOn() on already mocked functions
Changes Required:
// ❌ BEFORE (Vitest 3.x)
const mock = vi.fn();
const spy = vi.spyOn({ method: mock }, 'method');
// spy !== mock (created new spy)
// ✅ AFTER (Vitest 4.0)
const mock = vi.fn();
const spy = vi.spyOn({ method: mock }, 'method');
// spy === mock (returns same instance)
Action Items:
- Review code that creates spies on existing mocks
- Remove redundant spy creation if same instance is returned
- Update assertions that check spy identity
2.6 Automock Behavior Changes
Search Pattern: vi.mock() with factory functions, .mockRestore() on automocks
Changes Required:
// ❌ BEFORE (Vitest 3.x)
vi.mock('./utils', () => ({
get value() {
return 42;
}, // Would call getter
}));
import { value } from './utils';
console.log(value); // Would execute getter logic
// Restore might have worked
const spy = vi.spyOn(obj, 'method');
spy.mockRestore(); // Might work on automocks
// ✅ AFTER (Vitest 4.0)
vi.mock('./utils', () => ({
get value() {
return 42;
},
}));
import { value } from './utils';
console.log(value); // Returns undefined (doesn't call getter)
// Explicitly return value if needed
vi.mock('./utils', () => ({
value: 42, // Not a getter
}));
// mockRestore no longer works on automocks
const spy = vi.spyOn(obj, 'method');
spy.mockRestore(); // Throws error if method is automocked
// Use unmock instead
vi.unmock('./module');
Action Items:
- Convert automocked getters to plain property values where needed
- Remove
.mockRestore()calls on automocked methods - Use
vi.unmock()to clear automocks instead - Test instance method isolation (they now share state with prototype)
2.7 Settled Results Immediate Population
Search Pattern: .mock.settledResults in test files
Changes Required:
// ✅ AFTER (Vitest 4.0)
const asyncMock = vi.fn(async () => 'result');
const promise = asyncMock();
// settledResults is immediately populated with 'incomplete' status
expect(asyncMock.mock.settledResults[0]).toEqual({
type: 'incomplete',
value: undefined,
});
// After promise resolves
await promise;
expect(asyncMock.mock.settledResults[0]).toEqual({
type: 'fulfilled',
value: 'result',
});
Action Items:
- Update tests that check
settledResultsbefore promise resolution - Handle
'incomplete'status in assertions - Ensure tests properly await promises before checking settled results
3. Reporter and CLI Changes
3.1 Reporter API Changes
Search Pattern: Custom reporters, onCollected, onTaskUpdate, onFinished
Changes Required:
// ❌ BEFORE (Vitest 3.x)
export default {
onCollected(files) {
// Handle collected files
},
onTaskUpdate(task) {
// Handle task update
},
onFinished(files) {
// Handle completion
},
};
// ✅ AFTER (Vitest 4.0)
// Use new reporter API - consult Vitest 4 docs for replacement methods
Action Items:
- Review custom reporters for removed API usage
- Consult Vitest 4 documentation for new reporter API
- Update or rewrite custom reporters to use new APIs
3.2 Built-in Reporter Changes
Search Pattern: reporters: ['basic'], reporters: ['verbose']
Changes Required:
// ❌ BEFORE (Vitest 3.x)
export default defineConfig({
test: {
reporters: ['basic'],
},
});
// ✅ AFTER (Vitest 4.0)
export default defineConfig({
test: {
reporters: [['default', { summary: false }]], // Equivalent to 'basic'
},
});
// For verbose (tree output)
reporters: ['tree']; // Use 'tree' for hierarchical output
Action Items:
- Replace
'basic'reporter with['default', { summary: false }] - Replace
'verbose'reporter with'tree'for hierarchical output - Update CI configuration if reporters are specified there
4. Snapshot Changes
4.1 Custom Elements Shadow Root
Search Pattern: Snapshot tests involving custom elements or Web Components
Changes Required:
// ✅ AFTER (Vitest 4.0)
// Shadow root contents now printed by default in snapshots
// If you want old behavior (don't print shadow root):
export default defineConfig({
test: {
printShadowRoot: false,
},
});
Action Items:
- Review snapshot tests for custom elements
- Update snapshots if shadow root contents are now included
- Add
printShadowRoot: falseif old behavior is required
5. Environment Variable Updates
Search Pattern: CI/CD configuration files, .env files, documentation
Changes Required:
# ❌ BEFORE (Vitest 3.x)
VITEST_MAX_THREADS=4
VITEST_MAX_FORKS=2
VITE_NODE_DEPS_MODULE_DIRECTORIES=/custom/path
# ✅ AFTER (Vitest 4.0)
VITEST_MAX_WORKERS=4
VITEST_MODULE_DIRECTORIES=/custom/path
Action Items:
- Update CI/CD pipeline environment variables
- Update
.envfiles - Update documentation referencing old environment variables
- Search for
VITEST_MAX_THREADS,VITEST_MAX_FORKS,VITE_NODE_DEPS_MODULE_DIRECTORIES
6. Advanced: Module Runner Changes
Search Pattern: vitest/execute, __vitest_executor, vite-node
Changes Required:
// ❌ BEFORE (Vitest 3.x)
import { execute } from 'vitest/execute';
// Access to __vitest_executor
// ✅ AFTER (Vitest 4.0)
// Use Vite's Module Runner API instead
// Consult Vite Module Runner documentation
Action Items:
- If using
vitest/execute, migrate to Vite Module Runner - Remove dependencies on
__vitest_executor - Update custom pool implementations (complete rewrite needed)
7. Type Definition Updates
Search Pattern: TypeScript imports from vitest, type errors after upgrade
Changes Required:
// All deprecated type exports removed
// If you get TypeScript errors about missing types:
// - Check if you're using deprecated type names
// - Update to current type names from Vitest 4 API
// - Remove explicit @types/node if it was only needed due to Vitest bug
Action Items:
- Run TypeScript compilation on all test files
- Fix any type errors related to removed Vitest type definitions
- Review
@types/nodeusage (may no longer be accidentally included)
Post-Migration Validation
1. Run Tests Per Project
# Test each project individually
nx run-many -t test -p PROJECT_NAME
2. Run All Tests
# Run tests across all affected projects
nx affected -t test
3. Check Coverage
# Verify coverage generation works with new config
nx affected -t test --coverage
4. Validate CI Pipeline
# Run full CI validation
nx prepush
5. Review Migration Checklist
- All configuration files updated
- All test files pass
- Coverage reports generate correctly
- CI/CD pipeline runs successfully
- Environment variables updated
- Documentation updated
- No deprecated API warnings in console
Common Issues and Solutions
Issue: Coverage includes too many files
Solution: Add explicit coverage.include patterns to match your source files
Issue: Tests fail with "arrow function constructors not supported"
Solution: Convert arrow functions used as constructors to function keyword or class syntax
Issue: Automocks not resetting between tests
Solution: Use vi.unmock() or vi.resetModules() instead of vi.restoreAllMocks()
Issue: Mock call order assertions failing
Solution: Update to 1-based indexing for invocationCallOrder
Issue: Browser tests failing after upgrade
Solution: Check browser provider is object format and imports use vitest/browser
Issue: TypeScript errors in test files
Solution: Update to new type definitions and remove usage of deprecated types
Files to Review
Create a checklist of all files that need review:
# Configuration files
find . -name "vitest.config.*" -o -name "vitest.workspace.*"
find . -name "project.json" -exec grep -l "vitest" {} \;
# Test files
find . -name "*.spec.*" -o -name "*.test.*"
# Files with mock usage
rg "vi\.(fn|spyOn|mock|restoreAllMocks)" --type ts --type tsx --type js
# Files with coverage config
rg "coverage\.(all|extensions|ignoreEmptyLines)" --type ts --type js
# CI configuration
find . -name ".github/workflows/*.yml" -o -name ".gitlab-ci.yml" -o -name "azure-pipelines.yml"
Migration Strategy for Large Workspaces
- Migrate in phases: Start with a small project, validate, then expand
- Use feature branches: Create separate branches for different migration aspects
- Run tests frequently: After each configuration change, run affected tests
- Document issues: Keep track of project-specific issues and solutions
- Automate where possible: Create codemods for repetitive changes
Useful Commands During Migration
# Find all vitest configurations
nx show projects --with-target test
# Test specific project after changes
nx test PROJECT_NAME
# Test all affected
nx affected -t test
# View project details
nx show project PROJECT_NAME --web
# Clear Nx cache if needed
nx reset
Guard Rails
DO NOT
- Force tests to pass by removing test logic and replacing it with
expect(true).toBe(true) - Remove assertions
- Add additional mocks that force tests to pass
Notes for LLM Execution
When executing this migration:
- Work systematically: Complete one category before moving to the next
- Test after each change: Don't batch all changes without validation
- Keep user informed: Report progress through each section
- Handle errors promptly: If tests fail, fix immediately before proceeding
- Update documentation: Note any workspace-specific patterns or issues
- Create meaningful commits: Group related changes together with clear messages
- Use TodoWrite tool: Track migration progress for visibility