@nx/vite 23.0.0-beta.20 → 23.0.0-beta.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/plugins/nx-copy-assets.plugin.d.ts +6 -0
  2. package/dist/plugins/nx-copy-assets.plugin.js +8 -0
  3. package/dist/plugins/nx-tsconfig-paths.plugin.d.ts +7 -0
  4. package/dist/plugins/nx-tsconfig-paths.plugin.js +9 -0
  5. package/dist/src/generators/configuration/configuration.js +2 -0
  6. package/dist/src/generators/convert-to-inferred/convert-to-inferred.js +2 -0
  7. package/dist/src/generators/init/init.js +2 -0
  8. package/dist/src/generators/init/lib/utils.js +1 -1
  9. package/dist/src/generators/init/schema.json +1 -1
  10. package/dist/src/generators/setup-paths-plugin/setup-paths-plugin.js +2 -0
  11. package/dist/src/migrations/update-23-0-0/ai-instructions-for-vitest-3.md +604 -0
  12. package/dist/src/migrations/update-23-0-0/ai-instructions-for-vitest-4.md +817 -0
  13. package/dist/src/migrations/update-23-0-0/lib/ast-edits.d.ts +26 -0
  14. package/dist/src/migrations/update-23-0-0/lib/ast-edits.js +49 -0
  15. package/dist/src/migrations/update-23-0-0/lib/ci-files.d.ts +3 -0
  16. package/dist/src/migrations/update-23-0-0/lib/ci-files.js +30 -0
  17. package/dist/src/migrations/update-23-0-0/lib/vitest-config-files.d.ts +5 -0
  18. package/dist/src/migrations/update-23-0-0/lib/vitest-config-files.js +34 -0
  19. package/dist/src/migrations/update-23-0-0/migrate-to-vitest-3.d.ts +10 -0
  20. package/dist/src/migrations/update-23-0-0/migrate-to-vitest-3.js +335 -0
  21. package/dist/src/migrations/update-23-0-0/migrate-to-vitest-4.d.ts +17 -0
  22. package/dist/src/migrations/update-23-0-0/migrate-to-vitest-4.js +726 -0
  23. package/dist/src/utils/assert-supported-vite-version.d.ts +2 -0
  24. package/dist/src/utils/assert-supported-vite-version.js +8 -0
  25. package/dist/src/utils/deprecation.d.ts +4 -0
  26. package/dist/src/utils/deprecation.js +26 -1
  27. package/dist/src/utils/ensure-dependencies.js +1 -1
  28. package/dist/src/utils/generator-utils.js +2 -0
  29. package/dist/src/utils/versions.d.ts +1 -0
  30. package/dist/src/utils/versions.js +7 -1
  31. package/migrations.json +116 -9
  32. package/package.json +6 -6
