vitest-auto-spy 5.31.0 → 5.32.0

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 (86) hide show
  1. package/AGENTS.md +201 -81
  2. package/CHANGELOG.md +259 -1
  3. package/README.md +47 -13
  4. package/dist/angular-diagnostics.js +49 -42
  5. package/dist/angular-doubles.d.ts +10 -4
  6. package/dist/angular-doubles.js +17 -15
  7. package/dist/angular-http.js +64 -31
  8. package/dist/angular-matchers.js +30 -8
  9. package/dist/angular-router.d.ts +1 -1
  10. package/dist/angular-router.js +67 -35
  11. package/dist/angular.d.ts +6 -6
  12. package/dist/angular.js +640 -259
  13. package/dist/bun-angular.d.ts +5 -5
  14. package/dist/bun-angular.js +141 -68
  15. package/dist/bun.d.ts +7 -6
  16. package/dist/bun.js +12 -11
  17. package/dist/{chunk-VSWA6PQW.js → chunk-25OHJOAA.js} +72 -26
  18. package/dist/chunk-3Q76UTT6.js +65 -0
  19. package/dist/{chunk-ROSQKG22.js → chunk-4H6KZWGX.js} +28 -2
  20. package/dist/{chunk-33JL4DF3.js → chunk-4IRFL76M.js} +4 -4
  21. package/dist/{chunk-NFFBGS7E.js → chunk-ANQVIGAG.js} +2 -2
  22. package/dist/{chunk-7TGT2436.js → chunk-D2R6EQJJ.js} +32 -4
  23. package/dist/{chunk-3MRCVCRZ.js → chunk-E46PPK33.js} +3 -3
  24. package/dist/chunk-EDEGKIIR.js +52 -0
  25. package/dist/{chunk-TZ7DRSRD.js → chunk-ENCEEH23.js} +1 -1
  26. package/dist/{chunk-6RP37RWX.js → chunk-FRD6NXVY.js} +1 -1
  27. package/dist/chunk-G5KZLT7Q.js +41 -0
  28. package/dist/{chunk-64KOOW3H.js → chunk-K3EUF6RK.js} +109 -47
  29. package/dist/{chunk-TBT4J2AX.js → chunk-KCMPDMLY.js} +76 -41
  30. package/dist/{chunk-L6VPMLRJ.js → chunk-LWSUV6IX.js} +3 -2
  31. package/dist/chunk-M3NTXLZG.js +27 -0
  32. package/dist/{chunk-F4SLC724.js → chunk-NTCEH2FY.js} +219 -127
  33. package/dist/chunk-SDTXE5SW.js +13 -0
  34. package/dist/chunk-SQFEEO4W.js +63 -0
  35. package/dist/{chunk-5FM2TZKG.js → chunk-TH64BXPP.js} +27 -16
  36. package/dist/chunk-W5EXSI22.js +26 -0
  37. package/dist/{chunk-FZMWANZV.js → chunk-X74IM4MQ.js} +3 -3
  38. package/dist/cli.js +547 -284
  39. package/dist/console.d.ts +1 -1
  40. package/dist/console.js +9 -8
  41. package/dist/diagnostics.js +30 -3
  42. package/dist/dom-stubs.d.ts +95 -2
  43. package/dist/dom-stubs.js +214 -75
  44. package/dist/eslint-plugin.cjs +464 -220
  45. package/dist/{expect-emission-DRoCm2M1.d.ts → expect-emission-wUaoy0NW.d.ts} +9 -3
  46. package/dist/index.d.ts +4 -4
  47. package/dist/index.js +567 -240
  48. package/dist/jasmine-compat.d.ts +1 -1
  49. package/dist/jasmine-compat.js +3 -3
  50. package/dist/jasmine.d.ts +2 -2
  51. package/dist/jasmine.js +66 -45
  52. package/dist/nestjs.d.ts +2 -2
  53. package/dist/nestjs.js +13 -12
  54. package/dist/node.cjs +613 -272
  55. package/dist/node.d.ts +4 -4
  56. package/dist/node.js +544 -226
  57. package/dist/observer-spy.js +17 -14
  58. package/dist/{package-identity-CCuANjTk.d.ts → package-identity-5pFBRWW9.d.ts} +2 -2
  59. package/dist/{prop-mock-Vbd3FuyF.d.ts → prop-mock-DUcl1uMH.d.ts} +3 -1
  60. package/dist/react.d.ts +4 -4
  61. package/dist/react.js +567 -240
  62. package/dist/rstest.d.ts +4 -4
  63. package/dist/rstest.js +12 -11
  64. package/dist/rxjs.d.ts +2 -2
  65. package/dist/rxjs.js +16 -11
  66. package/dist/setup.d.ts +192 -162
  67. package/dist/setup.js +879 -273
  68. package/dist/shared-state.js +67 -50
  69. package/dist/signal-forms.js +24 -7
  70. package/dist/svelte.d.ts +4 -4
  71. package/dist/svelte.js +567 -240
  72. package/dist/{track-injections-bhctaGyo.d.ts → track-injections-DyqkWQmT.d.ts} +1 -1
  73. package/dist/{track-signal-runs-DDLi1--g.d.ts → track-signal-runs-pFMHNAPy.d.ts} +57 -4
  74. package/dist/{types-IEQUztPB.d.ts → types-BVKEkhWj.d.ts} +5 -0
  75. package/dist/vue.d.ts +5 -5
  76. package/dist/vue.js +568 -241
  77. package/dist/zone.d.ts +0 -30
  78. package/dist/zone.js +15 -3
  79. package/package.json +1 -1
  80. package/skills/vitest-auto-spy/SKILL.md +128 -121
  81. package/dist/chunk-2O3N4OPX.js +0 -52
  82. package/dist/chunk-2XXGISYD.js +0 -48
  83. package/dist/chunk-3VXZRDH3.js +0 -12
  84. package/dist/chunk-SOROJD3W.js +0 -9
  85. package/dist/chunk-V76LFJW4.js +0 -41
  86. package/dist/chunk-YZSBSAYZ.js +0 -31
package/AGENTS.md CHANGED
@@ -54,22 +54,22 @@ adapter installed and spies fail at runtime.
54
54
 
55
55
  Add-ons, orthogonal to the runner:
56
56
 
57
- | Add-on | Import | Needed for |
58
- | ------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
59
- | Observable spies | `import 'vitest-auto-spy/rxjs'` | `nextWith` & friends. **Side-effect import, once**, and in a file the `tsconfig` includes (§4) |
60
- | observer-spy shim | `vitest-auto-spy/observer-spy` | `subscribeSpyTo` — the `@hirez_io/observer-spy` surface (§20). Its own entry so `/rxjs` does not carry it |
61
- | Console spies | `vitest-auto-spy/console` | silent typed spies over the global `console` — `installConsoleSpies()` per test, `restoreConsole()` after |
62
- | DOM stubs | `vitest-auto-spy/dom-stubs` | `stubIntersectionObserver` / `stubResizeObserver` / `stubMutationObserver` / `stubObserver`, `stubMediaElement`, `stubAbortController`, `stubWebStorage` (§12), `intersectionEntry` / `resizeEntry` / `mutationRecord`. **Moved off the root in 4.0.0** |
63
- | Run diagnostics | `vitest-auto-spy/diagnostics` | `compareTestRuns`, `summarizeTestRun`, `formatTestRunComparison`, `diffByField`. **Moved off the root in 4.0.0** |
64
- | Angular HTTP | `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest` — `httpResource()` / `HttpClient` (§13). Optional `@angular/common` peer, this entry only |
65
- | Angular router | `vitest-auto-spy/angular-router` | `provideActivatedRoute`, `injectActivatedRoute` — an `ActivatedRoute` whose streams and snapshot share one record; `provideRouterDouble`, `injectRouterDouble` — a `Router` whose URL, `routerState` and `events` agree (§13). Optional `@angular/router` peer, this entry only |
66
- | Angular diagnostics | `vitest-auto-spy/angular/diagnostics` | `enableAngularDiagnostics` and the whole TestBed timing family (§13). Companion to `/angular` like `/angular-http` — no core re-export; **moved off `/angular` in 5.21.0** so importing spies stops evaluating it |
67
- | Angular doubles | `vitest-auto-spy/angular/doubles` | The Material dialog trio and the `Window`/`Document` platform doubles (§13). Companion to `/angular`; registers the Vitest adapter, so its doubles spy out of the box; **moved off `/angular` in 5.21.0** |
68
- | Angular matchers | `vitest-auto-spy/angular/matchers` | `registerDirectiveMatchers`, `registerResourceMatchers`, `registerSignalMatchers` (§13). Companion to `/angular` — no core re-export; **moved off `/angular` in 5.21.0** |
69
- | Signal forms | `vitest-auto-spy/signal-forms` | `createForm`, `registerFormMatchers` — a signal form built where `form()` can inject, and `toHaveFieldErrors` over what it produced (§13). Optional `@angular/forms` peer, this entry only; Angular 22+ |
70
- | Setup helpers | `vitest-auto-spy/setup` | `setupAutoSpy()`, `setupFakeTimers()`, `blockNetwork()`, `stubResponse()`; the entry imports Vitest, so it is not for `bun test` |
71
- | Zone patch | `import 'vitest-auto-spy/zone'` | `fakeAsync` / `waitForAsync` on Vitest (§14) |
72
- | jasmine compat | `vitest-auto-spy/jasmine` | `.and` / `.calls` / `.withArgs`, the `jasmine` namespace (§20) |
57
+ | Add-on | Import | Needed for |
58
+ | ------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
59
+ | Observable spies | `import 'vitest-auto-spy/rxjs'` | `nextWith` & friends. **Side-effect import, once**, and in a file the `tsconfig` includes (§4) |
60
+ | observer-spy shim | `vitest-auto-spy/observer-spy` | `subscribeSpyTo` — the `@hirez_io/observer-spy` surface (§20). Its own entry so `/rxjs` does not carry it |
61
+ | Console spies | `vitest-auto-spy/console` | silent typed spies over the global `console` — `installConsoleSpies()` per test, `restoreConsole()` after |
62
+ | DOM stubs | `vitest-auto-spy/dom-stubs` | `stubIntersectionObserver` / `stubResizeObserver` / `stubMutationObserver` / `stubObserver`, `stubMediaElement`, `stubAbortController`, `stubWebStorage` (§12), `stubAnimationFrame`, `stubElementRect`, `intersectionEntry` / `resizeEntry` / `mutationRecord`. **Moved off the root in 4.0.0** |
63
+ | Run diagnostics | `vitest-auto-spy/diagnostics` | `compareTestRuns`, `summarizeTestRun`, `formatTestRunComparison`, `diffByField`. **Moved off the root in 4.0.0** |
64
+ | Angular HTTP | `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest` — `httpResource()` / `HttpClient` (§13). Optional `@angular/common` peer, this entry only |
65
+ | Angular router | `vitest-auto-spy/angular-router` | `provideActivatedRoute`, `injectActivatedRoute` — an `ActivatedRoute` whose streams and snapshot share one record; `provideRouterDouble`, `injectRouterDouble` — a `Router` whose URL, `routerState` and `events` agree (§13). Optional `@angular/router` peer, this entry only |
66
+ | Angular diagnostics | `vitest-auto-spy/angular/diagnostics` | `enableAngularDiagnostics` and the whole TestBed timing family (§13). Companion to `/angular` like `/angular-http` — no core re-export; **moved off `/angular` in 5.21.0** so importing spies stops evaluating it |
67
+ | Angular doubles | `vitest-auto-spy/angular/doubles` | The Material dialog trio and the `Window`/`Document` platform doubles (§13). Companion to `/angular`; registers the Vitest adapter, so its doubles spy out of the box; **moved off `/angular` in 5.21.0** |
68
+ | Angular matchers | `vitest-auto-spy/angular/matchers` | `registerDirectiveMatchers`, `registerResourceMatchers`, `registerSignalMatchers` (§13). Companion to `/angular` — no core re-export; **moved off `/angular` in 5.21.0** |
69
+ | Signal forms | `vitest-auto-spy/signal-forms` | `createForm`, `registerFormMatchers` — a signal form built where `form()` can inject, and `toHaveFieldErrors` over what it produced (§13). Optional `@angular/forms` peer, this entry only; Angular 22+ |
70
+ | Setup helpers | `vitest-auto-spy/setup` | `setupAutoSpy()`, `setupFakeTimers()`, `blockNetwork()`, `stubResponse()`; the entry imports Vitest, so it is not for `bun test` |
71
+ | Zone patch | `import 'vitest-auto-spy/zone'` | `fakeAsync` / `waitForAsync` on Vitest (§14) |
72
+ | jasmine compat | `vitest-auto-spy/jasmine` | `.and` / `.calls` / `.withArgs`, the `jasmine` namespace (§20) |
73
73
 
