@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,604 @@
1
+ # Vitest 3.0 Migration Instructions for LLM
2
+
3
+ ## Overview
4
+
5
+ These instructions guide you through migrating an Nx workspace containing multiple Vitest projects to Vitest 3.0. The workspace may currently be on Vitest 1.x or Vitest 2.x. This guide covers breaking changes for both upgrade paths:
6
+
7
+ - **From Vitest 1.x**: apply BOTH the "Vitest 1.x → 2.0" and "Vitest 2.x → 3.0" sections below.
8
+ - **From Vitest 2.x**: apply only the "Vitest 2.x → 3.0" section.
9
+
10
+ Work systematically through each breaking change category.
11
+
12
+ <pre_pass_summary note="a deterministic pre-pass already applied these specific edits; verify the new shape is in place rather than redoing them">
13
+
14
+ The pre-pass handled, mechanically:
15
+
16
+ - `--segfault-retry` removal from `package.json` scripts AND `project.json` `options.{args,command,commands}`
17
+ - `@vitest/coverage-c8` → `@vitest/coverage-v8` package rename (preserving the user's pin)
18
+ - `vitest typecheck` → `vitest --typecheck` in `package.json` scripts AND `project.json` `options.{args,command,commands}`
19
+ - `SnapshotEnvironment` import path `'vitest'` → `'vitest/snapshot'` (only when it is the sole named binding)
20
+ - `browser.provider: 'none'` → `'preview'` (only when the value is a direct string literal under `test.browser.provider`)
21
+ - `browser.indexScripts` → `orchestratorScripts` (only as a direct property name)
22
+
23
+ The pre-pass **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 matches it finds there are forwarded to you in `<advisory_context>`.
24
+
25
+ **The vast majority of action items below are NOT covered by the pre-pass** and still require your attention — every section other than the six items above.
26
+
27
+ How to read the wrapper sections above this file:
28
+
29
+ - `<files_changed>` lists files the pre-pass already wrote to. Verify the new shape is in place; do not re-apply the same edit.
30
+ - `<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.
31
+
32
+ </pre_pass_summary>
33
+
34
+ <handoff_guidance>
35
+ 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 browser-mode configs in this workspace").
36
+ </handoff_guidance>
37
+
38
+ ## Pre-Migration Checklist
39
+
40
+ 1. **Identify the current Vitest version**:
41
+
42
+ ```bash
43
+ npx vitest --version
44
+ ```
45
+
46
+ 2. **Identify all Vitest projects**:
47
+
48
+ ```bash
49
+ nx show projects --with-target test
50
+ ```
51
+
52
+ 3. **Locate all Vitest configuration files**:
53
+ - Search for `vitest.config.{ts,js,mjs}`
54
+ - Search for `vitest.workspace.{ts,js,mjs}` (deprecated in Vitest 3.2 — see "v3.2 Workspace File Deprecation" below)
55
+ - Check `project.json` files for `@nx/vitest:test` / `@nx/vite:test` executor options
56
+ - For workspaces relying on the inferred plugin (`@nx/vitest/plugin`), targets come from inference — inspect them with `nx show project <name> --json | jq .targets`
57
+
58
+ 4. **Identify affected code**:
59
+ - Test files: `**/*.{spec,test}.{ts,js,tsx,jsx}`
60
+ - Mock usage: files using `vi.fn()`, `vi.spyOn()`, `vi.mock()`, `vi.useFakeTimers()`
61
+ - Coverage configuration references
62
+ - Browser mode configurations
63
+ - Custom reporters, sequencers, and snapshot-related imports
64
+
65
+ ---
66
+
67
+ ## Nx-Specific Notes (read first)
68
+
69
+ - **Inferred plugin targets**: modern Nx workspaces use `@nx/vitest/plugin` (or `@nx/vite/plugin` historically) to _infer_ the test target from the presence of a `vitest.config.*` file. `project.json` may have no `test` target at all. Renaming or moving `vitest.config.*` invalidates inference. After config edits, run `nx reset && nx show project <name>` on a sample project to confirm the target is still present.
70
+ - **Shared base config pattern**: many Nx workspaces extend a workspace-root `vitest.config.base.ts` via `mergeConfig`. Apply transforms to the BASE config first, then per-project overrides. Otherwise a migrated base may conflict with un-migrated children.
71
+ - **Angular projects using AnalogJS** (`@analogjs/vitest-angular`, `@analogjs/vite-plugin-angular`): the Analog packages are bumped automatically by Nx's `packageJsonUpdates`. Review the Analog-specific setup file (typically `src/test-setup.ts`) and any plugin invocations in the per-project `vitest.config.ts` for changes between Analog `~1.x` and `~2.x` lines.
72
+ - **CI configuration**: when `@nx/vitest/plugin` is configured with `ciTargetName`, per-test-file targets are inferred — your `.github/workflows/*.yml` doesn't need direct reporter changes. CI-side reporter config matters only if you bypass the plugin.
73
+
74
+ ---
75
+
76
+ ## Vitest 1.x → 2.0 Breaking Changes
77
+
78
+ Skip this section if your workspace is already on Vitest 2.x.
79
+
80
+ ### 1.1 Default Pool Changed from `threads` to `forks`
81
+
82
+ **Search Pattern**: `poolOptions` in all `vitest.config.*` files
83
+
84
+ **What Changed**: The default `pool` switched from `threads` to `forks` for improved stability. Existing `poolOptions.threads` configurations now apply to the non-default pool unless `pool` is explicitly set.
85
+
86
+ ```typescript
87
+ // ❌ BEFORE (Vitest 1.x — relying on implicit `threads` default)
88
+ export default defineConfig({
89
+ test: {
90
+ poolOptions: {
91
+ threads: { singleThread: true },
92
+ },
93
+ },
94
+ });
95
+
96
+ // ✅ AFTER (Vitest 2.0 — either explicitly set the pool or move options to `forks`)
97
+ export default defineConfig({
98
+ test: {
99
+ poolOptions: {
100
+ forks: { singleFork: true },
101
+ },
102
+ },
103
+ });
104
+ ```
105
+
106
+ **Action Items**:
107
+
108
+ - [ ] If a project relied on the implicit pool, decide whether to keep `threads` (set `pool: 'threads'` explicitly) or move options to `forks`.
109
+ - [ ] Migrate `poolOptions.threads.singleThread` → `poolOptions.forks.singleFork` if switching.
110
+ - [ ] Migrate `poolOptions.threads.maxThreads/minThreads` → `poolOptions.forks.maxForks/minForks` if switching.
111
+
112
+ <fail_if note="this decision changes runtime semantics; if you can't determine the project's intent from the workspace, write status: failed and explain in summary">
113
+ You cannot determine whether the workspace intended the v1.x implicit-`threads` behavior (e.g., the codebase uses worker-only APIs like `SharedArrayBuffer`) or expected the v2.x `forks` default. Do not guess.
114
+ </fail_if>
115
+
116
+ ### 1.2 Hook Execution Order: Now Serial (and Reverse for `after*`)
117
+
118
+ **Search Pattern**: `beforeAll`, `beforeEach`, `afterAll`, `afterEach` usages relying on parallel execution
119
+
120
+ **What Changed**: Hooks moved from parallel to serial execution. `afterAll`/`afterEach` now run in reverse declaration order. Tests relying on parallel hook side effects or specific teardown ordering may break.
121
+
122
+ ```typescript
123
+ // To revert to parallel behavior:
124
+ export default defineConfig({
125
+ test: {
126
+ sequence: {
127
+ hooks: 'parallel',
128
+ },
129
+ },
130
+ });
131
+ ```
132
+
133
+ **Action Items**:
134
+
135
+ - [ ] Audit hooks that mutate shared state — they now execute sequentially in declaration order.
136
+ - [ ] Audit `afterAll`/`afterEach` hooks that depend on registration order; their order is now reversed.
137
+ - [ ] Add `sequence.hooks: 'parallel'` only if you cannot otherwise resolve hook-order dependencies.
138
+
139
+ ### 1.3 Concurrent Suites Now Run All Tests Concurrently
140
+
141
+ **Search Pattern**: `describe.concurrent`, `suite.concurrent`
142
+
143
+ **What Changed**: Declaring `concurrent` on a suite now runs all its tests concurrently (Jest-aligned) instead of grouping by suite. Bound by `maxConcurrency`.
144
+
145
+ **Action Items**:
146
+
147
+ - [ ] Review concurrent suites for shared mutable state that previously serialized through suite grouping.
148
+ - [ ] Adjust `maxConcurrency` if needed.
149
+
150
+ ### 1.4 V8 Coverage: `ignoreEmptyLines` On by Default
151
+
152
+ **Search Pattern**: `coverage` configuration in `vitest.config.*` (V8 provider)
153
+
154
+ **What Changed**: `coverage.ignoreEmptyLines` defaults to `true`. Coverage thresholds may shift.
155
+
156
+ ```typescript
157
+ // To restore the previous behavior:
158
+ export default defineConfig({
159
+ test: {
160
+ coverage: {
161
+ ignoreEmptyLines: false,
162
+ },
163
+ },
164
+ });
165
+ ```
166
+
167
+ **Action Items**:
168
+
169
+ - [ ] Re-baseline V8 coverage thresholds if they exist.
170
+ - [ ] Set `coverage.ignoreEmptyLines: false` only if you need the prior numbers exactly.
171
+
172
+ ### 1.5 `watchExclude` Option Removed
173
+
174
+ **Search Pattern**: `watchExclude` in `vitest.config.*`
175
+
176
+ ```typescript
177
+ // ❌ BEFORE (Vitest 1.x)
178
+ export default defineConfig({
179
+ test: {
180
+ watchExclude: ['node_modules', 'custom/path/**'],
181
+ },
182
+ });
183
+
184
+ // ✅ AFTER (Vitest 2.0)
185
+ export default defineConfig({
186
+ server: {
187
+ watch: {
188
+ ignored: ['**/node_modules/**', 'custom/path/**'],
189
+ },
190
+ },
191
+ });
192
+ ```
193
+
194
+ **Pattern Semantics Note**: `server.watch.ignored` uses **chokidar** patterns, while Vitest 1.x's `watchExclude` accepted simpler relative-path matchers. A literal find-and-replace may over- or under-ignore. Treat each entry as manual review:
195
+
196
+ - A bare directory name like `'node_modules'` typically needs `'**/node_modules/**'` to match nested occurrences.
197
+ - Glob patterns ending in `/**` are usually portable as-is.
198
+ - After the rewrite, verify watch mode picks up the right files with `nx test <project> --watch`.
199
+
200
+ **Action Items**:
201
+
202
+ - [ ] Move every `test.watchExclude` entry to `server.watch.ignored`, adjusting pattern syntax to chokidar conventions.
203
+ - [ ] Remove `watchExclude` from `vitest.config.*`.
204
+
205
+ <fail_if note="pattern semantics differ between the two options; a blind copy can over- or under-ignore files">
206
+ An existing `watchExclude` entry uses a pattern whose chokidar equivalent is ambiguous (e.g., a relative path without `**` wrapping, an entry that may need both `**/foo/**` and `foo/**`). Write status: failed with the specific patterns you're unsure about; the user should decide.
207
+ </fail_if>
208
+
209
+ ### 1.6 `--segfault-retry` CLI Flag Removed
210
+
211
+ **What Changed**: The CLI flag was removed; the underlying issue is fixed by switching to the `forks` pool (now the default).
212
+
213
+ **Action Items**:
214
+
215
+ - [ ] Remove `--segfault-retry` from scripts, `project.json` test target options, and CI configuration.
216
+
217
+ ### 1.7 Task API: `.suite` Now Optional, `.testPath` Required
218
+
219
+ **Search Pattern**: Custom reporters and tooling that traverse the task tree
220
+
221
+ **What Changed**: Top-level task `.suite` is now optional. Use `.file` (available on all tasks) instead. `expect.getState().testPath` is now always populated; `expect.getState().currentTestName` no longer includes the file name.
222
+
223
+ **Action Items**:
224
+
225
+ - [ ] In custom reporters / utilities, replace `.suite` chains with `.file` where the top-level task could be a file root.
226
+ - [ ] If you parsed file names out of `currentTestName`, switch to `testPath`.
227
+
228
+ ### 1.8 JSON Reporter Now Includes `task.meta`
229
+
230
+ **What Changed**: Output shape gained `task.meta` per assertion result.
231
+
232
+ **Action Items**:
233
+
234
+ - [ ] If you ingest the JSON reporter output, accept the new `task.meta` field (additive — most consumers will be unaffected).
235
+
236
+ ### 1.9 Mock Generic Types Simplified
237
+
238
+ **Search Pattern**: `vi.fn<...>(...)`, `Mock<...>`
239
+
240
+ ```typescript
241
+ // ❌ BEFORE (Vitest 1.x)
242
+ import { type Mock, vi } from 'vitest';
243
+
244
+ const add = (x: number, y: number): number => x + y;
245
+
246
+ const mockAdd = vi.fn<Parameters<typeof add>, ReturnType<typeof add>>();
247
+ const mockAdd2: Mock<Parameters<typeof add>, ReturnType<typeof add>> = vi.fn();
248
+
249
+ // ✅ AFTER (Vitest 2.0)
250
+ const mockAdd = vi.fn<typeof add>();
251
+ const mockAdd2: Mock<typeof add> = vi.fn();
252
+ ```
253
+
254
+ **Action Items**:
255
+
256
+ - [ ] Replace two-generic `vi.fn<Args, Return>()` with single-generic `vi.fn<typeof fn>()` everywhere.
257
+ - [ ] Replace two-generic `Mock<Args, Return>` with single-generic `Mock<typeof fn>` everywhere.
258
+
259
+ ### 1.10 `mock.results` No Longer Auto-Resolves Promises
260
+
261
+ **Search Pattern**: `.mock.results` accesses on async mocks
262
+
263
+ **What Changed**: For mocks returning Promises, `mock.results` now contains the Promise itself. Use the new `mock.settledResults` for resolved values.
264
+
265
+ ```typescript
266
+ // ❌ BEFORE (Vitest 1.x)
267
+ const fn = vi.fn().mockResolvedValueOnce('result');
268
+ await fn();
269
+ const result = fn.mock.results[0]; // 'result' (auto-resolved)
270
+
271
+ // ✅ AFTER (Vitest 2.0)
272
+ const result = fn.mock.results[0]; // a Promise
273
+ const settled = fn.mock.settledResults[0]; // 'result'
274
+ ```
275
+
276
+ **Action Items**:
277
+
278
+ - [ ] Replace `.mock.results[i]` reads with `.mock.settledResults[i]` for promise-returning mocks.
279
+ - [ ] Consider switching assertions to the new `toHaveResolved*` matchers where appropriate.
280
+
281
+ ### 1.11 Browser Mode Renames
282
+
283
+ **Search Pattern**: `browser.provider`, `browser.indexScripts` in `vitest.config.*`
284
+
285
+ **What Changed**: The `none` provider was renamed to `preview` and is now the default. `indexScripts` was renamed to `orchestratorScripts`.
286
+
287
+ **Action Items**:
288
+
289
+ - [ ] Rename `browser.provider: 'none'` → `browser.provider: 'preview'`.
290
+ - [ ] Rename `browser.indexScripts` → `browser.orchestratorScripts`.
291
+
292
+ ### 1.12 Deprecated APIs Fully Removed in 2.0
293
+
294
+ **Action Items**:
295
+
296
+ - [ ] Replace `vitest typecheck` command usages with `vitest --typecheck`.
297
+ - [ ] Remove `VITEST_JUNIT_CLASSNAME` and `VITEST_JUNIT_SUITE_NAME` env vars; move equivalent values into JUnit reporter options.
298
+ - [ ] If you import `SnapshotEnvironment`, change the import path from `vitest` to `vitest/snapshot`.
299
+ - [ ] Replace any `SpyInstance` type imports with `MockInstance`.
300
+ - [ ] If you still configure `c8` as a coverage provider, switch to `v8` (`@vitest/coverage-v8`).
301
+
302
+ ---
303
+
304
+ ## Vitest 2.x → 3.0 Breaking Changes
305
+
306
+ ### 2.1 Test Options Argument Position
307
+
308
+ **Search Pattern**: `test('name', () => {...}, { ... })`, `describe('name', () => {...}, { ... })`
309
+
310
+ **What Changed**: Test/describe options objects must now be passed as the **second** argument, not the third.
311
+
312
+ ```typescript
313
+ // ❌ BEFORE (Vitest 2.x)
314
+ test(
315
+ 'validation works',
316
+ () => {
317
+ /* ... */
318
+ },
319
+ { retry: 3 }
320
+ );
321
+
322
+ // ✅ AFTER (Vitest 3.0)
323
+ test('validation works', { retry: 3 }, () => {
324
+ /* ... */
325
+ });
326
+ ```
327
+
328
+ A numeric timeout value as the third argument is still accepted (`test('name', () => {}, 1000)`).
329
+
330
+ **Action Items**:
331
+
332
+ - [ ] Move every options-object argument from third to second position in `test`, `it`, `describe`, and their variants (`.skip`, `.only`, `.each`, etc.).
333
+
334
+ ### 2.2 Browser Configuration: `browser.instances`
335
+
336
+ **Search Pattern**: `browser.name`, `browser.providerOptions` in `vitest.config.*`
337
+
338
+ **What Changed**: `browser.name` and `browser.providerOptions` are **deprecated in v3** (still work, emit warnings) and **removed in v4**. Use `browser.instances` instead. Migrating now silences the v3 warnings and is required before reaching v4.
339
+
340
+ ```typescript
341
+ // ❌ BEFORE (Vitest 2.x)
342
+ export default defineConfig({
343
+ test: {
344
+ browser: {
345
+ name: 'chromium',
346
+ providerOptions: {
347
+ launch: { devtools: true },
348
+ },
349
+ },
350
+ },
351
+ });
352
+
353
+ // ✅ AFTER (Vitest 3.0)
354
+ export default defineConfig({
355
+ test: {
356
+ browser: {
357
+ instances: [
358
+ {
359
+ browser: 'chromium',
360
+ launch: { devtools: true },
361
+ },
362
+ ],
363
+ },
364
+ },
365
+ });
366
+ ```
367
+
368
+ **Action Items**:
369
+
370
+ - [ ] Collapse `browser.name` + `browser.providerOptions` into a single entry in `browser.instances`.
371
+ - [ ] Remove `browser.name` and `browser.providerOptions`.
372
+
373
+ ### 2.3 `mockReset()` Restores Original Implementation
374
+
375
+ **Search Pattern**: `.mockReset()`, `mockReset: true` in `vitest.config.*`
376
+
377
+ **What Changed**: `spy.mockReset()` now restores the original implementation rather than replacing it with a noop returning `undefined`.
378
+
379
+ ```typescript
380
+ // Behavior change illustration:
381
+ const foo = { bar: () => 'Hello, world!' };
382
+ vi.spyOn(foo, 'bar').mockImplementation(() => 'Hello, mock!');
383
+ foo.bar(); // 'Hello, mock!'
384
+
385
+ foo.bar.mockReset();
386
+ foo.bar(); // BEFORE: undefined → AFTER: 'Hello, world!'
387
+ ```
388
+
389
+ **Action Items**:
390
+
391
+ - [ ] Audit tests that rely on the post-reset behavior returning `undefined`; explicitly mock to a noop if needed (`vi.spyOn(foo, 'bar').mockReturnValue(undefined)`).
392
+ - [ ] If you set `mockReset: true` globally, expect spied methods to return their original implementation between tests.
393
+
394
+ ### 2.4 `vi.spyOn()` Reuses Existing Mocks
395
+
396
+ **Search Pattern**: Repeated `vi.spyOn(obj, 'method')` on the same target
397
+
398
+ **What Changed**: Calling `vi.spyOn()` on an already-mocked method now returns the existing mock rather than creating a new one. After `vi.restoreAllMocks()`, the method is no longer a mock.
399
+
400
+ ```typescript
401
+ vi.spyOn(fooService, 'foo').mockImplementation(() => 'bar');
402
+ vi.spyOn(fooService, 'foo').mockImplementation(() => 'bar');
403
+ vi.restoreAllMocks();
404
+ vi.isMockFunction(fooService.foo);
405
+ // BEFORE: true (the second spy survived restore)
406
+ // AFTER: false (both calls referenced the same spy)
407
+ ```
408
+
409
+ **Action Items**:
410
+
411
+ - [ ] Audit tests that double-spied on the same method expecting two independent mocks.
412
+ - [ ] Audit tests that asserted a method remained mocked after `restoreAllMocks` — they will now see the original.
413
+
414
+ ### 2.5 Fake Timers Mock Everything by Default
415
+
416
+ **Search Pattern**: `vi.useFakeTimers()` usages and `fakeTimers.toFake` config
417
+
418
+ **What Changed**: Vitest now mocks **all** timer-related APIs by default (the previously-restricted built-in subset is gone). This includes `performance.now()`. Only `nextTick` is left unmocked. To restore the prior, narrower subset, configure `fakeTimers.toFake` explicitly.
419
+
420
+ ```typescript
421
+ // To restore the prior subset:
422
+ export default defineConfig({
423
+ test: {
424
+ fakeTimers: {
425
+ toFake: [
426
+ 'setTimeout',
427
+ 'clearTimeout',
428
+ 'setInterval',
429
+ 'clearInterval',
430
+ 'setImmediate',
431
+ 'clearImmediate',
432
+ 'Date',
433
+ ],
434
+ },
435
+ },
436
+ });
437
+ ```
438
+
439
+ **Action Items**:
440
+
441
+ - [ ] Audit tests using `vi.useFakeTimers()` that observe `performance.now()` or other newly-faked APIs.
442
+ - [ ] Restrict `fakeTimers.toFake` if a test should leave specific timer APIs real.
443
+
444
+ ### 2.6 Stricter Error Equality
445
+
446
+ **Search Pattern**: `toEqual(new Error(...))`, `toThrowError(new Error(...))`
447
+
448
+ **What Changed**: Error comparisons via `toEqual()` and `toThrowError()` now check `name`, `message`, `cause`, `AggregateError.errors`, and prototype.
449
+
450
+ ```typescript
451
+ // Cause is checked asymmetrically:
452
+ expect(new Error('hi', { cause: 'x' })).toEqual(new Error('hi')); // ✅ passes
453
+ expect(new Error('hi')).toEqual(new Error('hi', { cause: 'x' })); // ❌ fails
454
+
455
+ // Prototype is checked:
456
+ expect(() => {
457
+ throw new TypeError('type error');
458
+ })
459
+ .toThrowError(new Error('type error')) // ❌ fails (Error vs TypeError)
460
+ .toThrowError(new TypeError('type error')); // ✅ passes
461
+ ```
462
+
463
+ **Action Items**:
464
+
465
+ - [ ] Update assertions that pass a base `Error` to match a subclassed throw — use the matching subclass.
466
+ - [ ] Update assertions whose expected error has a `cause`/`name` that the actual error lacks.
467
+
468
+ ### 2.7 Vite 6 + Vitest 3: `module` Condition Excluded from `resolve.conditions`
469
+
470
+ **Applies when**: Workspace is on Vite 6 (and Vitest 3 with Vite 6).
471
+
472
+ **What Changed**: `module` is excluded from `resolve.conditions` by default to align with the upstream Vite 6 migration.
473
+
474
+ **Action Items**:
475
+
476
+ - [ ] If a dependency requires the `module` condition for tests, add it explicitly to `resolve.conditions` in your Vitest config.
477
+
478
+ ### 2.8 Hook Signature: `onTestFinished` / `onTestFailed` Receive Context
479
+
480
+ **Search Pattern**: `onTestFinished`, `onTestFailed` usages in custom reporters or test utilities
481
+
482
+ **What Changed**: These hooks now receive a test context as the first argument, matching `beforeEach`/`afterEach`.
483
+
484
+ **Action Items**:
485
+
486
+ - [ ] Update `onTestFinished`/`onTestFailed` callbacks to take a context argument and access the previous "result" through it.
487
+
488
+ ### 2.9 `Custom` Type Deprecated; `WorkspaceSpec` Removed
489
+
490
+ **Search Pattern**: `import { Custom, WorkspaceSpec } from 'vitest'`
491
+
492
+ **What Changed**: `Custom` is now an alias for `Test`; prefer `RunnerCustomCase` and `RunnerTestCase`. `WorkspaceSpec` is removed — use `TestSpecification` instead. Tasks created via `getCurrentSuite().custom()` now have `type: 'test'`.
493
+
494
+ **Action Items**:
495
+
496
+ - [ ] Replace `import { Custom } from 'vitest'`: most usages should become `RunnerTestCase` (the regular test case type — `Custom` was an alias for `Test`). Only use `RunnerCustomCase` if the workspace explicitly creates tasks via `getCurrentSuite().custom()`.
497
+ - [ ] Replace `WorkspaceSpec` references with `TestSpecification`.
498
+
499
+ ### 2.10 `resolveConfig()` API Shape Changed
500
+
501
+ **Search Pattern**: `import { resolveConfig } from 'vitest/node'` (or similar) used by custom tooling
502
+
503
+ **What Changed**: `resolveConfig()` now takes user configuration and returns a resolved configuration, rather than taking an already-resolved Vite config.
504
+
505
+ **Action Items**:
506
+
507
+ - [ ] If you call `resolveConfig()`, pass user config (not already-resolved Vite config) and consume the resolved return value.
508
+
509
+ ### 2.11 `vitest/reporters` Export Slimmed Down
510
+
511
+ **Search Pattern**: Type imports from `vitest/reporters`
512
+
513
+ **What Changed**: `vitest/reporters` now exports only reporter implementations and their option types. Task types (`TestCase`, `TestSuite`, etc.) moved.
514
+
515
+ **Action Items**:
516
+
517
+ - [ ] Move task-type imports (`TestCase`, `TestSuite`, related) from `vitest/reporters` to `vitest/node`.
518
+
519
+ ### 2.12 Test Files Always Excluded from Coverage
520
+
521
+ **Search Pattern**: `coverage.excludes` in `vitest.config.*` attempting to include test files
522
+
523
+ **What Changed**: Test files are always excluded from coverage, even if `coverage.excludes` is customized.
524
+
525
+ **Action Items**:
526
+
527
+ - [ ] Remove any `coverage.excludes` patterns that were trying to surface test files in coverage — they no longer apply.
528
+
529
+ ### 2.13 Snapshot Internal API Restructured
530
+
531
+ **Applies when**: A workspace consumes `@vitest/snapshot` directly (custom snapshot tooling).
532
+
533
+ **What Changed**: The public Snapshot API in `@vitest/snapshot` was restructured to support multiple states within a single run. The `.toMatchSnapshot()` matcher API is unchanged.
534
+
535
+ **Action Items**:
536
+
537
+ - [ ] Most workspaces have nothing to do here. If you maintain custom snapshot tooling against `@vitest/snapshot`, review your usage against the v3 API.
538
+
539
+ ### 2.14 Workspace → Projects (v3.2 option rename, v4 file removal)
540
+
541
+ **Applies when**: Workspace has `vitest.workspace.{ts,js,mjs}` files, uses `defineWorkspace`, or has `test.workspace` set in `vitest.config.*`.
542
+
543
+ **What Changed**:
544
+
545
+ - **Vitest 3.2**: introduced `test.projects` as the config option, with `test.workspace` deprecated in favor of it (the option emits a deprecation warning).
546
+ - **Vitest 4**: the external file form (`vitest.workspace.*` + `defineWorkspace`) is **removed entirely**. Projects must be inlined in the root `vitest.config.*`.
547
+
548
+ Migrating now (while still on v3) clears the v3.2 deprecation warning and is required before reaching v4.
549
+
550
+ ```typescript
551
+ // ❌ DEPRECATED (Vitest 3.2+)
552
+ // vitest.workspace.ts
553
+ import { defineWorkspace } from 'vitest/config';
554
+ export default defineWorkspace(['apps/*', 'libs/*']);
555
+
556
+ // ✅ AFTER (inline in root vitest.config.ts)
557
+ import { defineConfig } from 'vitest/config';
558
+ export default defineConfig({
559
+ test: {
560
+ projects: ['apps/*', 'libs/*'],
561
+ },
562
+ });
563
+ ```
564
+
565
+ **Action Items**:
566
+
567
+ - [ ] Move project list from `vitest.workspace.*` into `test.projects` in the root `vitest.config.*`.
568
+ - [ ] Delete the `vitest.workspace.*` file once migration is complete.
569
+ - [ ] If you import `defineWorkspace` anywhere, replace with `defineConfig` and the inlined shape above.
570
+ - [ ] If you continue to Vitest 4, this step is required, not optional.
571
+
572
+ ---
573
+
574
+ ## Post-Migration Verification
575
+
576
+ 1. Reinstall: `pnpm install` (or `npm install` / `yarn install`)
577
+ 2. Run affected tests: `nx affected -t test`
578
+ 3. Re-baseline coverage on key projects: `nx run <project>:test --coverage`
579
+ 4. Typecheck custom reporters / sequencers / snapshot tooling: `nx affected -t typecheck`
580
+ 5. Watch mode (if applicable): verify `server.watch.ignored` covers what `watchExclude` previously did.
581
+
582
+ Confirm:
583
+
584
+ - All configuration files updated
585
+ - All test files pass (or are flagged in your handoff `summary` if they remain failing — see `<test_integrity_guardrails>` below)
586
+ - Coverage reports generate correctly
587
+ - No deprecated API warnings in console
588
+
589
+ <test_integrity_guardrails note="violating any of these masks regressions and defeats the migration's purpose">
590
+
591
+ - Do NOT force tests to pass by replacing test logic with `expect(true).toBe(true)`.
592
+ - Do NOT remove assertions to silence a failure.
593
+ - Do NOT add mocks that exist solely to make a failing test pass.
594
+
595
+ If a test cannot be made to pass within the scope of this migration, leave it failing and report it in your handoff `summary`.
596
+
597
+ </test_integrity_guardrails>
598
+
599
+ ## References
600
+
601
+ - Vitest 2.0 migration guide: https://v2.vitest.dev/guide/migration
602
+ - Vitest 3.0 migration guide: https://v3.vitest.dev/guide/migration
603
+ - Vitest 4.0 migration guide (for cascading bumps): https://vitest.dev/guide/migration
604
+ - Vitest releases: https://github.com/vitest-dev/vitest/releases