@@ -0,0 +1,817 @@
1
+ # Vitest 4.0 Migration Instructions for LLM
2
+
3
+ ## Overview
4
+
5
+ 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.
6
+
7
+ <pre_pass_summary note="a deterministic pre-pass already applied these specific edits; verify the new shape is in place rather than redoing them">
8
+
9
+ The pre-pass handled, mechanically:
10
+
11
+ - dead `coverage.{all,extensions,ignoreEmptyLines,experimentalAstAwareRemapping}` removal
12
+ - `test.workspace` → `test.projects` rename (the property only — external `vitest.workspace.*` files are NOT inlined)
13
+ - `@vitest/browser/context` import-path rewrite to `vitest/browser`
14
+ - `deps.optimizer.web` → `deps.optimizer.client` rename
15
+ - `poolOptions.threads.useAtomics` and top-level `test.minWorkers` removal
16
+ - `'verbose'` → `'tree'` and `'basic'` → `['default', { summary: false }]` inside `test.reporters`
17
+ - `VITEST_MAX_{THREADS,FORKS}` → `VITEST_MAX_WORKERS` and `VITE_NODE_DEPS_MODULE_DIRECTORIES` → `VITEST_MODULE_DIRECTORIES` renames in: `package.json` scripts, `.env` / `.env.*` files, `project.json` `options.env` keys, and inline `VAR=value` prefixes inside `project.json` `options.{args,command,commands}`
18
+
19
+ The pre-pass **skips the rename when both `VITEST_MAX_THREADS` and `VITEST_MAX_FORKS` appear in the same file/scope** (they collapse to a single `VITEST_MAX_WORKERS` whose value depends on which pool the project uses — a decision the pre-pass can't make safely). It also **does not** edit CI provider configs (`.github/workflows/*.yml`, `.gitlab-ci.yml`, `azure-pipelines.yml`, `.circleci/config.yml`, `bitbucket-pipelines.yml`) — YAML structure varies too much. Any conflicts and any CI matches are forwarded to you in `<advisory_context>`.
20
+
21
+ **The cross-cutting changes below still require your attention** — pool option flattening (`singleThread`/`singleFork`, `maxThreads`/`maxForks`, `poolOptions.<pool>.{execArgv,isolate}`, `poolOptions.vmThreads.memoryLimit`), `test.deps.{external,inline,fallbackCJS}` → `test.server.deps.*` move, `test.{poolMatchGlobs,environmentMatchGlobs}` projects rewrite, browser provider function-form rewrite, `browser.testerScripts` → `testerHtmlPath`, `vitest.workspace.*` file inlining + `defineWorkspace` removal, custom reporter callback API updates, and `@vitest/browser` package replacement with per-provider packages.
22
+
23
+ How to read the wrapper sections above this file:
24
+
25
+ - `<files_changed>` lists files the pre-pass already wrote to. Verify the new shape is in place; do not re-apply the same edit.
26
+ - `<advisory_context>` lists detections the pre-pass forwarded because it could not safely complete them. **Every entry is pending work** — address each one in the relevant section below, not as a separate task.
27
+
28
+ A workspace-wide reminder is also emitted as a post-run "next step" about env vars set in CI provider dashboards — those can't be detected from the workspace tree.
29
+
30
+ </pre_pass_summary>
31
+
32
+ <handoff_guidance>
33
+ In your handoff `summary` (1–3 sentences per the system prompt), name the breaking-change categories you applied; explicitly call out any you skipped because they didn't apply (e.g., "no custom reporters in this workspace", "no browser-mode configs").
34
+ </handoff_guidance>
35
+
36
+ <prerequisites note="hard runtime requirements; do not edit any vitest config until these are satisfied">
37
+
38
+ Vitest 4 has hard runtime requirements:
39
+
40
+ - **Vite ≥ 6.0.0** (Vite 5 is unsupported). Check with `npx vite --version`. If on Vite 5, apply the Vite 6 / 7 / 8 migration guides first.
41
+ - **Node.js ≥ 20.0.0** (Node 18 support dropped). Check with `node --version`. Update CI `actions/setup-node` versions, `.nvmrc`, `engines` in `package.json`, and Docker base images.
42
+
43
+ If either prerequisite is unmet, write status: failed with the unmet requirement in `summary` — config-level migration on an unsupported runtime will produce confusing errors.
44
+
45
+ </prerequisites>
46
+
47
+ ## Nx-Specific Notes (read first)
48
+
49
+ - **Inferred plugin targets**: modern Nx workspaces use `@nx/vitest/plugin` (or historically `@nx/vite/plugin`) to _infer_ test targets from `vitest.config.*` presence. `project.json` may have no `test` target. Inspect inferred targets with `nx show project <name> --json | jq .targets`. Renaming/moving `vitest.config.*` invalidates inference; run `nx reset` after structural moves.
50
+ - **`@nx/vitest:test` / `@nx/vite:test` executor options**: when present in `project.json`, the relevant options are `configFile`, `reportsDirectory`, `mode`, `testFiles`, `watch`. Most option-level breaking changes in v4 are inside `vitest.config.*`, not these. The exception: any `--segfault-retry` in the executor `args` is now invalid and must be removed.
51
+ - **Shared base config**: apply transforms to a workspace-root `vitest.config.base.ts` (extended via `mergeConfig`) BEFORE per-project overrides. Otherwise inherited options may shadow the migrated ones.
52
+ - **Angular projects using AnalogJS** (`@analogjs/vitest-angular` / `@analogjs/vite-plugin-angular`): the Analog packages are bumped automatically by Nx's `packageJsonUpdates` (to the `~2.2.x` line). Review `src/test-setup.ts` and Analog plugin invocations in per-project `vitest.config.ts`.
53
+
54
+ ## Pre-Migration Checklist
55
+
56
+ 1. **Identify all Vitest projects**:
57
+
58
+ ```bash
59
+ nx show projects --with-target test
60
+ ```
61
+
62
+ 2. **Locate all Vitest configuration files**:
63
+ - Search for `vitest.config.{ts,js,mjs}`
64
+ - Search for `vitest.workspace.{ts,js,mjs}` (removed in Vitest 4 — migrate to inline `test.projects`; see section 1.3 below)
65
+ - Check `project.json` files for `@nx/vitest:test` / `@nx/vite:test` executor options
66
+ - For workspaces relying on the inferred plugin (`@nx/vitest/plugin`), targets come from inference — inspect them with `nx show project <name> --json | jq .targets`
67
+
68
+ 3. **Identify affected code**:
69
+ - Test files: `**/*.{spec,test}.{ts,js,tsx,jsx}`
70
+ - Mock usage: Files using `vi.fn()`, `vi.spyOn()`, `vi.mock()`
71
+ - Coverage configuration references
72
+
73
+ ## Migration Steps by Category
74
+
75
+ ### 1. Configuration File Updates
76
+
77
+ #### 1.1 Coverage Configuration
78
+
79
+ **Search Pattern**: `coverage` in all `vitest.config.*` files and `project.json` test target options
80
+
81
+ **Changes Required**:
82
+
83
+ ```typescript
84
+ // ❌ BEFORE (Vitest 3.x)
85
+ export default defineConfig({
86
+ test: {
87
+ coverage: {
88
+ all: true,
89
+ extensions: ['.ts', '.tsx'],
90
+ ignoreEmptyLines: false,
91
+ experimentalAstAwareRemapping: true,
92
+ },
93
+ },
94
+ });
95
+
96
+ // ✅ AFTER (Vitest 4.0)
97
+ export default defineConfig({
98
+ test: {
99
+ coverage: {
100
+ // Explicitly define files to include in coverage
101
+ include: ['src/**/*.{ts,tsx}'],
102
+ // Remove: all, extensions, ignoreEmptyLines, experimentalAstAwareRemapping
103
+ },
104
+ },
105
+ });
106
+ ```
107
+
108
+ **Action Items**:
109
+
110
+ - [ ] Remove `coverage.all` option
111
+ - [ ] Remove `coverage.extensions` option
112
+ - [ ] Remove `coverage.ignoreEmptyLines` option
113
+ - [ ] Remove `coverage.experimentalAstAwareRemapping` option
114
+ - [ ] Add explicit `coverage.include` patterns based on project structure
115
+ - [ ] Update any documentation referencing these options
116
+
117
+ #### 1.2 Pool Options Restructuring
118
+
119
+ **Search Pattern**: `poolOptions`, `maxThreads`, `maxForks`, `singleThread`, `singleFork` in all Vitest config files
120
+
121
+ **Changes Required**:
122
+
123
+ ```typescript
124
+ // ❌ BEFORE (Vitest 3.x — pool serialized via singleThread/singleFork)
125
+ export default defineConfig({
126
+ test: {
127
+ maxThreads: 4,
128
+ maxForks: 2,
129
+ singleFork: true,
130
+ poolOptions: {
131
+ forks: {
132
+ execArgv: ['--expose-gc'],
133
+ isolate: false,
134
+ },
135
+ threads: {
136
+ useAtomics: true,
137
+ },
138
+ vmThreads: {
139
+ memoryLimit: '512MB',
140
+ },
141
+ },
142
+ },
143
+ });
144
+
145
+ // ✅ AFTER (Vitest 4.0 — top-level pool config)
146
+ export default defineConfig({
147
+ test: {
148
+ maxWorkers: 1, // singleFork: true => maxWorkers: 1, isolate: false
149
+ isolate: false,
150
+ execArgv: ['--expose-gc'], // moved from poolOptions.forks
151
+ vmMemoryLimit: '512MB', // moved from poolOptions.vmThreads.memoryLimit
152
+ // Remove: poolOptions, threads.useAtomics, minWorkers (also removed in v4)
153
+ },
154
+ });
155
+ ```
156
+
157
+ **Action Items**:
158
+
159
+ - [ ] Replace `maxThreads` and `maxForks` with a single `maxWorkers` option. If both values were set with different numbers (one for threads pool, one for forks pool), pick the value matching the pool the project actually uses — `maxWorkers` is pool-agnostic in v4.
160
+ - [ ] Replace `singleThread: true` or `singleFork: true` with `maxWorkers: 1, isolate: false`.
161
+ - [ ] If `singleThread: false` or `singleFork: false` was set explicitly, just delete — it's the default.
162
+ - [ ] Move `poolOptions.{forks,threads}.execArgv` → top-level `execArgv`.
163
+ - [ ] Move `poolOptions.{forks,threads}.isolate` → top-level `isolate`.
164
+ - [ ] Move `poolOptions.vmThreads.memoryLimit` → top-level `vmMemoryLimit`.
165
+ - [ ] Remove `poolOptions.threads.useAtomics` (option removed).
166
+ - [ ] Remove `minWorkers` if present (option removed; behaves as if set to 0 in non-watch mode).
167
+ - [ ] Update CI environment variables: `VITEST_MAX_THREADS` and `VITEST_MAX_FORKS` → `VITEST_MAX_WORKERS`.
168
+
169
+ <fail_if note="pool flattening is pool-agnostic in v4; picking the wrong value silently changes concurrency">
170
+ Both `maxThreads` and `maxForks` are set with different numbers AND the project has no explicit `test.pool` you can use to decide which value `maxWorkers` should take. Do not guess. Write status: failed and ask the user which pool the project uses.
171
+ </fail_if>
172
+
173
+ #### 1.3 Workspace to Projects Rename
174
+
175
+ **Search Pattern**: `workspace` property in Vitest config files
176
+
177
+ **Changes Required**:
178
+
179
+ ```typescript
180
+ // ❌ BEFORE (Vitest 3.x)
181
+ export default defineConfig({
182
+ test: {
183
+ workspace: ['apps/*', 'libs/*'],
184
+ },
185
+ });
186
+
187
+ // ✅ AFTER (Vitest 4.0)
188
+ export default defineConfig({
189
+ test: {
190
+ projects: ['apps/*', 'libs/*'],
191
+ },
192
+ });
193
+ ```
194
+
195
+ **Action Items**:
196
+
197
+ - [ ] Rename `workspace` property to `projects` in all config files.
198
+ - [ ] Inline any external `vitest.workspace.*` content into `test.projects` and delete the workspace file (external file references are no longer supported).
199
+ - [ ] If projects need different pool/environment options, set them inside each project entry rather than via the (now-removed) `poolMatchGlobs` / `environmentMatchGlobs` — see section 1.5.
200
+
201
+ <fail_if note="inlining is a semantic merge, not a copy">
202
+ The `vitest.workspace.*` file imports modules / calls functions / uses spreads that you cannot evaluate at edit time (dynamic project arrays, conditional imports). Write status: failed listing the file path and the dynamic shape that needs human resolution.
203
+ </fail_if>
204
+
205
+ #### 1.4 Browser Configuration
206
+
207
+ **Search Pattern**: `browser.provider`, `browser.testerScripts`, imports from `@vitest/browser`, `@vitest/browser/context`, `@vitest/browser/utils`
208
+
209
+ **Changes Required**: `browser.provider` is no longer a string. It's now the **return value of a provider function** imported from a per-provider package. Official packages:
210
+
211
+ - `@vitest/browser-playwright` — exports `playwright(options?)`
212
+ - `@vitest/browser-webdriverio` — exports `webdriverio(options?)`
213
+ - `@vitest/browser-preview` — exports `preview(options?)` (dev-only)
214
+
215
+ ```typescript
216
+ // ❌ BEFORE (Vitest 3.x)
217
+ export default defineConfig({
218
+ test: {
219
+ browser: {
220
+ enabled: true,
221
+ provider: 'playwright', // string
222
+ testerScripts: ['./setup.js'], // array of scripts
223
+ },
224
+ },
225
+ });
226
+ import { page } from '@vitest/browser/context';
227
+ import { getElementError } from '@vitest/browser/utils';
228
+
229
+ // ✅ AFTER (Vitest 4.0)
230
+ import { defineConfig } from 'vitest/config';
231
+ import { playwright } from '@vitest/browser-playwright';
232
+
233
+ export default defineConfig({
234
+ test: {
235
+ browser: {
236
+ enabled: true,
237
+ provider: playwright({
238
+ launchOptions: { slowMo: 100 },
239
+ }),
240
+ instances: [{ browser: 'chromium' }],
241
+ testerHtmlPath: './test-setup.html', // single HTML path replaces script array
242
+ },
243
+ },
244
+ });
245
+ import { page, utils } from 'vitest/browser';
246
+ const { getElementError } = utils;
247
+ ```
248
+
249
+ **Action Items**:
250
+
251
+ - [ ] Install the appropriate provider package: `@vitest/browser-playwright`, `@vitest/browser-webdriverio`, or `@vitest/browser-preview`. Match whatever your `browser.provider` string was previously.
252
+ - [ ] Remove `@vitest/browser` from `dependencies`/`devDependencies` — its public surface moved into the main `vitest` package and the per-provider packages.
253
+ - [ ] Replace string `browser.provider: 'name'` with the function-call form: `provider: <providerFn>(<options>)`.
254
+ - [ ] Replace `browser.testerScripts: [...]` with `browser.testerHtmlPath: '<single-file>.html'`. Note: this is a **semantic** change (array of scripts → one HTML file). Move the script contents into a `<script>` block of the HTML file, or load them with `<script src>` references inside it.
255
+ - [ ] Update imports: `@vitest/browser/context` → `vitest/browser`; `@vitest/browser/utils` → `vitest/browser` (named export `utils`).
256
+
257
+ <fail_if note="provider package selection changes which browser tests actually run against">
258
+ The previous `browser.provider` string is dynamic (variable reference, ternary, or constructed at runtime) so you cannot determine which per-provider package to install. Write status: failed and ask the user which provider the project targets.
259
+ </fail_if>
260
+
261
+ #### 1.5 Deprecated Configuration Options
262
+
263
+ **Search Pattern**: `deps.external`, `deps.inline`, `deps.fallbackCJS` in config files
264
+
265
+ **Changes Required**:
266
+
267
+ ```typescript
268
+ // ❌ BEFORE (Vitest 3.x)
269
+ export default defineConfig({
270
+ test: {
271
+ deps: {
272
+ external: ['some-package'],
273
+ inline: ['inline-package'],
274
+ fallbackCJS: true,
275
+ },
276
+ },
277
+ });
278
+
279
+ // ✅ AFTER (Vitest 4.0)
280
+ export default defineConfig({
281
+ test: {
282
+ server: {
283
+ deps: {
284
+ external: ['some-package'],
285
+ inline: ['inline-package'],
286
+ fallbackCJS: true,
287
+ },
288
+ },
289
+ },
290
+ });
291
+ ```
292
+
293
+ **Action Items**:
294
+
295
+ - [ ] Move `deps.*` options under `server.deps` namespace.
296
+ - [ ] Rename `deps.optimizer.web` → `deps.optimizer.client` (separate rename, same area).
297
+ - [ ] Remove `poolMatchGlobs` (use `projects` with conditions instead).
298
+ - [ ] Remove `environmentMatchGlobs` (use `projects` with conditions instead).
299
+
300
+ #### 1.6 Default Test File Exclusions Narrowed
301
+
302
+ **Search Pattern**: workspaces that have non-test files in `dist/`, `cypress/`, `.idea/`, `.cache/`, `.output/`, `.temp/`, or root config files (`rollup.config.*`, `prettier.config.*`, etc.) that look like test files.
303
+
304
+ **What Changed**: Vitest 4 excludes ONLY `node_modules` and `.git` by default. Previously, additional directories and root config files were excluded. Workspaces with `*.{spec,test}.*`-named files in those previously-excluded locations will see them picked up by the test runner in v4 unless an explicit exclusion is added.
305
+
306
+ ```typescript
307
+ // To restore Vitest 3.x default exclusion behavior:
308
+ import { configDefaults, defineConfig } from 'vitest/config';
309
+
310
+ export default defineConfig({
311
+ test: {
312
+ exclude: [
313
+ ...configDefaults.exclude,
314
+ '**/dist/**',
315
+ '**/cypress/**',
316
+ '**/.{idea,git,cache,output,temp}/**',
317
+ '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*',
318
+ ],
319
+ },
320
+ });
321
+ ```
322
+
323
+ **Recommendation**: prefer `test.dir` to scope discovery for performance, instead of relying on negative exclusions.
324
+
325
+ **Action Items**:
326
+
327
+ - [ ] After upgrading, run `nx test <project>` for each affected project. If new test files surface unexpectedly from `dist/`, `cypress/`, etc., apply the snippet above or add a narrower exclude pattern.
328
+ - [ ] Consider setting `test.dir: './src'` (or equivalent) per project to scope discovery to source.
329
+
330
+ #### 1.7 Custom Environments: `transformMode` → `viteEnvironment`
331
+
332
+ **Applies when**: A project exports a custom Vitest environment (rare; check for files with `default export` of `{ name, setup, transformMode, ... }` consumed via `test.environment`).
333
+
334
+ ```typescript
335
+ // ❌ BEFORE (Vitest 3.x)
336
+ export default {
337
+ name: 'my-env',
338
+ setup() {
339
+ /* ... */
340
+ },
341
+ transformMode: 'ssr',
342
+ };
343
+
344
+ // ✅ AFTER (Vitest 4.0)
345
+ export default {
346
+ name: 'my-env',
347
+ setup() {
348
+ /* ... */
349
+ },
350
+ viteEnvironment: 'custom-env',
351
+ };
352
+ ```
353
+
354
+ **Action Items**:
355
+
356
+ - [ ] If a custom environment file exists, rename its `transformMode` property to `viteEnvironment`.
357
+ - [ ] No change needed for workspaces using only built-in environments (`node`, `jsdom`, `happy-dom`, `edge-runtime`).
358
+
359
+ <fail_if note="viteEnvironment is not a value-for-value swap from transformMode">
360
+ The custom environment's `transformMode` value is something other than `'ssr'` or `'web'` (or its replacement Vite-environment name isn't obvious from the workspace). Write status: failed listing the file and the current `transformMode` value.
361
+ </fail_if>
362
+
363
+ ### 2. Test Code Updates
364
+
365
+ #### 2.1 Mock Function Name Changes
366
+
367
+ **Search Pattern**: `.getMockName()` calls in test files
368
+
369
+ **Changes Required**:
370
+
371
+ ```typescript
372
+ // ❌ BEFORE (Vitest 3.x)
373
+ const mockFn = vi.fn();
374
+ expect(mockFn.getMockName()).toBe('spy'); // Old default
375
+
376
+ // ✅ AFTER (Vitest 4.0)
377
+ const mockFn = vi.fn();
378
+ expect(mockFn.getMockName()).toBe('vi.fn()'); // New default
379
+
380
+ // If you need custom names, set them explicitly
381
+ const namedMock = vi.fn().mockName('myCustomName');
382
+ expect(namedMock.getMockName()).toBe('myCustomName');
383
+ ```
384
+
385
+ **Action Items**:
386
+
387
+ - [ ] Update test assertions checking default mock names from `'spy'` to `'vi.fn()'`
388
+ - [ ] Add explicit `.mockName()` calls where specific names are required
389
+
390
+ #### 2.2 Mock Invocation Call Order
391
+
392
+ **Search Pattern**: `.mock.invocationCallOrder` in test files
393
+
394
+ **Changes Required**:
395
+
396
+ ```typescript
397
+ // ❌ BEFORE (Vitest 3.x)
398
+ const mockFn = vi.fn();
399
+ mockFn();
400
+ expect(mockFn.mock.invocationCallOrder[0]).toBe(0); // Started at 0
401
+
402
+ // ✅ AFTER (Vitest 4.0)
403
+ const mockFn = vi.fn();
404
+ mockFn();
405
+ expect(mockFn.mock.invocationCallOrder[0]).toBe(1); // Now starts at 1 (Jest-compatible)
406
+ ```
407
+
408
+ **Action Items**:
409
+
410
+ - [ ] Update assertions on `invocationCallOrder` to account for 1-based indexing
411
+ - [ ] Search for off-by-one errors in call order comparisons
412
+
413
+ #### 2.3 Constructor Spies and Mocks
414
+
415
+ **Search Pattern**: `vi.spyOn` on constructors, `vi.fn()` used as constructors
416
+
417
+ **Changes Required**:
418
+
419
+ ```typescript
420
+ // ❌ BEFORE (Vitest 3.x) - Arrow function constructors might have worked
421
+ const MockConstructor = vi.fn(() => ({ value: 42 }));
422
+ new MockConstructor(); // May have worked in v3
423
+
424
+ // ✅ AFTER (Vitest 4.0) - Must use function or class
425
+ const MockConstructor = vi.fn(function () {
426
+ return { value: 42 };
427
+ });
428
+ new MockConstructor(); // Correctly supports 'new'
429
+
430
+ // Or use class syntax
431
+ class MockClass {
432
+ value = 42;
433
+ }
434
+ const MockConstructor = vi.fn(MockClass);
435
+ ```
436
+
437
+ **Action Items**:
438
+
439
+ - [ ] Convert arrow function mocks used as constructors to `function` keyword or `class` syntax
440
+ - [ ] Test all constructor spies to ensure `new` keyword works correctly
441
+ - [ ] Update any mocks that expect constructor behavior
442
+
443
+ #### 2.4 RestoreAllMocks Behavior
444
+
445
+ **Search Pattern**: `vi.restoreAllMocks()` in test files
446
+
447
+ **Changes Required**:
448
+
449
+ ```typescript
450
+ // ❌ BEFORE (Vitest 3.x)
451
+ vi.mock('./module', () => ({ fn: vi.fn() }));
452
+ vi.restoreAllMocks(); // Would restore automocks
453
+
454
+ // ✅ AFTER (Vitest 4.0)
455
+ vi.mock('./module', () => ({ fn: vi.fn() }));
456
+ vi.restoreAllMocks(); // Only restores manual spies, NOT automocks
457
+
458
+ // To reset automocks, use:
459
+ vi.unmock('./module');
460
+ // or
461
+ vi.resetModules();
462
+ ```
463
+
464
+ **Action Items**:
465
+
466
+ - [ ] Review all `vi.restoreAllMocks()` usage
467
+ - [ ] Add explicit `vi.unmock()` or `vi.resetModules()` calls for automocked modules
468
+ - [ ] Ensure test isolation is maintained after this change
469
+
470
+ #### 2.5 SpyOn Return Value Changes
471
+
472
+ **Search Pattern**: `vi.spyOn()` on already mocked functions
473
+
474
+ **Changes Required**:
475
+
476
+ ```typescript
477
+ // ❌ BEFORE (Vitest 3.x)
478
+ const mock = vi.fn();
479
+ const spy = vi.spyOn({ method: mock }, 'method');
480
+ // spy !== mock (created new spy)
481
+
482
+ // ✅ AFTER (Vitest 4.0)
483
+ const mock = vi.fn();
484
+ const spy = vi.spyOn({ method: mock }, 'method');
485
+ // spy === mock (returns same instance)
486
+ ```
487
+
488
+ **Action Items**:
489
+
490
+ - [ ] Review code that creates spies on existing mocks
491
+ - [ ] Remove redundant spy creation if same instance is returned
492
+ - [ ] Update assertions that check spy identity
493
+
494
+ #### 2.6 Automock Behavior Changes
495
+
496
+ **Search Pattern**: `vi.mock()` factories with getters, instance methods on automocked classes, `vi.restoreAllMocks()` usage when modules are automocked.
497
+
498
+ **Changes Required**:
499
+
500
+ ```typescript
501
+ // ❌ BEFORE (Vitest 3.x)
502
+ vi.mock('./utils', () => ({
503
+ get value() {
504
+ return 42;
505
+ }, // getter executed
506
+ }));
507
+
508
+ import { value } from './utils';
509
+ console.log(value); // executes getter, prints 42
510
+
511
+ // ✅ AFTER (Vitest 4.0)
512
+ vi.mock('./utils', () => ({
513
+ get value() {
514
+ return 42;
515
+ },
516
+ }));
517
+
518
+ import { value } from './utils';
519
+ console.log(value); // getter NOT executed; prints undefined
520
+
521
+ // To spy on the getter explicitly:
522
+ vi.spyOn(utilsModule, 'value', 'get').mockReturnValue(42);
523
+
524
+ // Or define as a plain property (not a getter):
525
+ vi.mock('./utils', () => ({ value: 42 }));
526
+ ```
527
+
528
+ Additional behavior changes for automocked instance methods:
529
+
530
+ ```typescript
531
+ // Each instance gets its own mock for the same method name
532
+ const a = new AutoMockedClass();
533
+ const b = new AutoMockedClass();
534
+ a.method.mockReturnValue(42);
535
+ expect(a.method()).toBe(42);
536
+ expect(b.method()).toBeUndefined(); // independent per instance
537
+
538
+ // But the prototype is shared: a method mocked on the prototype affects all
539
+ // instances that don't have a per-instance mock.
540
+ AutoMockedClass.prototype.method.mockReturnValue(100);
541
+ b.method.mockReset(); // remove per-instance mock if any
542
+ expect(b.method()).toBe(100);
543
+ ```
544
+
545
+ `vi.restoreAllMocks()` no longer touches automocks. Use `vi.unmock(modulePath)` or `vi.resetModules()` to clear an automocked module. **`.mockRestore()` on a single spy still works** (resets its implementation and clears state) regardless of whether the underlying module is automocked.
546
+
547
+ **Action Items**:
548
+
549
+ - [ ] Replace automocked **getters** that need to return values with plain property definitions, or add an explicit `vi.spyOn(obj, name, 'get').mockReturnValue(...)`.
550
+ - [ ] If a test relied on `vi.restoreAllMocks()` clearing an automocked module, add explicit `vi.unmock('./module')` or `vi.resetModules()` calls.
551
+ - [ ] Audit tests that mock instance methods of an automocked class; the per-instance independence may surface latent bugs.
552
+ - [ ] Do NOT remove `.mockRestore()` calls on single spies — they still work correctly.
553
+
554
+ #### 2.7 Settled Results Immediate Population
555
+
556
+ **Search Pattern**: `.mock.settledResults` in test files
557
+
558
+ **Changes Required**:
559
+
560
+ ```typescript
561
+ // ✅ AFTER (Vitest 4.0)
562
+ const asyncMock = vi.fn(async () => 'result');
563
+ const promise = asyncMock();
564
+
565
+ // settledResults is immediately populated with 'incomplete' status
566
+ expect(asyncMock.mock.settledResults[0]).toEqual({
567
+ type: 'incomplete',
568
+ value: undefined,
569
+ });
570
+
571
+ // After promise resolves
572
+ await promise;
573
+ expect(asyncMock.mock.settledResults[0]).toEqual({
574
+ type: 'fulfilled',
575
+ value: 'result',
576
+ });
577
+ ```
578
+
579
+ **Action Items**:
580
+
581
+ - [ ] Update tests that check `settledResults` before promise resolution
582
+ - [ ] Handle `'incomplete'` status in assertions
583
+ - [ ] Ensure tests properly await promises before checking settled results
584
+
585
+ ### 3. Reporter and CLI Changes
586
+
587
+ #### 3.1 Reporter API Changes
588
+
589
+ **Search Pattern**: custom reporter classes/objects implementing any of these removed callbacks:
590
+
591
+ - `onCollected`
592
+ - `onSpecsCollected`
593
+ - `onPathsCollected`
594
+ - `onTaskUpdate`
595
+ - `onFinished`
596
+
597
+ **Changes Required**:
598
+
599
+ ```typescript
600
+ // ❌ BEFORE (Vitest 3.x)
601
+ export default {
602
+ onCollected(files) {
603
+ /* ... */
604
+ },
605
+ onTaskUpdate(task) {
606
+ /* ... */
607
+ },
608
+ onFinished(files) {
609
+ /* ... */
610
+ },
611
+ };
612
+
613
+ // ✅ AFTER (Vitest 4.0)
614
+ // Use the new test-module / test-case event API. See:
615
+ // https://vitest.dev/api/advanced/reporters
616
+ // Typical replacements:
617
+ // onCollected → onTestModuleCollected
618
+ // onTaskUpdate → onTestCaseResult / onTestModuleEnd
619
+ // onFinished → onTestRunEnd
620
+ ```
621
+
622
+ **Action Items**:
623
+
624
+ - [ ] Audit every custom reporter file for any of the five removed callbacks listed above. Each one needs replacement.
625
+ - [ ] Consult the v4 reporters API at https://vitest.dev/api/advanced/reporters for the exact replacement method name and signature.
626
+ - [ ] After rewriting, run the reporter in isolation against a small project to verify event delivery before applying workspace-wide.
627
+
628
+ <fail_if note="custom reporters carry external behavior the migration cannot deduce">
629
+ A reporter callback receives or emits data whose v4 equivalent you can't determine from the v4 reporters API alone (e.g., uses internal task tree shapes, depends on event ordering that has no v4 analogue). Write status: failed naming the reporter file and the callback; do not stub.
630
+ </fail_if>
631
+
632
+ #### 3.2 Built-in Reporter Changes
633
+
634
+ **Search Pattern**: `reporters: ['basic']`, `reporters: ['verbose']`
635
+
636
+ **Changes Required**:
637
+
638
+ ```typescript
639
+ // ❌ BEFORE (Vitest 3.x)
640
+ export default defineConfig({
641
+ test: {
642
+ reporters: ['basic'],
643
+ },
644
+ });
645
+
646
+ // ✅ AFTER (Vitest 4.0)
647
+ export default defineConfig({
648
+ test: {
649
+ reporters: [['default', { summary: false }]], // Equivalent to 'basic'
650
+ },
651
+ });
652
+
653
+ // For verbose (tree output)
654
+ reporters: ['tree']; // Use 'tree' for hierarchical output
655
+ ```
656
+
657
+ **Action Items**:
658
+
659
+ - [ ] Replace `'basic'` reporter with `['default', { summary: false }]`
660
+ - [ ] Replace `'verbose'` reporter with `'tree'` for hierarchical output
661
+ - [ ] Update CI configuration if reporters are specified there
662
+
663
+ ### 4. Snapshot Changes
664
+
665
+ #### 4.1 Custom Elements Shadow Root
666
+
667
+ **Search Pattern**: Snapshot tests involving custom elements or Web Components
668
+
669
+ **Changes Required**:
670
+
671
+ ```typescript
672
+ // ✅ AFTER (Vitest 4.0)
673
+ // Shadow root contents now printed by default in snapshots
674
+
675
+ // If you want old behavior (don't print shadow root):
676
+ export default defineConfig({
677
+ test: {
678
+ snapshotFormat: {
679
+ printShadowRoot: false,
680
+ },
681
+ },
682
+ });
683
+ ```
684
+
685
+ **Action Items**:
686
+
687
+ - [ ] Review snapshot tests for custom elements
688
+ - [ ] Update snapshots if shadow root contents are now included
689
+ - [ ] Add `printShadowRoot: false` to `snapshotFormat` if old behavior is required
690
+
691
+ #### 4.2 `vi.fn` Snapshot String Changed
692
+
693
+ **Search Pattern**: `__snapshots__/**/*.snap` files containing `[MockFunction spy]`
694
+
695
+ **What Changed**: The default rendered string for an unnamed mock function in snapshots changed from `[MockFunction spy]` to `[MockFunction]`. Snapshots of mock objects taken in v3 will mismatch on re-run.
696
+
697
+ **Action Items**:
698
+
699
+ - [ ] Run tests with `--update` for projects that snapshot mock objects, then review the diff to confirm only the mock-name string changed.
700
+ - [ ] Where a specific mock name is asserted in snapshots, add explicit `.mockName('...')` calls.
701
+
702
+ #### 4.3 Custom Snapshot Matchers: Import Path Change
703
+
704
+ **Applies when**: A workspace defines custom snapshot matchers using `jest-snapshot`.
705
+
706
+ **What Changed**: Vitest 4 expects custom snapshot matchers to import from `vitest` (the new `Snapshots` namespace), not `jest-snapshot`.
707
+
708
+ ```typescript
709
+ // ❌ BEFORE (Vitest 3.x)
710
+ const { toMatchSnapshot } = require('jest-snapshot');
711
+
712
+ // ✅ AFTER (Vitest 4.0)
713
+ import { Snapshots } from 'vitest';
714
+ const { toMatchSnapshot } = Snapshots;
715
+
716
+ expect.extend({
717
+ toMatchTrimmedSnapshot(received: string, length: number) {
718
+ return toMatchSnapshot.call(this, received.slice(0, length));
719
+ },
720
+ });
721
+ ```
722
+
723
+ **Action Items**:
724
+
725
+ - [ ] Replace `require('jest-snapshot')` / `import 'jest-snapshot'` in custom matcher files with `import { Snapshots } from 'vitest'`.
726
+ - [ ] Remove `jest-snapshot` from `dependencies`/`devDependencies` if it's no longer used elsewhere.
727
+
728
+ ### 5. Environment Variable Updates
729
+
730
+ **Search Pattern**: CI/CD configuration files, `.env` files, documentation
731
+
732
+ **Changes Required**:
733
+
734
+ ```bash
735
+ # ❌ BEFORE (Vitest 3.x)
736
+ VITEST_MAX_THREADS=4
737
+ VITEST_MAX_FORKS=2
738
+ VITE_NODE_DEPS_MODULE_DIRECTORIES=/custom/path
739
+
740
+ # ✅ AFTER (Vitest 4.0)
741
+ VITEST_MAX_WORKERS=4
742
+ VITEST_MODULE_DIRECTORIES=/custom/path
743
+ ```
744
+
745
+ **Action Items**:
746
+
747
+ - [ ] Update CI/CD pipeline environment variables
748
+ - [ ] Update `.env` files
749
+ - [ ] Update documentation referencing old environment variables
750
+ - [ ] Search for `VITEST_MAX_THREADS`, `VITEST_MAX_FORKS`, `VITE_NODE_DEPS_MODULE_DIRECTORIES`
751
+
752
+ ### 6. Advanced: Module Runner Changes
753
+
754
+ **Search Pattern**: `vitest/execute`, `__vitest_executor`, `vite-node`
755
+
756
+ **Changes Required**:
757
+
758
+ ```typescript
759
+ // ❌ BEFORE (Vitest 3.x)
760
+ import { execute } from 'vitest/execute';
761
+ // Access to __vitest_executor
762
+
763
+ // ✅ AFTER (Vitest 4.0)
764
+ // Use Vite's Module Runner API instead
765
+ // Consult Vite Module Runner documentation
766
+ ```
767
+
768
+ **Action Items**:
769
+
770
+ - [ ] If using `vitest/execute`, migrate to Vite Module Runner
771
+ - [ ] Remove dependencies on `__vitest_executor`
772
+ - [ ] Update custom pool implementations (complete rewrite needed)
773
+
774
+ ### 7. Type Definition Updates
775
+
776
+ **Search Pattern**: TypeScript imports from `vitest`, type errors after upgrade
777
+
778
+ **Changes Required**:
779
+
780
+ ```typescript
781
+ // All deprecated type exports removed
782
+ // If you get TypeScript errors about missing types:
783
+ // - Check if you're using deprecated type names
784
+ // - Update to current type names from Vitest 4 API
785
+ // - Remove explicit @types/node if it was only needed due to Vitest bug
786
+ ```
787
+
788
+ **Action Items**:
789
+
790
+ - [ ] Run TypeScript compilation on all test files
791
+ - [ ] Fix any type errors related to removed Vitest type definitions
792
+ - [ ] Review `@types/node` usage (may no longer be accidentally included)
793
+
794
+ ## Post-Migration Validation
795
+
796
+ 1. Run tests per project: `nx test <project>`
797
+ 2. Run all affected: `nx affected -t test`
798
+ 3. Check coverage: `nx affected -t test --coverage`
799
+ 4. Validate CI pipeline: `nx prepush`
800
+
801
+ Confirm:
802
+
803
+ - All configuration files updated
804
+ - All test files pass (or are flagged in your handoff `summary` if they remain failing — see `<test_integrity_guardrails>` below)
805
+ - Coverage reports generate correctly
806
+ - Environment variables updated
807
+ - No deprecated API warnings in console
808
+
809
+ <test_integrity_guardrails note="violating any of these masks regressions and defeats the migration's purpose">
810
+
811
+ - Do NOT force tests to pass by replacing test logic with `expect(true).toBe(true)`.
812
+ - Do NOT remove assertions to silence a failure.
813
+ - Do NOT add mocks that exist solely to make a failing test pass.
814
+
815
+ If a test cannot be made to pass within the scope of this migration, leave it failing and report it in your handoff `summary`.
816
+
817
+ </test_integrity_guardrails>