74
74
  `vitest-auto-spy/jasmine` is Vitest-only, because it registers the Vitest adapter. On `bun test` and
75
75
  `node --test` call `enableJasmineCompat()` from `vitest-auto-spy/jasmine-compat` instead.
@@ -146,7 +146,10 @@ and is only ever asserted on — `createAutoMock`. Handed to the function under
146
146
  it has to satisfy `T` at the call site _and_ expose the spy helpers at the assertion, and
147
147
  `autoMocked<T>()` is that intersection — otherwise every call site needs an `asInstance()` and the
148
148
  noise scales with the number of them. Both take the same second argument (`returns`, `name`, `strict`,
149
- `observablePropsToSpyOn`).
149
+ `observablePropsToSpyOn`). A `let` assigned in `beforeEach` is declared `AutoMocked<T>`,
150
+ not with the intersection spelt out. `returns` also takes an optional method written as a property,
151
+ `getMinZoom?: (() => number) | undefined` — the way Leaflet and other third-party typings declare
152
+ them.
150
153
 
151
154
  `createMock<T>()` is the one to reach for on data shapes — it returns a plain `T`, so it satisfies a
152
155
  `no-type-assertion` lint rule without an `eslint-disable` on every fixture. `createMock<T>(undefined)`
@@ -654,7 +657,9 @@ order**, a `Date` by its time, a `RegExp` by its source and flags, an `Error` by
654
657
  `message` plus its own enumerable fields, a function by identity, and symbol-keyed properties
655
658
  participate like string ones. Cycles are handled, so a component graph with a back-edge is a legal
656
659
  argument. `mustBeCalledWith` uses the same map, and its `Wanted:` line renders a matcher as
657
- `Any<Number>` rather than as the object it serialises to.
660
+ `Any<Number>` rather than as the object it serialises to. With one config set up, its first line
661
+ names the first argument that differs — `argument 2: expected 'eu', got 'us'` — and it names the
662
+ class only on a strict double (`UserService.getName`), the method alone otherwise.
658
663
 
659
664
  Each `calledWith(...)` / `mustBeCalledWith(...)` call hands back **its own** handle, so a chain kept
660
665
  in a variable stays attached to the arguments it was written with:
@@ -733,8 +738,11 @@ one rather than disappearing.
733
738
 
734
739
  `nextWithValues` on an observable **property** builds a new stream, which a subscriber that already
735
740
  holds the old one never sees — the spec's values go nowhere and the assertion below reads the
736
- initial state. That case is reported now rather than passing silently; `nextWith` pushes into the
737
- subject the current subscriber is on and is the one to reach for mid-test.
741
+ initial state. That case is reported rather than passing silently —
742
+ `Feed.items$.nextWithValues() ran after something subscribed to Feed.items$` on a strict double, the
743
+ member name alone otherwise — once per property as a warning, and at every such call as a throw under
744
+ `setupAutoSpy({ misconfiguration: 'throw' })`. `nextWith` pushes into the subject the current
745
+ subscriber is on and is the one to reach for mid-test.
738
746
 
739
747
  What that does **not** change: `vi.clearAllMocks()` and `clearMocks: true` still cannot reach it,
740
748
  for the same reason they cannot reach a `calledWith` chain — that state lives in this library's
@@ -819,7 +827,8 @@ be told apart from an instance field and stays silent.
819
827
  Only `onlyMethodsToSpyOn` warns, because only a restricting list can be silently destructive — a
820
828
  misspelling there leaves the real method unspied, and the code under test then calls something that
821
829
  is not there:
822
- `[vitest-auto-spy] createSpyFromClass(X): onlyMethodsToSpyOn names method(s) that are not on the class prototype: …`.
830
+ `[vitest-auto-spy] createSpyFromClass(CartService): onlyMethodsToSpyOn names 'lod' (did you mean 'load'?), not a method of CartService.`
831
+ A name with nothing close gets `instanceMethodsToSpyOn: ['x']` as the fix instead.
823
832
 
824
833
  Also true, and worth not re-deriving:
825
834
 
@@ -959,8 +968,10 @@ users.currentTenant(); // throws here, not four frames later inside the componen
959
968
  ```
960
969
 
961
970
  ```
962
- [vitest-auto-spy] Nothing configured Cart.checkout, and strict mode is on.
963
- Called as: Cart.checkout(1,'now')
971
+ [vitest-auto-spy] Cart.checkout(1, 'now') was called; this strict double has nothing configured for it.
972
+ Called from src/app/cart.component.ts:41:12
973
+ Configure it in the test: cart.checkout.calledWith(1, 'now').mockReturnValue(…) for these arguments, or .mockReturnValue(…) for any — .resolveWith(…) / .nextWith(…) when it returns a Promise / Observable.
974
+ Docs: https://asdalexey.github.io/vitest-auto-spy/core/strict-mode#the-message
964
975
  ```
965
976
 
966
977
  Also on `createAutoMock`, `provideAutoSpy`, and suite-wide as `setupAutoSpy({ strict: true })` — which
@@ -992,7 +1003,9 @@ Precedence, first one set wins: the double's `onUnstubbedCall` → the double's
992
1003
  - **A throw something swallowed still fails the test.** Under `setupAutoSpy({ strict: true })` every
993
1004
  strict throw is recorded, and one that a `try`/`catch` in the code under test or an RxJS error with
994
1005
  no handler (its rethrow waits on a fake clock) kept from the test fails it after the test
995
- (`swallowedStrictCalls`, `'throw'` by default with `strict: true`). Provoking one on purpose:
1006
+ (`swallowedStrictCalls`, `'throw'` by default with `strict: true`). The report quotes each call
1007
+ with its arguments and the line that made it, and says what swallowed it — a `catch` in the code
1008
+ under test, or an RxJS subscriber with no error callback. Provoking one on purpose:
996
1009
  `expect(() => cart.total()).toThrow(…)` then `expect(takeStrictViolations()).toHaveLength(1)`.
997
1010
  - **It does not reach** accessor spies, observable-property spies, `mockDeep` nodes, `console-spy`,
998
1011
  `mockResourceProp`'s `reload` or a standalone `createFunctionSpy`. A strict double still answers
@@ -1012,7 +1025,7 @@ member, kind, count })` — suite-wide or per double, same precedence as `onUnst
1012
1025
  `ngAfter…` hooks answer `undefined` on a strict double: Angular calls `ngOnDestroy` itself on every
1013
1026
  provided value at teardown (a `createAutoMock` / `provideAutoSpyForToken` double always has one), and
1014
1027
  a throw there broke the teardown for every test after it. The calls are still recorded.
1015
- - A class instance or DOM node in the `Called as:` line prints as `[ClassName]`, data up to 200
1028
+ - A class instance or DOM node in the call the message quotes prints as `[ClassName]`, data up to 200
1016
1029
  characters per argument; `provideAutoSpyForToken` names the token (`createAutoMock` takes `name`).
1017
1030
  - Use `onUnstubbedCall` to survey a suite before turning the throw on — it is handed
1018
1031
  `{ className, method, args }` and whatever it returns is what the call answers, so a handler that
@@ -1151,6 +1164,12 @@ resetAutoSpy(service); // calls AND configuration (calledWith / resolveWith / mo
1151
1164
  Both cover method spies **and** accessor spies, on `createSpyFromClass` spies and `createAutoMock`
1152
1165
  proxies alike. Reach for these instead of looping over methods calling `mockClear` by hand.
1153
1166
 
1167
+ **`mockReset()` on one method keeps its `calledWith` chains** — they live in the library's state, where
1168
+ the runner's reset cannot reach — so `spy.isFeatureOn.mockReset(); spy.isFeatureOn.mockReturnValue(true)`
1169
+ still reports that `mockReturnValue()` replaced a configured chain (and throws under `strict`). To
1170
+ answer one value for every call after a `beforeEach` configured `calledWith(…)`, drop the chain with
1171
+ `resetAutoSpy(spy.isFeatureOn)` first; the report names it.
1172
+
1154
1173
  **`resetAutoSpy` is `vi.resetAllMocks()` for one double**, and two things it used to leave behind now
1155
1174
  go with the rest: a pending `mockReturnValueOnce` / `mockResolvedValueOnce` queue (it used to answer
1156
1175
  the first call _after_ the reset, in a later test) and an accessor spy's configuration (a
@@ -1210,6 +1229,15 @@ wait then runs to the runner's own test timeout, which is the trade. The source
1210
1229
  `subscribe` takes a bare callback) and hand-rolled subscribables all work — and every helper infers
1211
1230
  the emitted type, so `expectEmission(of(1))` is a `Promise<number>`.
1212
1231
 
1232
+ **A `void` stream calls its listener with one argument, `undefined`.** `output<void>().emit()` and
1233
+ `Subject<void>.next()` both do, so a `vi.fn()` subscribed to one records `[undefined]`, and
1234
+ `expect(listener).toHaveBeenCalledExactlyOnceWith()` fails on `[] vs [undefined]` — the form a
1235
+ `prefer-called-with` autofix used to write. The assertion is the emission itself:
1236
+ `await expect(expectEmission(component.closed)).resolves.toBeUndefined()`, or
1237
+ `subscribeSpyTo(subject$).getValuesLength()` to count. A listener kept anyway is typed
1238
+ `vi.fn<() => void>()` (`(value: void) => void` trips `no-invalid-void-type`) and asserted
1239
+ `toHaveBeenCalledExactlyOnceWith(undefined)`.
1240
+
1213
1241
  `expectCompletion` is the one to reach for on a stream whose value is not the point — a save, a
1214
1242
  purge, an `Observable<void>`, a `Subject` a teardown closes. `firstValueFrom` rejects such a stream
1215
1243
  with rxjs's `EmptyError`, and the workaround people arrive at,
@@ -1512,7 +1540,8 @@ setupAutoSpy({ strayTimers: true }); // wrap the schedulers, sweep the survivors
1512
1540
  The pieces are exported too — `trackStrayTimers()` (idempotent, returns the undo),
1513
1541
  `cancelStrayTimers()` (returns how many it cancelled), `countStrayTimers()` and
1514
1542
  `describeStrayTimers()` (each pending timer's `kind`, the `delay` of a timeout or interval, the spec
1515
- `file` that scheduled it and up to five `frames`), all from `vitest-auto-spy/setup`. Use
1543
+ `file` that scheduled it, up to five `frames`, and `test` — the `suite > test` that scheduled it — or
1544
+ `outsideTest: 'import' | 'hook'` when no test was running), all from `vitest-auto-spy/setup`. Use
1516
1545
  `expect(countStrayTimers()).toBe(0)` in an `afterEach` to make a leak fail rather than be tidied
1517
1546
  away, or take the per-file report from the sweep itself — `timers` carries the same origins, so the
1518
1547
  failure diff names the file and the scheduling call:
@@ -1524,10 +1553,11 @@ setupAutoSpy({ strayTimers: true, onStrayTimers: ({ timers }) => expect(timers).
1524
1553
  `frames` starts below the tracking wrapper and is up to forty frames deep, so a zone or an rxjs
1525
1554
  scheduler in between does not hide the caller: project frames come first; when the call came from
1526
1555
  dependencies only, their frames stand in. The package's own frames are never quoted, from source or
1527
- from `dist`. `onStrayTimers: 'throw'` fails the file with a message that lists every stray as
1528
- `timeout (300 ms) from <file> <frames[0]>`; a handler that prints its own message should print the
1529
- same `kind`, `delay`, `file` and `frames[0]` — they are what makes a report that only reproduces
1530
- under a slow coverage run actionable the first time.
1556
+ from `dist`. `onStrayTimers: 'throw'` fails the file with a message that names the file and lists
1557
+ every stray as `setTimeout 300 ms, scheduled in "<test>" at <frames[0]>`, then the one fix; a handler
1558
+ that prints its own message should print the same `kind`, `delay`, `file`, `test` and `frames[0]` —
1559
+ they are what makes a report that only reproduces under a slow coverage run actionable the first
1560
+ time.
1531
1561
 
1532
1562
  A stray whose `file` is not the file that failed was scheduled after the previous file's sweep —
1533
1563
  the previous file is the one to fix.
@@ -1546,8 +1576,9 @@ arrive at the same timer from opposite ends and the quiet one wins: the sweep ca
1546
1576
  Vitest collects its leaks afterwards, and a cancelled timeout is no longer referenced, so the run
1547
1577
  reports **no leaks** for a file that leaks. Cancelling is still the right default — a callback firing
1548
1578
  during a later file is the more expensive failure — so when both are on and no `onStrayTimers` is
1549
- given, the sweep prints one line to stderr saying how many it took away and where the first three
1550
- were scheduled; `onStrayTimers` gets all of them. Vitest's own report, with `strayTimers` off, points
1579
+ given, the sweep prints to stderr the report `'throw'` fails with — the file, the first three timers
1580
+ and the test that scheduled each — closed by one sentence saying they are missing from Vitest's
1581
+ "Async Leaks" report; `onStrayTimers` gets all of them. Vitest's own report, with `strayTimers` off, points
1551
1582
  its code frame at the `setTimeout` in the spec, because the library's own scheduler wrappers go
1552
1583
  through `vi.defineHelper` and are dropped from the stack.
1553
1584
 
@@ -1662,14 +1693,17 @@ initialisation — and the `afterAll` takes off everything added since. The piec
1662
1693
  `trackStrayListeners()` (idempotent, returns the undo), `baselineStrayListeners()`,
1663
1694
  `removeStrayListeners()` (returns how many), `countStrayListeners()` (throws before
1664
1695
  `trackStrayListeners()` has run, like the timer counter) and `describeStrayListeners()` — each
1665
- stray's target, type, spec file and up to five frames, the list `onStrayListeners({ removed, listeners })`
1666
- receives when a file should fail instead: `onStrayListeners: ({ removed }) => expect(removed).toBe(0)`,
1667
- or `onStrayListeners: 'throw'`, whose message lists each as `keydown on document from <file> <frames[0]>`.
1696
+ stray's target, type, spec file, up to five frames and the same `test` / `outsideTest` as a timer,
1697
+ the list `onStrayListeners({ removed, listeners })` receives when a file should fail instead:
1698
+ `onStrayListeners: ({ removed }) => expect(removed).toBe(0)`, or `onStrayListeners: 'throw'`, whose
1699
+ message names the file and lists each as `keydown on document, added in "<test>" at <frames[0]>`.
1668
1700
  A listener has `type` and `target` where a timer has `kind` and `delay` — a handler that prints its own
1669
- message prints those four fields instead.
1701
+ message prints those fields instead.
1670
1702
  With `strayTimers` on too, both reports run after both sweeps and both run even if the first throws —
1671
- a handler or `'throw'` alike; two failures surface as one `AggregateError`, so a throwing report never
1672
- leaves a timer uncancelled.
1703
+ a handler or `'throw'` alike; two failures surface as one `AggregateError`
1704
+ (`2 file-end checks failed when a.spec.ts ended:`, each report a numbered block with its `Docs:`
1705
+ line), so a throwing report never leaves a timer uncancelled. The stray-console file report runs in
1706
+ the same sweep.
1673
1707
  Under jsdom a listener registered with `{ once: true }` that already fired stays counted until
1674
1708
  something removes it: the wrapper cannot observe the firing without breaking identity-based
1675
1709
  `removeEventListener` from the code under test, and removing an already-fired listener is a no-op
@@ -1783,7 +1817,7 @@ Every channel is closed by default; the object is for narrowing it:
1783
1817
 
1784
1818
  | option | default | what it does |
1785
1819
  | -------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------- |
1786
- | `fetch` | `true` | `fetch` rejects, naming what was requested |
1820
+ | `fetch` | `true` | `fetch` rejects, naming the method, the URL and the test, and the `stubResponse` line that answers it |
1787
1821
  | `xhr` | `'reject'` | `'reject'` fails the request (`status` 0, an `error` event); `'empty'` answers 200 with an empty body; `false` leaves XHR alone |
1788
1822
  | `beacon` | `true` | `navigator.sendBeacon` answers `false` — only where the environment has one |
1789
1823
 
@@ -2134,17 +2168,21 @@ setupAutoSpy({ strayConsole: 'throw' }); // default 'off'; 'warn' prints the sam
2134
2168
  Any call to a console method that writes (`log`, `info`, `warn`, `error`, `debug`, `trace`, `table`,
2135
2169
  `dir`, `dirxml`, `timeLog`, `timeEnd`, `count`; `group` with a label; a falsy `assert`) made during a
2136
2170
  test that nothing absorbed fails **that test**, quoting the method, the first three lines and the first
2137
- frame outside `node_modules`:
2171
+ frame outside `node_modules`, and naming the spy that absorbs it:
2138
2172
 
2139
2173
  ```
2140
- [vitest-auto-spy] "CartService > reports a failed load" wrote to the console 1 time(s) and nothing absorbed it:
2174
+ [vitest-auto-spy] "CartService > reports a failed load" wrote to console.error 1 time and nothing absorbed it:
2141
2175
  - console.error: Error: load failed
2142
2176
  at CartService.load (src/app/cart.service.ts:41:15)
2177
+ Absorb what the test expects — installConsoleSpies() in a beforeEach, then assert consoleErrorSpy — or fix the code if the output is a defect.
2178
+ Docs: https://asdalexey.github.io/vitest-auto-spy/utilities/setup#_16-console-output-nothing-absorbed
2143
2179
  ```
2144
2180
 
2145
2181
  - **Absorbed** means the call never reached the console: a `/console` spy installed for the test or
2146
2182
  the file, `vi.spyOn(console, m).mockImplementation(…)`, any replacement that does not call
2147
- through. A bare `vi.spyOn(console, m)` calls through and **counts**.
2183
+ through. A bare `vi.spyOn(console, m)` calls through and **counts** — the report says so and names
2184
+ `.mockImplementation(() => undefined)`; a method with no `/console` spy (`table`, `dir`) gets a
2185
+ silent `vi.spyOn` instead.
2148
2186
  - **This library's own `'warn'`-grade reports are not stray console output.** `guardGlobals`,
2149
2187
  `prototypePollution`, `unconfiguredReads`, `swallowedStrictCalls`, `misconfiguration`, the
2150
2188
  duplicate-copy report, the skipped-teardown net and the `test.concurrent` notice write past the
@@ -2153,15 +2191,22 @@ frame outside `node_modules`:
2153
2191
  channel checks that the guard's own wrapper is the installed method and steps aside when anything
2154
2192
  sits on top of it.
2155
2193
  - Output **outside any test** — collection/import, `beforeAll` / `afterAll`, a callback after its
2156
- test ended — fails the **file** in `afterAll` (`… outside any test …`).
2194
+ test ended — fails the **file** at its end, in the same sweep as the timer and listener reports. The
2195
+ report names the phase — `while the file was being imported`, `in a beforeAll`,
2196
+ `after a test had ended`, or `outside any test` when they mix — and gives that phase's advice: for
2197
+ import-time output, the module and the line to fix. A recognised line (`NG0912`, any `NGxxxx`)
2198
+ adds a `Likely cause:` block. It always names the file that wrote the output: when that file's end
2199
+ never ran (its own `afterAll` threw), the next file reports it and says
2200
+ `It is reported at the end of b.spec.ts: the file-end check of a.spec.ts did not run.`
2157
2201
  - Every console method a test replaced is put back after it; one a file replaced, after the file.
2158
2202
  - Under the guard, **importing `vitest-auto-spy/console` installs nothing** — call
2159
2203
  `installConsoleSpies()` in a `beforeEach` (or at the top of the file). Spies an import installed
2160
2204
  before the guard armed are taken off.
2161
- - `strayConsole: { allow: ['…', /…/] }` is the last resort, for environment noise no spec can reach.
2162
- Output written to `process.stdout` / `process.stderr` directly, and jsdom's own virtual console, is
2163
- not seen. An Angular suite that resets modules under `isolate: false` allows `/NG0912/`, Angular's import-time
2164
- component-ID collision warning, which the guard otherwise blames on whichever file is importing.
2205
+ - `strayConsole: { allow: ['…', /…/] }` is the last resort, for a line from a dependency no spec can
2206
+ reach — the import-time report offers it, with the pattern, only when the quoted frame is inside
2207
+ `node_modules`. Output written to `process.stdout` / `process.stderr` directly, and jsdom's own
2208
+ virtual console, is not seen. `NG0912` is not noise to allow: it means two copies of one component
2209
+ in the bundle, fixed by importing the component from one place.
2165
2210
  - The wrapper forwards every call unchanged, so Vitest's attribution and `onConsoleLog` still work.
2166
2211
  `guardStrayConsole(reaction)` registers the same guard on its own.
2167
2212
 
@@ -2179,12 +2224,13 @@ an unconfigured call returns — a semantic switch, not a grade; the name was ta
2179
2224
  `blockNetwork` (changes the code under test), `restoreMocks` (drops `beforeAll` spies), failing on
2180
2225
  stray-timer counts (the sweep fails the file from `afterAll`, and a callback scheduled after the
2181
2226
  previous file's sweep is charged to the next — opt in with `onStrayTimers: 'throw'`, whose message
2182
- names each one's file, or `onStrayTimers: ({ timers }) => expect(timers).toEqual([])`), and
2227
+ names the file and the test that scheduled each one, or `onStrayTimers: ({ timers }) => expect(timers).toEqual([])`), and
2183
2228
  `enableAngularDiagnostics()`, which lives in `/angular/diagnostics` — call it in the same setup file as the Angular half of strict.
2184
2229
 
2185
2230
  `misconfiguration: 'throw'` on its own makes the library's misuse reports — an `onlyMethodsToSpyOn`
2186
2231
  typo, `gettersToSpyOn` naming a method, a `returns` key no spy answers to, `injectSpy` handed a real
2187
- instance, a `jasmine.DEFAULT_TIMEOUT_INTERVAL` write, `providedMethodNames` — throw at the call site,
2232
+ instance, a `jasmine.DEFAULT_TIMEOUT_INTERVAL` write, `providedMethodNames`, a `nextWithValues()` on an
2233
+ observable property something already subscribed to — throw at the call site,
2188
2234
  every occurrence. The grade is process-wide and released after the file. The printed grade of the
2189
2235
  `injectSpy` warning is de-duplicated per token **per spec file**, no longer per worker.
2190
2236
 
@@ -2411,6 +2457,37 @@ once and leaves a working storage alone; this one replaces whatever is there, fo
2411
2457
  installs even in a `node` environment because the spec asked for it. Named-property access
2412
2458
  (`localStorage.token`, `Object.keys(localStorage)`) does not see the items — read through the API.
2413
2459
 
2460
+ ### Animation frames and element boxes
2461
+
2462
+ Do not assign `window.requestAnimationFrame` by hand, and do not stub `getBoundingClientRect` with a
2463
+ partial literal. Both have a stub on `/dom-stubs`:
2464
+
2465
+ ```ts
2466
+ import { stubAnimationFrame, stubElementRect } from 'vitest-auto-spy/dom-stubs';
2467
+
2468
+ beforeEach(() => {
2469
+ stubAnimationFrame(); // 'immediate': the callback runs before requestAnimationFrame returns
2470
+ stubElementRect(container, { width: 800, height: 600 }); // x / y default to 0
2471
+ });
2472
+
2473
+ it('scrolls after the next frame', () => {
2474
+ const frames = stubAnimationFrame({ mode: 'queued' });
2475
+
2476
+ list.scrollToSelected();
2477
+ expect(frames.pending).toBe(1);
2478
+
2479
+ frames.flush(); // one frame; the timestamp defaults to performance.now()
2480
+ expect(list.scrolled).toBe(true);
2481
+ });
2482
+ ```
2483
+
2484
+ `stubAnimationFrame` stubs `cancelAnimationFrame` too, and hands both spies back as
2485
+ `frames.requestAnimationFrame` / `frames.cancelAnimationFrame`. A frame requested from inside a
2486
+ running frame is queued in either mode, so a loop that requests its own next frame advances one step
2487
+ per `flush()`. `stubElementRect` returns a real `DOMRect` per call, edges derived, and returns the undo.
2488
+ Both go through `mockValueProp`, so `restoreMockedProps()` (and `setupAutoSpy()` between tests) takes
2489
+ them off; `frames.restore()` does it sooner.
2490
+
2414
2491
  ### A module mock that did nothing
2415
2492
 
2416
2493
  `vi.mock()` is the one thing in a ported suite that fails **silently**. Under a bundler
@@ -3246,7 +3323,7 @@ TestBed.configureTestingModule({ providers: [provideAutoSpy(MatDialog)] });
3246
3323
  injectSpy(MatDialog).open.mockReturnValue(createMatDialogRef(MatDialogRef, { closedWith: 'saved' }).ref);
3247
3324
  ```
3248
3325
 
3249
- Five things to know:
3326
+ Six things to know:
3250
3327
 
3251
3328
  - **`close` is the spy, and it is the same function the ref carries.** `expect(dialog.close)` and
3252
3329
  `expect(TestBed.inject(MatDialogRef).close)` are one assertion. `emitClose(result?)` is the other
@@ -3259,7 +3336,15 @@ Five things to know:
3259
3336
  - **`MAT_DIALOG_DATA` is `InjectionToken<any>` in Material**, so a hand-written `useValue: null`
3260
3337
  compiles for a component that reads `data.name`. `provideMatDialogData<EditUserData>(…)` checks the
3261
3338
  object; a token of your own typed `InjectionToken<EditUserData>` checks it without naming anything.
3262
- The value is handed out as it is, so build it per test rather than hoisting it to a constant.
3339
+ With the type argument named the token parameter is `InjectionToken<unknown>`, so the call passes
3340
+ `@typescript-eslint/no-unsafe-argument` — do not drop the type argument to silence that rule, it
3341
+ only turns the data back into `any`. The value is handed out as it is, so build it per test rather than hoisting it to a constant.
3342
+ - **Name the ref type as a type argument, not as an instantiation expression.**
3343
+ `injectMatDialogRef(MatDialogRef<NameInputDialog, string>)` reads `Ref` off the class's `prototype`,
3344
+ which Material types `MatDialogRef<any, any>`, so the handle comes back `any`-typed and assigning it
3345
+ to a typed variable is `no-unsafe-assignment`. Write
3346
+ `injectMatDialogRef<MatDialogRef<NameInputDialog, string>>(MatDialogRef)` — the same for
3347
+ `createMatDialogRef` and `provideMatDialogRef`.
3263
3348
  - **`componentInstance` is how the opener drives the dialog**, and it is the one member Material
3264
3349
  keeps off the prototype, so a double that did not have it would read `undefined` rather than
3265
3350
  fail. Hand the stand-in over and the opener works against it — checked, member by member, against
@@ -3409,6 +3494,12 @@ await setInputs(fixture, { projectId: 7 }, { label: 'the task list' }); // optio
3409
3494
  // a fixture whose componentType carries no ɵcmp is refused by name too, not as a bare TypeError
3410
3495
  // aliases resolve by CLASS FIELD name, and an input a hostDirectives entry exposes is accepted
3411
3496
 
3497
+ // a typed element instead of Angular's `any` — never `fixture.nativeElement as HTMLElement`
3498
+ const host = hostElement(fixture); // HTMLElement, checked with instanceof
3499
+ queryElement(fixture, '.close').click(); // HTMLElement; throws naming the selector when nothing matches
3500
+ queryElement(fixture.debugElement, 'input[name=q]', HTMLInputElement).value; // typed; wrong type throws
3501
+ // also on /bun-angular; queryElement takes an element found earlier as its root too
3502
+
3412
3503
  // build a class through DI, every unprovided token auto-spied
3413
3504
  const { instance, spies } = createWithAutoSpies(CartService, {
3414
3505
  providers: [{ provide: TaxService, useValue: realTax }], // explicit providers win
@@ -3903,7 +3994,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
3903
3994
  | `prefer-set-inputs` | `warn` | suggest | a run of `fixture.componentRef.setInput('title', v)` on one fixture → `await setInputs(fixture, { title: v })` — the name is resolved against the compiled definition before the first write (an undeclared one is an `NG0303` and no change) and the value is typed. The run collapses into one call and a `detectChanges()` under it goes; offered, not applied, because `stable()` ticks and a zone.js suite answers that with `NG0101` |
3904
3995
  | `prefer-spy-on-own-method` | `warn` | `--fix` / suggest | `createSpyFromInstance(x, { onlyMethodsToSpyOn: ['m'], passthrough: true })` read for `m` alone — `.m` on the call, `const { m } = …`, a bare statement, or a name read only as `v.m` → `spyOnOwnMethod(x, 'm')`; the same whitelist with `returns: { m: undefined }` → `spyOnVoidMethod(x, 'm')`. Both fix, import and all; a `Spy<X>` annotation becomes `Spy<X>['m']` in a suggestion. A bare `returns: { m: undefined }` seed is a suggestion, offered only on a real event or element — `new MouseEvent(…)`, `document.createElement(…)`, `fixture.nativeElement` — never on a double |
3905
3996
  | `prefer-observer-stub` | `error` | — | a hand-rolled observer global → `stubIntersectionObserver()` / `stubResizeObserver()` / `stubMutationObserver()`; the manual save-and-restore goes too, `restoreMockedProps()` runs the undo |
3906
- | `no-hand-assigned-global` | `error` | — | a double assigned to a global (`global.fetch = vi.fn()`, `window.matchMedia = vi.fn()`, `window.localStorage = { getItem: vi.fn() }`) with no restore in `afterEach` / `afterAll` / `onTestFinished` → `mockValueProp(globalThis, name, value)` or `vi.stubGlobal` + `unstubGlobals`; `blockNetwork()` for network globals, `stubWebStorage()` for the storages; the three observers stay with `prefer-observer-stub` |
3997
+ | `no-hand-assigned-global` | `error` | `--fix` | a double assigned to a global (`global.fetch = vi.fn()`, `window.matchMedia = vi.fn()`, `window.localStorage = { getItem: vi.fn() }`) with no restore in `afterEach` / `afterAll` / `onTestFinished` → `mockValueProp(globalThis, name, value)` or `vi.stubGlobal` + `unstubGlobals`; `blockNetwork()` for network globals, `stubWebStorage()` for the storages; the three observers stay with `prefer-observer-stub`; any value written into an imported object (`environment.production = true`) → `mockValueProp(environment, 'production', true)`, fixed in a test or `beforeEach` |
3907
3998
  | `prefer-stub-response` | `error` | — | an object literal cast to `Response` (`as Response`, `as unknown as Response`, `<Response>{ … }`) or `createMock<Response>(…)` / `createAutoMock<Response>(…)` → `stubResponse({ body })` from `/setup`; the literal answers `undefined` for every member it does not list (`status`, `headers`, `text()`) and the cast is what makes that compile. `Response` must resolve to the **global**, so an Express handler's or a generated client's `Response` is never reported |
3908
3999
  | `prefer-provide-activated-route` | `error` | — | a hand-built `ActivatedRoute` — any `useValue` / `useClass` / `useFactory` / `useExisting`, and `provideAutoSpy(ActivatedRoute)` too → `provideActivatedRoute({ … })`; the double knows either the streams or the snapshot, never both, and `injectActivatedRoute().setParams(…)` moves them together mid-test |
3909
4000
  | `no-passthrough-console-spy` | `error` | suggest | `vi.spyOn(console, m)` nothing gives an implementation — it calls through and prints → `installConsoleSpies()` + `consoleXSpy`, or `.mockImplementation(() => undefined)` |
@@ -4110,6 +4201,10 @@ rules answer the same way they did — the ordering rules (`no-inject-before-ove
4110
4201
  decide by range, `prefer-render-shallow` asks the template-read question once per file, and
4111
4202
  `no-redundant-smoke-test` indexes identifiers only where a smoke test exists to judge.
4112
4203
 
4204
+ **A rule message names what it found and one repair, and ends in `Docs: <url>`** — the rule's own
4205
+ section of the rules page (`utilities/eslint-rules#<rule-name>`), which is also its `meta.docs.url`.
4206
+ The reasoning a message leaves out lives in that section; read it before overriding a severity.
4207
+
4113
4208
  The legacy `.eslintrc` `plugins: []` form cannot work — it resolves names to `eslint-plugin-*`
4114
4209
  packages, which a subpath export can never be.
4115
4210
 
@@ -4119,10 +4214,11 @@ packages, which a subpath export can never be.
4119
4214
 
4120
4215
  | Message contains | Cause | Fix |
4121
4216
  | -------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
4122
- | `No mock adapter registered` | no runtime entry was imported, or the wrong one — or the setup file that imports it never ran: an `@angular/build:unit-test` / `@nx/angular:unit-test` target runs only the `setupFiles` it names (`doctor` reports `builder-setup-unreached`) | import `vitest-auto-spy` (Vitest) / `…/bun` / `…/node` once before creating spies; under the unit-test builder, name the setup file in the target's `setupFiles` |
4217
+ | `No mock adapter registered` | no runtime entry was imported, or the wrong one — or the setup file that imports it never ran: an `@angular/build:unit-test` / `@nx/angular:unit-test` target runs only the `setupFiles` it names (`doctor` reports `builder-setup-unreached`) | the one import the message names — it detects the runner (Vitest, `bun:test`, `node:test`, Rstest) and links that runner's installation section; under the unit-test builder, name the setup file in the target's `setupFiles` |
4123
4218
  | `No "X" export is defined on the "<m>" mock`, in a spec whose `vi.mock('<m>')` has no factory | another spec file mocked `<m>` with a factory, and under `isolate: false` Vitest hands that factory to a later automock or `{ spy: true }` mock of the same module (`doctor` reports `module-mock-leak`) | mock it the same way in both files, or `vi.resetModules()` in this file before importing it |
4124
4219
  | `Observable spies require rxjs` | the rxjs layer was never loaded | `import 'vitest-auto-spy/rxjs';` once, in the setup file |
4125
4220
  | `… is not subscribable (…)` | the argument is not an observable — most often the value it emits, or a promise | pass the observable itself; `await` a promise directly (§8) |
4221
+ | `expectEmission(x): no value within N ms (0 received)` | nothing triggered the stream — or, when the message says timers are fake, the callbacks feeding it wait on a clock this watchdog never advances | check the call that should make it emit and the spy feeding it (`nextWith`); under fake timers pass `{ advance: () => vi.advanceTimersByTime(ms) }` (§8) |
4126
4222
  | ``… the `advance` callback threw: …`` | the `{ advance }` callback failed; the original is on `cause` and the subscription is torn down | fix the callback — a `vi.runAllTimers()` with no fake timers installed is the usual one (§8) |
4127
4223
  | `expectEmissions(source$, 0) can never succeed` | a count below 1 — usually `expectEmissions(s, expected.length)` with an empty expectation | `expectNoEmission(source$)`, which is the assertion that was meant (§8) |
4128
4224
  | `setEmissionTimeout(NaN) needs a non-negative number of milliseconds` | arithmetic on something unset reached the setter | pass a number, `0` or `Infinity` for no watchdog (§8) |
@@ -4133,42 +4229,51 @@ packages, which a subpath export can never be.
4133
4229
  | `jasmine is not defined` | jasmine's global is the runner's, and Vitest declares none | `import { jasmine } from 'vitest-auto-spy/jasmine'`, then `codemod --from jasmine` to rewrite the members (§20) |
4134
4230
  | `done.fail is not a function` | Vitest passes a `TestContext`, not jasmine's `done`; the line usually sits in an `error` callback nobody awaits, so the run stays **green** | assert on the failure — `await expect(firstValueFrom(src$)).rejects.toMatchObject({ … })`, or `expect.fail(message)` |
4135
4231
  | a `.withContext('…')` message that never appears in the failure output | Vitest's chai layer has an `@internal` `withContext(flags)`; handed a string it walks the character indices, sets nonsense flags and returns `this` | `expect(actual, 'message').toBe(expected)` — the second argument of `expect` is the label. Nothing throws, so nothing warns (§20) |
4136
- | `names method(s) that are not on the class prototype` | typo, or an instance-field callable | fix the name, or move it to `instanceMethodsToSpyOn` |
4232
+ | `onlyMethodsToSpyOn names 'x' (did you mean 'y'?), not a method of X` | a typo, or a method the constructor assigns (an arrow property, a `signal()` field, a `signalStore()` method) | take the name the message suggests; with no suggestion it names `instanceMethodsToSpyOn: ['x']`, which adds to the discovered methods (§5) |
4137
4233
  | `All declarations of 'Matchers' must have identical type parameters` | a custom matcher declares `interface Matchers<T = any>` (the Vitest 4 shape) while Vitest 5 declares `Matchers<R, T>` | declare it on `Chai.Assertion` instead — that interface has no type parameters in either major, so one declaration merges on both (§10) |
4138
4234
  | `toHaveBeenCalledTimes` reads `0` for a call an earlier test made, after moving to Vitest 5 | `clearMocks` defaults to `true` on Vitest 5, so `vi.clearAllMocks()` runs before every test | count the calls in a plain variable instead of reading the spy, or set `clearMocks: false` (§10) |
4139
- | `was configured with 'mustBeCalledWith'` | the code called the spy with other arguments | that is the assertion firing — fix the code, or relax to `calledWith` |
4235
+ | `X.method is set up with mustBeCalledWith, and this call matches none of its configs` | the code called the spy with other arguments; with one config set up the line ends with the first that differs (`argument 2: expected 'eu', got 'us'`). `X.` appears on a strict double only | that is the assertion firing — fix the value the code under test passes, or configure that call too; relax to `calledWith` when other arguments are fine |
4140
4236
  | `extendWithAutoSpies needs Vitest 4.1 or newer` | the `test` handed in has only the object-form `extend` (Vitest ≤ 4.0); the builder form the helper is written against arrived in 4.1 | upgrade Vitest, or keep `provideAutoSpy` + `injectSpy` in a `beforeEach` until then |
4141
4237
  | `advanceTimers() requires fake timers` | no fake timers installed | `setupFakeTimers()` or `vi.useFakeTimers()` first |
4142
4238
  | `advanceTimers() found only the clock faked, not the timers` | `mockSystemTime()` installed `Date` alone, so there is nothing to advance and the call used to pass having done nothing | `setupFakeTimers()` (or `vi.useFakeTimers()`) in the test that drives timers (§11) |
4143
- | `Nothing configured X.method, and strict mode is on` | a strict double was asked for a method no line configured | configure it (`mockReturnValue` / `resolveWith` / `nextWith` / `calledWith`), or `{ strict: false }` on that double (§5) |
4144
- | `X.member was read N times and nothing configured it, and strict mode is on` (or `was subscribed to N times and nothing fed it`), after the test | `setupAutoSpy({ unconfiguredReads })` — the code under test read a strict double's spied getter and got `undefined`, or subscribed to its observable property and nothing ever emitted | configure it: `accessorSpies.getters.member.mockReturnValue(…)`, `overrides: { member }`, `mockReadonlyProp`; `nextWith` / `returnSubject` / `complete` for a stream; `mockReturnValue(undefined)` when `undefined` is the answer meant; `{ strict: false }` on that double (§5) |
4145
- | `[vitest-auto-spy] "…" wrote to the console N time(s) and nothing absorbed it` | `setupAutoSpy({ strayConsole })` — the test printed, and nothing that does not call through stood on `console`: no `/console` spy installed for it, no `mockImplementation`. A bare `vi.spyOn(console, m)` calls through, and a `console.warn` from this library counts too | absorb and assert: `installConsoleSpies()` in a `beforeEach`, then `expect(consoleErrorSpy)…`; or fix the code that printed. Importing a spy installs nothing under the guard. `strayConsole: { allow: [...] }` only for environment noise no spec can reach |
4239
+ | `X.method(…) was called; this strict double has nothing configured for it` | a strict double was asked for a method no line configured | paste the call the `Configure it in the test:` line names (`calledWith(args).mockReturnValue(…)`, or `mockReturnValue(…)` for any); `Called from` is the calling line; or `{ strict: false }` on that double (§5) |
4240
+ | `X.member was read N times on a strict double and nothing configured it` (or `was subscribed to N times on a strict double and never emitted`) | `setupAutoSpy({ unconfiguredReads })`, after the test — the code under test read a strict double's spied getter and got `undefined`, or subscribed to its observable property and nothing ever emitted | the advice under each finding names the member: `accessorSpies.getters.url.mockReturnValue(…)` for a getter, `events.nextWith(…)` or `overrides: { events: new Subject() }` for a stream; `mockReturnValue(undefined)` when `undefined` is the answer meant (§5) |
4241
+ | `[vitest-auto-spy] "…" wrote to console.<method> N time(s) and nothing absorbed it` | `setupAutoSpy({ strayConsole })` — the test printed, and nothing that does not call through stood on `console`: no `/console` spy installed for it, no `mockImplementation`. A bare `vi.spyOn(console, m)` calls through, and a `console.warn` from this library counts too | the report names the spy to assert (`installConsoleSpies()` in a `beforeEach`, then `consoleErrorSpy`, …), or says the `vi.spyOn` calls through and needs `.mockImplementation(() => undefined)`; or fix the code that printed. Importing a spy installs nothing under the guard |
4146
4242
  | `… wrote to the console N time(s) outside any test` | output while the file was imported, in a `beforeAll` / `afterAll`, from a callback after its test ended, or from a test whose `afterEach` never ran — the frame names the line | move the work into a test and absorb it there; a callback firing late is a leak — clear it where it is scheduled |
4243
+ | `… wrote to console.<method> N time(s) while the file was being imported` | a module the file imports wrote while it was evaluated (a static block, a decorator, a top-level call), or a `describe` body did — before any hook, so no spy absorbs it; it lands on the first file of the worker to import that module | fix the line the report names, reading the `Likely cause:` block (`NG0912`: two copies of one component in the bundle — import it from one place); `strayConsole: { allow: [...] }` only for a line inside `node_modules`, which the report offers itself |
4244
+ | `It is reported at the end of b.spec.ts: the file-end check of a.spec.ts did not run.` | `a.spec.ts`'s own `afterAll` threw, so its file-end checks never ran and its console output was carried into the next file | fix that `afterAll` first — its error is in `a.spec.ts`'s output — then the output the report quotes, in the file it names |
4245
+ | `"…" made N call(s) a strict double had nothing configured for, and the throw never reached the test` | `setupAutoSpy({ strict: true })` — a strict double threw, and a `catch` in the code under test or an RxJS subscriber with no error callback swallowed it; each call is quoted with its arguments and the line that made it | configure that method (`.mockReturnValue`, `.calledWith` or `returns`), or drain it with `takeStrictViolations()` when the test provokes it on purpose (§5) |
4246
+ | `a.spec.ts left N timer(s) pending when it ended` | `onStrayTimers: 'throw'` — a timer outlived its file; each line names the test that scheduled it (or `while the file was imported`, `outside any test`), and it was cancelled | clear it in the test named — `clearTimeout`, unsubscribe, `fixture.destroy()` — or run it out with fake timers before the test ends (§10) |
4247
+ | `a.spec.ts left N window/document listener(s) attached when it ended` | `onStrayListeners: 'throw'` — a listener outlived its file; each line names the test that added it, and it was removed | remove it in that test — `removeEventListener` with the same capture flag, an `AbortSignal`, `fixture.destroy()` (§10) |
4248
+ | `N file-end checks failed when a.spec.ts ended` | several file-end reports failed together — stray timers, listeners, console output — and each numbered block is one of them | fix each block on its own; Vitest also lists every member error separately |
4249
+ | `fetch is stubbed in unit tests — GET <url>. The test "…" requested it` | `blockNetwork` refused a request the named test made (a blocked `XMLHttpRequest` carries the same marker on `statusText`) | answer it in that test — `vi.spyOn(globalThis, 'fetch').mockResolvedValue(stubResponse({ body: … }))` — or `blockNetwork({ xhr: 'empty' })` for an XHR nobody reads (§10) |
4250
+ | `X.member.nextWithValues() ran after something subscribed to X.member` | `nextWithValues` on an observable property publishes a new stream, which the subscriber already holding the old one never sees; `X.` appears on a strict double only | call it before the code under test subscribes, or push into the held stream with `nextWith()`; a throw under `setupAutoSpy({ misconfiguration: 'throw' })` |
4251
+ | `assertMocked(…): x is the real function — … did not apply` (or `no export is a mock`) | the module mock did not reach the code under test; the message names the cause that fits the run — `isolate: false` with the module already loaded, or the code importing it through another path | under `isolate: false` mock it in the earlier file too or run this file isolated; otherwise `vi.mock` the specifier the code imports, or inject the dependency |
4147
4252
  | `setupAutoSpy({ strict: true })` has no effect — no double throws, a suite with it on stays green | before this release the default lived in module scope, and `/setup` and the factory bundles each carried a copy; a 1759-file consumer ran 12 717 tests green with it on | upgrade — the default, `onUnstubbedCall` and `setSpyEngine()` now live on `globalThis` |
4148
4253
  | a seeded getter reads `undefined`: `provideAutoSpy(X, { overrides: { flagsConfig } })` next to `gettersToSpyOn` or a `registerAutoSpyDefaults` row | before this release the seed was assigned into the spied accessor's setter, and the getter kept answering `undefined` | upgrade — `overrides` now seeds the getter spy; a later `accessorSpies.getters.x.mockReturnValue(…)` still wins |
4149
4254
  | `Cannot read properties of undefined (reading 'name')` on `value.constructor.name`, with `createAutoMock<T>()` | `constructor` answered `undefined` on an auto-mock | upgrade — it answers `Object`, as every other double does; or seed it: `createAutoMock<T>({ constructor: … })` |
4150
4255
  | `Cannot redefine property: …` from a library accessor spy | the target is an ES module namespace a bundler inlined | no spy library can win this — give the code a real seam (inject it, pass it in) and spy on that |
4151
4256
  | `Cannot spy on this instance in place: it is not extensible` | `createSpyFromInstance` was handed a frozen or sealed object | spy it before it is frozen, or build a separate double with `createSpyFromClass` (§2) |
4152
4257
  | `Cannot mock the property 'X': it is not configurable` | the member was defined with `configurable: false`, so it cannot be redefined | members replaced before it are still restorable — `restoreSpiedInstance(instance)` or `restoreMockedProps()` |
4153
- | `overrideComponentProvider(…): the override did not apply` | the component injects a different token, or a later `overrideProvider` won | pass the token the component actually injects (a base class, an `InjectionToken`), and override after nothing else re-configures |
4154
- | `the test ended with N unflushed HttpTestingController request(s)` | `enableAngularDiagnostics({ pendingRequests })` — nothing answered them | flush each (`controller.expectOne(url).flush(body)`), or `controller.verify()` where absence is the assertion (§13) |
4258
+ | `overrideComponentProvider(…): the override did not apply — C resolved T to …` | the component injects a different token, or something configured the token again after the call; the message says whether the real service or a different double won | pass the token the component actually injects (a base class, an `InjectionToken`), and override after nothing else re-configures |
4155
4259
  | `.value() was read while the resource is in the error state: …` | `mockResourceProp`'s double after `fail(reason)`, as a real `ResourceRef` behaves; the reason is on `cause` | branch on `hasValue()` / `status()` first, or assert with `toHaveResourceError()` (§13) |
4156
4260
  | `@angular/core <version> no longer carries …, which this package reads` | an Angular internal this package reads has moved; checked once per worker | nothing is fixable from a spec — report the version in the message and pin the previous Angular until a release reads the new shape (§13) |
4157
4261
  | `provideHttpTesting(): globalThis.__vitest_worker__ is not there` | the runner does not say which test is running, so the end-of-test check cannot arm — `bun:test`, `node:test` | call `verifyNoPendingRequests()` yourself (§13) |
4158
- | `NgModule(s) with an empty runtime scope: …` | `ngModuleScopes` (or `assertNgModuleScopes`) — an AOT test bundle stripped `ɵɵsetNgModuleScope`, so the import contributes nothing | declare what the spec needs in the TestBed module directly; pass only modules expected to bring declarations (§13) |
4262
+ | `ngModuleScopes: X is imported into the testing module but contributes nothing` | `enableAngularDiagnostics({ ngModuleScopes })` — an AOT test bundle stripped `ɵɵsetNgModuleScope`, so the directives it exports are missing (`NG0303` / `NG0304`) | import the directives it exports directly, or declare them in the TestBed (§13) |
4263
+ | `assertNgModuleScopes(): X has an empty runtime scope` | the same stripped `ɵɵsetNgModuleScope`, found by the manual check; a module with providers of its own may be providers-only, and the message says so | declare what the spec needs in the TestBed module directly; leave a providers-only module out of the call (§13) |
4159
4264
  | `Cannot read properties of undefined`, stack inside the component's own constructor or a field initializer | a member the double must **hold** rather than spy on is read while the component is being constructed, and the `mockReadonlyProp` that seeds it is written _below_ `TestBed.createComponent` — so it has not run yet, and never will | seed it **before** construction: `provideAutoSpy(X, { overrides: { paymentParams: … } })`, or `provideAutoSpyForToken(TOKEN, { … })`. No later call can repair this — the throw happens inside `createComponent`, so the helper is not reached and cannot say so; the stack names the component (§9, §13) |
4160
4265
  | `arr.map(asInstance)` infers `unknown[]` | `asInstance` is overloaded, and passing it **by reference** resolves against the `DeepMockProxy<T>` overload, from which `T` cannot be inferred | `arr.map((spy) => asInstance(spy))` — the arrow gives each call its own inference. For a fixed set, `asInstances(a, b, c)` keeps the tuple types (§6) |
4161
4266
  | `Cannot read properties of undefined (reading 'provide')`, stack inside `render3/di_setup` | a barrel chunk had not run when the component's definition was built, so a provider slot is `undefined` | `assertComponentDefIntact(Cmp)` before `createComponent` — it names the list and the index (§13) |
4162
4267
  | `assertComponentDefIntact(): argument N is undefined, which carries no ɵcmp or ɵdir` | the class reference itself never arrived — the `Cannot read properties of undefined (reading 'ɵcmp')` case | the same split barrel; import the component from its own module, not the barrel (§13) |
4163
4268
  | `TS1117: An object literal cannot have multiple properties with the same name`, in a fixture | a hundred-line model literal copied into eight specs and edited independently | one `createFixtureFactory<T>(defaults)`; note the runtime keeps the **second** key, so do not auto-fix by dropping one (§12) |
4164
4269
  | `configureTestingModule was given N schema(s) that can never apply` | `enableAngularDiagnostics({ deadSchemas })` — `schemas` next to a standalone component, with `declarations` empty | drop the `schemas` entry and put the missing directive/pipe in the component's own `imports`, or use `createDirectiveHost` (§13) |
4165
- | `injectSpy(X): the injector returned a plain instance, not an auto-spy` | the token is provided for real; a `console.warn` once per token and spec file, a **throw** under `enableAngularDiagnostics({ unspiedProviders })` or `setupAutoSpy({ misconfiguration: 'throw' })` | `provideAutoSpy(X)` / `provideAutoSpyForToken(TOKEN)`, or `TestBed.inject(X)` if the real thing is what the spec wants |
4270
+ | `injectSpy(X): got a real X` (or `got a plain value, not an auto-spy` for an `InjectionToken`) | nothing in the testing module provides a double; a `console.warn` once per token and spec file, a **throw** under `enableAngularDiagnostics({ unspiedProviders })` or `setupAutoSpy({ misconfiguration: 'throw' })` | the fix the message names: `provideAutoSpy(X)` in `providers`, or `{ provide: TOKEN, useValue: createAutoMock<T>() }` for a token; `TestBed.inject(X)` if the real thing is what the spec wants |
4166
4271
  | `trackInjections(...).get(X): that token is not tracked by this log` | `get` only answers for tokens whose providers the log created | add the token to the `trackInjections([...])` list, or read it from the injector directly (§13) |
4167
4272
  | `Spread syntax requires ...iterable[Symbol.iterator] to be a function`, at load time | a module-scope spread of an imported binding, inside a bundle | build the array in a function or a getter — `no-import-time-spread` (§16) |
4168
4273
  | `the timers APIs are not mocked` in a nested `describe`'s `beforeAll` | fakes armed in `beforeEach` only; Jest armed them for the whole file | `setupFakeTimers(cfg, { betweenTests: true })` / `setupAutoSpy({ globalFakeTimers: true })` |
4169
4274
  | setup-file hooks reaching only the first spec file of a worker | the setup module stayed cached (Angular unit-test builder + coverage) | run coverage with `--isolate`, or call `setupAutoSpy()` from a per-file module |
4170
4275
  | `no DOM could be installed` | `bun-angular` preload with no DOM package | `bun add -d @happy-dom/global-registrator` (or `jsdom`) |
4171
- | `cannot read "…" referenced by …` | a `templateUrl` / `styleUrls` path does not resolve | fix the path, relative to the component file |
4276
+ | `cannot read "…" referenced by …: ENOENT at …` | a `templateUrl` / `styleUrl` path does not resolve; the errno (`ENOENT`, `EACCES`) and the resolved path say why | fix the path, relative to the component file |
4172
4277
  | duplicate-copy report from `setupAutoSpy()` | two installs, or one loaded as both ESM and CJS | dedupe the dependency; `setupAutoSpy({ duplicateCopies: 'warn' })` to downgrade. A setup file that initialises the Angular test environment before the library loads splits Angular the same way — two copies, a second `TestBed`, and `No mock adapter registered` or overrides that never apply: `resolve.dedupe: ['@angular/core', '@angular/common', '@angular/platform-browser', '@angular/compiler', 'rxjs']` plus `test.server.deps.inline: ['vitest-auto-spy']` (Vitest 1 and up) leaves one copy of each |
4173
4278
  | `Type 'Spy<T>' is not assignable to type 'T'` | `Spy<T>` drops private members — by design | declare as `Spy<T>`, or use `asInstance()` / `asSpy()` (§6) |
4174
4279
  | a spy is never called, no warning | the method is an instance field, not on the prototype | `instanceMethodsToSpyOn`, or `createAutoMock<T>()` |
@@ -4212,7 +4317,7 @@ packages, which a subpath export can never be.
4212
4317
  | `Not implemented: HTMLMediaElement.play`, or `duration` is `NaN` and cannot be set | jsdom implements the media elements as a shell | `stubMediaElement({ duration })`, then `media.set(el, …)` to fire the events |
4213
4318
  | `Cannot set base providers because it has already been called` | zone and zoneless spec files sharing one worker | `setupAngularTestEnv({ zoneless, initZone, initZoneless })` (§13) |
4214
4319
  | a stub that works in the first test of the file and in no other | installed at `describe` level or in `beforeAll`, then restored away | install it in `beforeEach`, or `installPerTest(() => stub…())` |
4215
- | a third-party library failing every other run, no test named | a test sealed a global with `Object.defineProperty` (non-configurable) | `setupAutoSpy({ guardGlobals: 'throw' })` names the file; then `mockValueProp` |
4320
+ | a third-party library failing every other run, no test named | a test sealed a global with `Object.defineProperty` (non-configurable) | `setupAutoSpy({ guardGlobals: 'throw' })` names the test; then the `mock*Prop` helper the message names |
4216
4321
  | a spec green alone that fails only in a full run, on something it reads off `<body>` / `<html>` (`querySelector('[data-…]')`, a class) | an earlier file in the worker left an attribute on the shared document | `setupAutoSpy({ documentPollution: 'throw' })` (in `preset: 'strict'`) names the test; take the attribute off in `ngOnDestroy` / `DestroyRef.onDestroy` or an `afterEach` |
4217
4322
  | `A metric with the name … has already been registered` as a failed suite with 0 failed tests, only in a full run | under `@angular/build:unit-test` with `isolate: false` a workspace module can run its module scope once **per spec file**, while an external package such as `prom-client` keeps one default registry per worker | not something a hook can catch — it throws at import. Look the metric up before creating it: `register.getSingleMetric(name) ?? new Histogram({ name, … })`, or give the module its own `new Registry()` |
4218
4323
  | `NotSupportedError: This name has already been registered in the registry` at import | the same per-spec-file evaluation reaching a module-scope `customElements.define` — the registry belongs to the worker's document | `if (!customElements.get(name)) customElements.define(name, Element)`; a definition cannot be undone, so nothing can sweep it |
@@ -4226,10 +4331,10 @@ packages, which a subpath export can never be.
4226
4331
  | `Expected to be running in 'ProxyZone', but it was not found` | `zone.js/testing` patches jasmine/mocha/jest, not Vitest | `import 'vitest-auto-spy/zone'` after zone.js, with `globals: true` (§14) |
4227
4332
  | `nextWith` demanding `HttpEvent<T>` on a generated API client | `Parameters`/`ReturnType` read the **last** overload | `asSpy<Client, { overload: 'first' }>(…)`, or `Overload<M, 0>` — never `@ts-expect-error`, which `no-ts-expect-error-on-double` reports (§16) |
4228
4333
  | `NG0303` / `NG0304` / nothing at all, from a directive spec | the host is `standalone: false`, or the module is in the TestBed | `createDirectiveHost({ template, scope: [Module] })` (§13) |
4229
- | `setInputs: … declares no input named …` | the key is not an input of that component — a typo, an input renamed since, or one of its own fields | set the names the message lists; a signal the component owns is moved through the signal (§13) |
4230
- | `setInputs: X carries no ɵcmp, so there are no inputs to set` | the fixture's `componentType` is not a compiled `@Component` — a `@Directive` or `@Pipe` class, or one Angular never compiled (a barrel import that resolved to `undefined`) | pass a component fixture; a directive takes its inputs through the host element that applies it (`createDirectiveHost`), and for `undefined` import the class from its own file (§13) |
4334
+ | `setInputs: … declares no input named …` (with a `Did you mean` for a near typo) | the key is not an input of that component — a typo, an input renamed since, or one of its own fields | take the suggestion, or set a name the message lists; a signal the component owns is moved through the signal (§13) |
4335
+ | `setInputs: X carries no ɵcmp, so there are no inputs to set` (or `the component class is undefined`, `X is a @Pipe`) | the fixture's `componentType` is not a compiled `@Component` — a `@Pipe`, a class Angular never compiled, or `undefined` from a barrel split across chunks | pass the `@Component` class the fixture was created from; call a pipe's `transform()` directly; for `undefined` import the class from its own file (§13) |
4231
4336
  | `TypeError: dependencies is not a function`, from `renderShallow({ keepTemplate: true })` | the compiled `ɵcmp.dependencies` is the flat array AOT emits when no cycle forced a factory, and the trim called it | upgrade — all three shapes (array, factory, `null`) are read; nothing to change in the spec |
4232
- | `renderShallow(X, { keepTemplate: true }): … declared by an NgModule rather than standalone` | an AOT dependency list: ngtsc flattened an imported `NgModule`'s exported declarations into it, and Angular takes only standalone declarations and `NgModule`s in `imports` | drop `keepTemplate` when the spec reads TypeScript state only, or build the component with `TestBed` directly and keep its compiled scope — the declarations named are not the thing to change |
4337
+ | `renderShallow(X, { keepTemplate: true }): … is declared by Module, not standalone` (or `by an NgModule`) | an AOT dependency list: ngtsc flattened an imported `NgModule`'s exported declarations into it, and Angular takes only standalone declarations and `NgModule`s in `imports` | name that module in `keepModules: [Module]` and it is put back whole — the message names it when the spec imports it; or drop `keepTemplate` when the spec reads TypeScript state only |
4233
4338
  | `TS2540: Cannot assign to 'X' because it is a read-only property` | a `readonly` field of an object under test | `mockValueProp(obj, 'X', value)` — on a class **getter**, `mockReadonlyProp` |
4234
4339
  | `TypeError: Cannot read properties of undefined (reading 'mockReturnValue')` | the member is an **instance field**, not on the prototype — `Router.currentNavigation` since Angular 20 | `provideAutoSpy(Router, { instanceMethodsToSpyOn: ['currentNavigation'] })` (§5) |
4235
4340
  | `throwWith` reaching the success branch first, with the previous test's value | the spy's `ReplaySubject(1)` outlived the test that filled it | upgrade; and `resetAutoSpy(spy)` in `beforeEach` when the TestBed is built in `beforeAll` |
@@ -4249,7 +4354,7 @@ packages, which a subpath export can never be.
4249
4354
  | a 30 s timeout, in a different file each run | module-level `vi.fn()` in a fixture shared by files | make the fixture a factory (§10) |
4250
4355
  | a component's `afterNextRender` state is empty | `detectChanges()` does not run the after-render phase | `await stable(fixture)` (§11) |
4251
4356
  | a green test whose only line about a call is `spy.m.calledWith(1);` | that is a **stub**, not an assertion — chai's `expect(fn).to.have.been.calledWith(x)` is the one that checks | continue the chain, or `expect(spy.m).toHaveBeenCalledWith(1)` — `no-bare-called-with` (§16) |
4252
- | `--detect-async-leaks` reporting no leaks in a suite that visibly leaks timers | `setupAutoSpy({ strayTimers: true })` cancelled them before Vitest collected | the warning on stderr says how many were taken and where the first three were scheduled; `onStrayTimers` gets every one in `timers` (§10) |
4357
+ | `--detect-async-leaks` reporting no leaks in a suite that visibly leaks timers | `setupAutoSpy({ strayTimers: true })` cancelled them before Vitest collected | the stderr report names the file, the first three timers and the test that scheduled each; `onStrayTimers` gets every one in `timers` (§10) |
4253
4358
  | `createNestUnit(X): parameter #n of Y is typed as \`Object\``(or`undefined — usually a circular import`) | the compiler emitted no runtime class for that parameter — interface, union, primitive, or a circular import | `@Inject(TOKEN)` plus a `providers` entry, or `@Optional()` to receive `undefined`; `@Inject(forwardRef(() => X))` for the import cycle |
4254
4359
  | `createNestUnit(X): Y declares n constructor parameter(s) but carries no design:paramtypes metadata` | no `emitDecoratorMetadata` (esbuild / Vite), or `reflect-metadata` loaded after the class | tsc or SWC with `decoratorMetadata`, `@Injectable()`, `reflect-metadata` first; or `@Inject(X)` on every parameter |
4255
4360
  | `createNestUnit(X): A -> B -> A is a cycle among the classes built for real` | two exposed / `useClass` classes depend on each other; the helper does not resolve `forwardRef` cycles | expose one side less, or provide it |
@@ -4257,17 +4362,22 @@ packages, which a subpath export can never be.
4257
4362
  | `createNestUnit(X).spies.get(Y): Y is in \`expose\`, so the unit got a real instance` | an exposed class is real, not a spy | read the spies of its own collaborators, or drop it from `expose` |
4258
4363
  | `expect(component.ngOnInit).toHaveBeenCalled()` fails after `fixture.detectChanges()`, or a hook's `.mockImplementation` never runs | the spy was put on the **instance**; a view calls the hook it read off the class prototype when the component was created | `vi.spyOn(Cls.prototype, 'ngOnInit')` before `TestBed.createComponent`, or assert what the hook does — `no-instance-lifecycle-spy` (§16) |
4259
4364
  | `TS2345: Argument of type 'InjectionToken<X>' is not assignable to parameter of type 'ClassType<unknown>'`, on `registerAutoSpyDefaults` | the core entry's `registerAutoSpyDefaults` takes a class only — it may not name Angular's token type | import `registerAutoSpyDefaults` (and `clearAutoSpyDefaults`) from `vitest-auto-spy/angular`, which also takes an `InjectionToken` (§5) |
4260
- | `selfReturning names 'x', which is not a spied method` | the name is not a method of the double — a typo, or a restricting `onlyMethodsToSpyOn` left it out; on a type-driven double, `then` and `constructor` are never spies; a method the constructor assigns (an ngrx `rxMethod`) is not on the prototype | fix the name or the whitelist, or list a constructor-assigned method in `instanceMethodsToSpyOn`; the value is silently never returned otherwise (§5) |
4365
+ | `returns names 'x', not a method of X` (or `which onlyMethodsToSpyOn left out`) | the name is not a method of the double — a typo, or a restricting `onlyMethodsToSpyOn` left it out; on a type-driven double, `then` and `constructor` are never spies; a method the constructor assigns (an ngrx `rxMethod`) is not on the prototype | the message names the fix: the close method it meant, adding it to `onlyMethodsToSpyOn`, or `instanceMethodsToSpyOn` for a constructor-assigned method; the value is silently never returned otherwise (§5) |
4261
4366
  | a spec green over a `useValue` fixture whose key the code never reads — `queryParams$` on an `ActivatedRoute` | `useValue` is `any`, so a renamed or misspelled key of an object literal compiles | the member's real name — `no-unknown-use-value-key` (§16), with type information; or `provideAutoSpy(X, { overrides })`, which the compiler checks |
4262
4367
  | a spec runs down the "browser" (or any flag-on) branch whatever its `useValue` says | `{ provide: IS_BROWSER, useValue: {} }` for an `InjectionToken<boolean>` — `useValue` is `any`, and an object is truthy | a value of the token's type (`useValue: false`) — `no-mistyped-use-value` (§16), with type information |
4263
- | `no request matched …` (with the requests that were made listed) | the URL, verb or predicate does not describe anything the code under test sent, or nothing subscribed | read the list in the message — it is the requests that were actually made; then fix the URL, add `{ method }`, or subscribe (§13) |
4368
+ | `expectRequest: no request matched … Made instead: …` | the URL or predicate describes nothing the code under test sent | compare with the requests listed — they are the ones actually made; then fix the URL or the predicate (§13) |
4369
+ | `expectRequest: no POST … — but GET … was made` | only the verb differs | pass `{ method }` for the verb the code sends, or fix the verb it sends (§13) |
4370
+ | `expectRequest: no GET …?page=2 — but GET …?page=3 was made; the query differs` | the same path, another query string | name the query the code sends, or match the path alone: `expectRequest('/api/products')` (§13) |
4371
+ | `expectRequest: no request matched … — nothing was requested at all` | an `httpResource()` whose `request()` returned no URL yet, or an `HttpClient` call nothing subscribed to | set the signal the resource reads, or subscribe to the call (§13) |
4264
4372
  | `this TestBed has no HttpTestingController` | `expectRequest` / `expectNoRequest` on a module that never got the testing backend | `TestBed.configureTestingModule({ providers: [...provideHttpTesting()] })` from `vitest-auto-spy/angular-http` (§13) |
4265
- | `the test ended with N unanswered request(s)` | `provideHttpTesting({ verifyOnTeardown })` found a request the spec never flushed | answer it (`await expectRequest(url).flush(body)`), assert its absence (`expectNoRequest(url)`), forgive an unsubscribed one with `{ verifyOnTeardown: { ignoreCancelled: true } }`, or `{ verifyOnTeardown: false }` (§13) |
4373
+ | `X was never answered (end of "…")` / `N requests were never answered` | `provideHttpTesting({ verifyOnTeardown })` or `enableAngularDiagnostics({ pendingRequests })` — the test left a request open; the message names its URL and the test | answer it with the line the message prints (`await expectRequest(url).flush(body)`, `controller.expectOne(url).flush(body)` under diagnostics), or assert its absence (`expectNoRequest(url)`); a cancelled one is forgiven with the `ignoreCancelled` opt-in the message names (§13) |
4266
4374
  | `injectActivatedRoute(): nothing provides ActivatedRoute here` | no `ActivatedRoute` provider in the injector it read | `provideActivatedRoute({ … })` from `vitest-auto-spy/angular-router` in `providers`; `injectActivatedRoute(fixture.debugElement.injector)` for a component-level one (§13) |
4267
4375
  | `the ActivatedRoute here is … not one provideActivatedRoute() built` | a later provider of `ActivatedRoute` won — `provideRouter()`, `RouterModule`, a `useValue`, `provideAutoSpy(ActivatedRoute)` | list `provideActivatedRoute()` last, or drop the other provider (§13) |
4268
- | `provideActivatedRoute: the installed @angular/router does not wire ActivatedRoute …` | a router major builds `ActivatedRoute` with different internal constructors | report it with the `@angular/router` version; provide the route by hand until then (§13) |
4376
+ | `provideActivatedRoute: @angular/router <version> does not wire ActivatedRoute …` | a router major builds `ActivatedRoute` with different internal constructors | report it with the `@angular/router` version; provide the route by hand until then (§13) |
4377
+ | `provideActivatedRoute({ title }): @angular/router <version> keeps the route title under a key …` | that router version stores the resolved title under a key this helper could not find | leave `title` out of the init for now, and report it with the `@angular/router` version (§13) |
4269
4378
  | `provideRouterDouble: the Router double has no …` | the code under test reached for a `Router` member the double does not cover — `isActive`, `resetConfig`, `lastSuccessfulNavigation` | the double covers `url`, `events`, `navigate`, `navigateByUrl`, `createUrlTree`, `serializeUrl`, `parseUrl`, `routerState` and `currentNavigation` / `getCurrentNavigation()`; past that, a real `provideRouter([])` with `RouterTestingHarness` (§13) |
4270
4379
  | `injectRouterDouble(): the Router here is Angular's own …` | `Router` is `providedIn: 'root'`, so a `TestBed` without the double hands out a real one | add `provideRouterDouble({ … })` from `vitest-auto-spy/angular-router` to `providers` (§13) |
4380
+ | `collectRouterEvents().expect(): the events differ at #N` (or `#N … never came`, `#N … was not expected`) | the recorded navigation events part from the expected ones there; the message prints both sequences under it | read the `Expected:` / `Recorded:` lines under it, then fix the expectation or the navigation the test drives (§13) |
4271
4381
 
4272
4382
  **Seven of these are library defects, not spec ones.** On anything below 3.8.0, upgrade and change
4273
4383
  nothing in the spec: `of(double)` never emitting; `accessorSpies.setters.X` still `undefined` after
@@ -4474,8 +4584,12 @@ Full reference: <https://asdalexey.github.io/vitest-auto-spy/utilities/cli>.
4474
4584
  Exit codes, as the commands implement them: **0** is "ran, nothing to report"; **1** is "ran, and
4475
4585
  here is the finding" — `doctor` with an error, `init --check` with a stale block, `codemod` with any
4476
4586
  note, `perf --gate` over budget; **2** is "there was nothing to judge" — no command, an unknown
4477
- command, an unknown flag, an unreadable `--only` / `--from` value, a path matching no file, and a
4478
- `perf` run that measured nothing (including a red suite, which the gate will not judge at all).
4587
+ command, an unknown flag, a flag value it cannot use (an `--min-severity` word it does not know, a
4588
+ number flag given a word or a negative number, a value flag with nothing after it, an `--ignore` id
4589
+ `doctor` has no check for), an unreadable `--only` / `--from` value, a path matching no file, and a
4590
+ `perf` run that measured nothing (including a red suite, which the gate will not judge at all). An
4591
+ invalid flag value stops the command before it does anything: the message says so (`Nothing ran.`),
4592
+ and a typo in a command, a flag, `--only` or `--ignore` gets a `Did you mean`.
4479
4593
 
4480
4594
  **Reading the output from a script: `--format json`**, on `doctor` and on `perf`. One JSON document
4481
4595
  on stdout — `schema`, `exitCode`, `tally` (`errors`, `warnings`, `notes`), every finding with
@@ -4483,13 +4597,15 @@ on stdout — `schema`, `exitCode`, `tally` (`errors`, `warnings`, `notes`), eve
4483
4597
  per candidate, `outcome` one of `confirmed`, `not reproduced`, `unconfirmed`, `single reading`,
4484
4598
  `over budget`). Parse that rather than the text: the text is wrapped to the terminal (80 columns in a
4485
4599
  pipe), groups one cause found in many files into one block, and ends in a tally line that starts
4486
- with `N errors, N warnings, N notes`. `--format markdown` renders the same document as tables — for a
4600
+ with `N errors, N warnings, N notes`. In the text, every finding ends in a `Docs:` line — its own
4601
+ section of the CLI page (`utilities/cli#<check-id>`), or of the codemod page for a codemod note. `--format markdown` renders the same document as tables — for a
4487
4602
  merge request note or a job summary, not for parsing.
4488
4603
 
4489
4604
  **`doctor --ignore <check,…>`** leaves the named checks out of the report, the tally, the exit code
4490
4605
  and the `--code-quality` file. Use it only for a finding the repository has answered in a way
4491
4606
  `doctor` cannot see — `no-agent-instructions` in CI where the instruction files are kept out of git,
4492
- `angular-build-splitting-off` under a patched builder. `--min-severity` hides findings from the text
4607
+ `angular-build-splitting-off` under a patched builder. An id `doctor` has no check for is exit 2,
4608
+ with the closest id suggested. `--min-severity` hides findings from the text
4493
4609
  only; `--ignore` removes them.
4494
4610
 
4495
4611
  ### If you were asked why a suite is slow
@@ -4514,8 +4630,9 @@ assumed safe — do not treat an undecided file as a candidate, and do not add p
4514
4630
  yourself; a false positive breaks someone's suite on `document is not defined`. When `import`
4515
4631
  dominates, `perf-import` names specs that reach their subject through a barrel (`index`/`public-api`
4516
4632
  re-exporting a whole directory). When `environment` + `setup` + `prepare` together dominate,
4517
- `perf-isolation` suggests `test.isolate: false` and links to this package's own memory measurements
4518
- of that trade rather than restating them — read that page before recommending the flag, since it
4633
+ `perf-isolation` suggests `isolate: false` in the config it names, kept only if peak memory stays
4634
+ acceptable; its `Docs:` line leads to the check's section of the CLI page, which links this
4635
+ package's own memory measurements of that trade — read them before recommending the flag, since it
4519
4636
  raises peak memory.
4520
4637
 
4521
4638
  Two more findings are about settings rather than files. `perf-environment-engine` fires when
@@ -4523,11 +4640,14 @@ Two more findings are about settings rather than files. `perf-environment-engine
4523
4640
  mentions `happy-dom`: measured on this package's own 117-file Angular suite, `happy-dom` is 23.2 s of
4524
4641
  user CPU against jsdom's 26.5 s, and on a spec that builds a DOM and does nothing else the gap is
4525
4642
  253 ms against 119 ms per file. It is a swap, not a flag — `happy-dom` implements less of the
4526
- platform — so take one project at a time. `perf-workers` fires on a run over a minute of summed CPU
4643
+ platform — so take one project at a time; the finding names the config that sets `jsdom`, and the
4644
+ numbers stay in the docs section. `perf-workers` fires on a run over a minute of summed CPU
4527
4645
  that declares no `maxWorkers`, and it is the one finding here about **memory**: one worker per core
4528
4646
  is the default, resident memory measured at 1.42 GB plus ~155 MB per worker, and a cap of four costs
4529
- about 2.8 % of wall clock. Do not quote a worker count as universally right — it is a property of
4530
- the machine.
4647
+ about 2.8 % of wall clock. The finding itself counts this machine's cores
4648
+ (`os.availableParallelism()`) and suggests half of them as `maxWorkers` in the config it names; the
4649
+ figures above are in the docs section, not in the message. Do not quote a worker count as
4650
+ universally right — it is a property of the machine.
4531
4651
 
4532
4652
  **A bare `vitest run` is not every repository's suite, and `perf` refuses to pretend otherwise.**
4533
4653
  Where the suite is assembled by something else — an Angular builder, an Nx target, a script that