vitest-auto-spy 5.31.0 → 5.32.1
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.
- package/AGENTS.md +208 -82
- package/CHANGELOG.md +306 -1
- package/README.md +55 -15
- package/dist/angular-diagnostics.js +49 -42
- package/dist/angular-doubles.d.ts +10 -4
- package/dist/angular-doubles.js +17 -15
- package/dist/angular-http.js +64 -31
- package/dist/angular-matchers.js +30 -8
- package/dist/angular-router.d.ts +1 -1
- package/dist/angular-router.js +67 -35
- package/dist/angular.d.ts +6 -6
- package/dist/angular.js +663 -266
- package/dist/bun-angular.d.ts +5 -5
- package/dist/bun-angular.js +156 -68
- package/dist/bun.d.ts +7 -6
- package/dist/bun.js +12 -11
- package/dist/chunk-24JTFFQL.js +13 -0
- package/dist/{chunk-L6VPMLRJ.js → chunk-2QEM4WP7.js} +3 -2
- package/dist/{chunk-NFFBGS7E.js → chunk-6JLBZHQ3.js} +2 -2
- package/dist/{chunk-TBT4J2AX.js → chunk-6JQ7AFWM.js} +76 -41
- package/dist/{chunk-64KOOW3H.js → chunk-72H6HNDI.js} +109 -47
- package/dist/{chunk-33JL4DF3.js → chunk-7WM3A4IB.js} +4 -4
- package/dist/chunk-CLQUJX6E.js +52 -0
- package/dist/chunk-DNXCZOYY.js +41 -0
- package/dist/{chunk-TZ7DRSRD.js → chunk-ENCEEH23.js} +1 -1
- package/dist/{chunk-ROSQKG22.js → chunk-F2KBJC7P.js} +28 -2
- package/dist/chunk-GSMWYZ5A.js +63 -0
- package/dist/chunk-JOPCMFYN.js +66 -0
- package/dist/{chunk-5FM2TZKG.js → chunk-JR55747M.js} +27 -16
- package/dist/{chunk-3MRCVCRZ.js → chunk-LAPHUX5G.js} +3 -3
- package/dist/chunk-M3NTXLZG.js +27 -0
- package/dist/{chunk-F4SLC724.js → chunk-TMFV4DXT.js} +219 -127
- package/dist/chunk-W5EXSI22.js +26 -0
- package/dist/{chunk-7TGT2436.js → chunk-XNXIHKVM.js} +32 -4
- package/dist/{chunk-6RP37RWX.js → chunk-XUPBHHJD.js} +1 -1
- package/dist/{chunk-VSWA6PQW.js → chunk-YDPOBKST.js} +72 -26
- package/dist/{chunk-FZMWANZV.js → chunk-YUGSITWJ.js} +3 -3
- package/dist/cli.js +564 -287
- package/dist/console.d.ts +1 -1
- package/dist/console.js +9 -8
- package/dist/diagnostics.js +30 -3
- package/dist/dom-stubs.d.ts +95 -2
- package/dist/dom-stubs.js +214 -75
- package/dist/eslint-plugin.cjs +474 -227
- package/dist/{expect-emission-DRoCm2M1.d.ts → expect-emission-wUaoy0NW.d.ts} +9 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +567 -240
- package/dist/jasmine-compat.d.ts +1 -1
- package/dist/jasmine-compat.js +3 -3
- package/dist/jasmine.d.ts +2 -2
- package/dist/jasmine.js +66 -45
- package/dist/nestjs.d.ts +2 -2
- package/dist/nestjs.js +13 -12
- package/dist/node.cjs +613 -272
- package/dist/node.d.ts +4 -4
- package/dist/node.js +544 -226
- package/dist/observer-spy.js +17 -14
- package/dist/{package-identity-CCuANjTk.d.ts → package-identity-5pFBRWW9.d.ts} +2 -2
- package/dist/{prop-mock-Vbd3FuyF.d.ts → prop-mock-DUcl1uMH.d.ts} +3 -1
- package/dist/react.d.ts +4 -4
- package/dist/react.js +567 -240
- package/dist/rstest.d.ts +4 -4
- package/dist/rstest.js +12 -11
- package/dist/rxjs.d.ts +2 -2
- package/dist/rxjs.js +16 -11
- package/dist/setup.d.ts +192 -162
- package/dist/setup.js +879 -273
- package/dist/shared-state.js +67 -50
- package/dist/signal-forms.js +24 -7
- package/dist/svelte.d.ts +4 -4
- package/dist/svelte.js +567 -240
- package/dist/{track-injections-bhctaGyo.d.ts → track-injections-DyqkWQmT.d.ts} +1 -1
- package/dist/{track-signal-runs-DDLi1--g.d.ts → track-signal-runs-BVbIBVS7.d.ts} +44 -4
- package/dist/{types-IEQUztPB.d.ts → types-BVKEkhWj.d.ts} +5 -0
- package/dist/vue.d.ts +5 -5
- package/dist/vue.js +568 -241
- package/dist/zone.d.ts +0 -30
- package/dist/zone.js +15 -3
- package/package.json +1 -1
- package/skills/vitest-auto-spy/SKILL.md +133 -121
- package/dist/chunk-2O3N4OPX.js +0 -52
- package/dist/chunk-2XXGISYD.js +0 -48
- package/dist/chunk-3VXZRDH3.js +0 -12
- package/dist/chunk-SOROJD3W.js +0 -9
- package/dist/chunk-V76LFJW4.js +0 -41
- package/dist/chunk-YZSBSAYZ.js +0 -31
package/AGENTS.md
CHANGED
|
@@ -16,7 +16,9 @@ Setting this up for a team? `npx vitest-auto-spy init` writes a pointer to this
|
|
|
16
16
|
instruction files this repository's agents actually read — `AGENTS.md` (Codex, Cursor, Copilot and
|
|
17
17
|
most of the field), `CLAUDE.md` (Claude Code) and `GEMINI.md` (Gemini CLI), plus the glob-scoped
|
|
18
18
|
rule file of any tool whose own directory already exists. `--check` is the CI form; `--only
|
|
19
|
-
CLAUDE.md,.claude` limits it to the files a repository keeps untracked.
|
|
19
|
+
CLAUDE.md,.claude` limits it to the files a repository keeps untracked. A hand-made copy of the
|
|
20
|
+
package's skill in `.claude/skills/vitest-auto-spy/` is reported `stale` (and fails `--check`): delete
|
|
21
|
+
it and re-run `init`. Full table:
|
|
20
22
|
<https://asdalexey.github.io/vitest-auto-spy/agents>.
|
|
21
23
|
|
|
22
24
|
| Resource | Where |
|
|
@@ -54,22 +56,22 @@ adapter installed and spies fail at runtime.
|
|
|
54
56
|
|
|
55
57
|
Add-ons, orthogonal to the runner:
|
|
56
58
|
|
|
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)
|
|
59
|
+
| Add-on | Import | Needed for |
|
|
60
|
+
| ------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
61
|
+
| Observable spies | `import 'vitest-auto-spy/rxjs'` | `nextWith` & friends. **Side-effect import, once**, and in a file the `tsconfig` includes (§4) |
|
|
62
|
+
| 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 |
|
|
63
|
+
| Console spies | `vitest-auto-spy/console` | silent typed spies over the global `console` — `installConsoleSpies()` per test, `restoreConsole()` after |
|
|
64
|
+
| 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** |
|
|
65
|
+
| Run diagnostics | `vitest-auto-spy/diagnostics` | `compareTestRuns`, `summarizeTestRun`, `formatTestRunComparison`, `diffByField`. **Moved off the root in 4.0.0** |
|
|
66
|
+
| Angular HTTP | `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest` — `httpResource()` / `HttpClient` (§13). Optional `@angular/common` peer, this entry only |
|
|
67
|
+
| 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 |
|
|
68
|
+
| 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 |
|
|
69
|
+
| 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** |
|
|
70
|
+
| 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** |
|
|
71
|
+
| 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+ |
|
|
72
|
+
| Setup helpers | `vitest-auto-spy/setup` | `setupAutoSpy()`, `setupFakeTimers()`, `blockNetwork()`, `stubResponse()`; the entry imports Vitest, so it is not for `bun test` |
|
|
73
|
+
| Zone patch | `import 'vitest-auto-spy/zone'` | `fakeAsync` / `waitForAsync` on Vitest (§14) |
|
|
74
|
+
| jasmine compat | `vitest-auto-spy/jasmine` | `.and` / `.calls` / `.withArgs`, the `jasmine` namespace (§20) |
|
|
73
75
|
|
|
74
76
|
`vitest-auto-spy/jasmine` is Vitest-only, because it registers the Vitest adapter. On `bun test` and
|
|
75
77
|
`node --test` call `enableJasmineCompat()` from `vitest-auto-spy/jasmine-compat` instead.
|
|
@@ -146,7 +148,10 @@ and is only ever asserted on — `createAutoMock`. Handed to the function under
|
|
|
146
148
|
it has to satisfy `T` at the call site _and_ expose the spy helpers at the assertion, and
|
|
147
149
|
`autoMocked<T>()` is that intersection — otherwise every call site needs an `asInstance()` and the
|
|
148
150
|
noise scales with the number of them. Both take the same second argument (`returns`, `name`, `strict`,
|
|
149
|
-
`observablePropsToSpyOn`).
|
|
151
|
+
`observablePropsToSpyOn`). A `let` assigned in `beforeEach` is declared `AutoMocked<T>`,
|
|
152
|
+
not with the intersection spelt out. `returns` also takes an optional method written as a property,
|
|
153
|
+
`getMinZoom?: (() => number) | undefined` — the way Leaflet and other third-party typings declare
|
|
154
|
+
them.
|
|
150
155
|
|
|
151
156
|
`createMock<T>()` is the one to reach for on data shapes — it returns a plain `T`, so it satisfies a
|
|
152
157
|
`no-type-assertion` lint rule without an `eslint-disable` on every fixture. `createMock<T>(undefined)`
|
|
@@ -654,7 +659,9 @@ order**, a `Date` by its time, a `RegExp` by its source and flags, an `Error` by
|
|
|
654
659
|
`message` plus its own enumerable fields, a function by identity, and symbol-keyed properties
|
|
655
660
|
participate like string ones. Cycles are handled, so a component graph with a back-edge is a legal
|
|
656
661
|
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.
|
|
662
|
+
`Any<Number>` rather than as the object it serialises to. With one config set up, its first line
|
|
663
|
+
names the first argument that differs — `argument 2: expected 'eu', got 'us'` — and it names the
|
|
664
|
+
class only on a strict double (`UserService.getName`), the method alone otherwise.
|
|
658
665
|
|
|
659
666
|
Each `calledWith(...)` / `mustBeCalledWith(...)` call hands back **its own** handle, so a chain kept
|
|
660
667
|
in a variable stays attached to the arguments it was written with:
|
|
@@ -733,8 +740,11 @@ one rather than disappearing.
|
|
|
733
740
|
|
|
734
741
|
`nextWithValues` on an observable **property** builds a new stream, which a subscriber that already
|
|
735
742
|
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
|
|
737
|
-
|
|
743
|
+
initial state. That case is reported rather than passing silently —
|
|
744
|
+
`Feed.items$.nextWithValues() ran after something subscribed to Feed.items$` on a strict double, the
|
|
745
|
+
member name alone otherwise — once per property as a warning, and at every such call as a throw under
|
|
746
|
+
`setupAutoSpy({ misconfiguration: 'throw' })`. `nextWith` pushes into the subject the current
|
|
747
|
+
subscriber is on and is the one to reach for mid-test.
|
|
738
748
|
|
|
739
749
|
What that does **not** change: `vi.clearAllMocks()` and `clearMocks: true` still cannot reach it,
|
|
740
750
|
for the same reason they cannot reach a `calledWith` chain — that state lives in this library's
|
|
@@ -819,7 +829,8 @@ be told apart from an instance field and stays silent.
|
|
|
819
829
|
Only `onlyMethodsToSpyOn` warns, because only a restricting list can be silently destructive — a
|
|
820
830
|
misspelling there leaves the real method unspied, and the code under test then calls something that
|
|
821
831
|
is not there:
|
|
822
|
-
`[vitest-auto-spy] createSpyFromClass(
|
|
832
|
+
`[vitest-auto-spy] createSpyFromClass(CartService): onlyMethodsToSpyOn names 'lod' (did you mean 'load'?), not a method of CartService.`
|
|
833
|
+
A name with nothing close gets `instanceMethodsToSpyOn: ['x']` as the fix instead.
|
|
823
834
|
|
|
824
835
|
Also true, and worth not re-deriving:
|
|
825
836
|
|
|
@@ -959,8 +970,10 @@ users.currentTenant(); // throws here, not four frames later inside the componen
|
|
|
959
970
|
```
|
|
960
971
|
|
|
961
972
|
```
|
|
962
|
-
[vitest-auto-spy]
|
|
963
|
-
Called
|
|
973
|
+
[vitest-auto-spy] Cart.checkout(1, 'now') was called; this strict double has nothing configured for it.
|
|
974
|
+
Called from src/app/cart.component.ts:41:12
|
|
975
|
+
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.
|
|
976
|
+
Docs: https://asdalexey.github.io/vitest-auto-spy/core/strict-mode#the-message
|
|
964
977
|
```
|
|
965
978
|
|
|
966
979
|
Also on `createAutoMock`, `provideAutoSpy`, and suite-wide as `setupAutoSpy({ strict: true })` — which
|
|
@@ -992,7 +1005,9 @@ Precedence, first one set wins: the double's `onUnstubbedCall` → the double's
|
|
|
992
1005
|
- **A throw something swallowed still fails the test.** Under `setupAutoSpy({ strict: true })` every
|
|
993
1006
|
strict throw is recorded, and one that a `try`/`catch` in the code under test or an RxJS error with
|
|
994
1007
|
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`).
|
|
1008
|
+
(`swallowedStrictCalls`, `'throw'` by default with `strict: true`). The report quotes each call
|
|
1009
|
+
with its arguments and the line that made it, and says what swallowed it — a `catch` in the code
|
|
1010
|
+
under test, or an RxJS subscriber with no error callback. Provoking one on purpose:
|
|
996
1011
|
`expect(() => cart.total()).toThrow(…)` then `expect(takeStrictViolations()).toHaveLength(1)`.
|
|
997
1012
|
- **It does not reach** accessor spies, observable-property spies, `mockDeep` nodes, `console-spy`,
|
|
998
1013
|
`mockResourceProp`'s `reload` or a standalone `createFunctionSpy`. A strict double still answers
|
|
@@ -1012,7 +1027,7 @@ member, kind, count })` — suite-wide or per double, same precedence as `onUnst
|
|
|
1012
1027
|
`ngAfter…` hooks answer `undefined` on a strict double: Angular calls `ngOnDestroy` itself on every
|
|
1013
1028
|
provided value at teardown (a `createAutoMock` / `provideAutoSpyForToken` double always has one), and
|
|
1014
1029
|
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
|
|
1030
|
+
- A class instance or DOM node in the call the message quotes prints as `[ClassName]`, data up to 200
|
|
1016
1031
|
characters per argument; `provideAutoSpyForToken` names the token (`createAutoMock` takes `name`).
|
|
1017
1032
|
- Use `onUnstubbedCall` to survey a suite before turning the throw on — it is handed
|
|
1018
1033
|
`{ className, method, args }` and whatever it returns is what the call answers, so a handler that
|
|
@@ -1151,6 +1166,12 @@ resetAutoSpy(service); // calls AND configuration (calledWith / resolveWith / mo
|
|
|
1151
1166
|
Both cover method spies **and** accessor spies, on `createSpyFromClass` spies and `createAutoMock`
|
|
1152
1167
|
proxies alike. Reach for these instead of looping over methods calling `mockClear` by hand.
|
|
1153
1168
|
|
|
1169
|
+
**`mockReset()` on one method keeps its `calledWith` chains** — they live in the library's state, where
|
|
1170
|
+
the runner's reset cannot reach — so `spy.isFeatureOn.mockReset(); spy.isFeatureOn.mockReturnValue(true)`
|
|
1171
|
+
still reports that `mockReturnValue()` replaced a configured chain (and throws under `strict`). To
|
|
1172
|
+
answer one value for every call after a `beforeEach` configured `calledWith(…)`, drop the chain with
|
|
1173
|
+
`resetAutoSpy(spy.isFeatureOn)` first; the report names it.
|
|
1174
|
+
|
|
1154
1175
|
**`resetAutoSpy` is `vi.resetAllMocks()` for one double**, and two things it used to leave behind now
|
|
1155
1176
|
go with the rest: a pending `mockReturnValueOnce` / `mockResolvedValueOnce` queue (it used to answer
|
|
1156
1177
|
the first call _after_ the reset, in a later test) and an accessor spy's configuration (a
|
|
@@ -1210,6 +1231,15 @@ wait then runs to the runner's own test timeout, which is the trade. The source
|
|
|
1210
1231
|
`subscribe` takes a bare callback) and hand-rolled subscribables all work — and every helper infers
|
|
1211
1232
|
the emitted type, so `expectEmission(of(1))` is a `Promise<number>`.
|
|
1212
1233
|
|
|
1234
|
+
**A `void` stream calls its listener with one argument, `undefined`.** `output<void>().emit()` and
|
|
1235
|
+
`Subject<void>.next()` both do, so a `vi.fn()` subscribed to one records `[undefined]`, and
|
|
1236
|
+
`expect(listener).toHaveBeenCalledExactlyOnceWith()` fails on `[] vs [undefined]` — the form a
|
|
1237
|
+
`prefer-called-with` autofix used to write. The assertion is the emission itself:
|
|
1238
|
+
`await expect(expectEmission(component.closed)).resolves.toBeUndefined()`, or
|
|
1239
|
+
`subscribeSpyTo(subject$).getValuesLength()` to count. A listener kept anyway is typed
|
|
1240
|
+
`vi.fn<() => void>()` (`(value: void) => void` trips `no-invalid-void-type`) and asserted
|
|
1241
|
+
`toHaveBeenCalledExactlyOnceWith(undefined)`.
|
|
1242
|
+
|
|
1213
1243
|
`expectCompletion` is the one to reach for on a stream whose value is not the point — a save, a
|
|
1214
1244
|
purge, an `Observable<void>`, a `Subject` a teardown closes. `firstValueFrom` rejects such a stream
|
|
1215
1245
|
with rxjs's `EmptyError`, and the workaround people arrive at,
|
|
@@ -1512,7 +1542,8 @@ setupAutoSpy({ strayTimers: true }); // wrap the schedulers, sweep the survivors
|
|
|
1512
1542
|
The pieces are exported too — `trackStrayTimers()` (idempotent, returns the undo),
|
|
1513
1543
|
`cancelStrayTimers()` (returns how many it cancelled), `countStrayTimers()` and
|
|
1514
1544
|
`describeStrayTimers()` (each pending timer's `kind`, the `delay` of a timeout or interval, the spec
|
|
1515
|
-
`file` that scheduled it
|
|
1545
|
+
`file` that scheduled it, up to five `frames`, and `test` — the `suite > test` that scheduled it — or
|
|
1546
|
+
`outsideTest: 'import' | 'hook'` when no test was running), all from `vitest-auto-spy/setup`. Use
|
|
1516
1547
|
`expect(countStrayTimers()).toBe(0)` in an `afterEach` to make a leak fail rather than be tidied
|
|
1517
1548
|
away, or take the per-file report from the sweep itself — `timers` carries the same origins, so the
|
|
1518
1549
|
failure diff names the file and the scheduling call:
|
|
@@ -1524,10 +1555,11 @@ setupAutoSpy({ strayTimers: true, onStrayTimers: ({ timers }) => expect(timers).
|
|
|
1524
1555
|
`frames` starts below the tracking wrapper and is up to forty frames deep, so a zone or an rxjs
|
|
1525
1556
|
scheduler in between does not hide the caller: project frames come first; when the call came from
|
|
1526
1557
|
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
|
|
1528
|
-
`
|
|
1529
|
-
same `kind`, `delay`, `file` and `frames[0]` —
|
|
1530
|
-
under a slow coverage run actionable the first
|
|
1558
|
+
from `dist`. `onStrayTimers: 'throw'` fails the file with a message that names the file and lists
|
|
1559
|
+
every stray as `setTimeout 300 ms, scheduled in "<test>" at <frames[0]>`, then the one fix; a handler
|
|
1560
|
+
that prints its own message should print the same `kind`, `delay`, `file`, `test` and `frames[0]` —
|
|
1561
|
+
they are what makes a report that only reproduces under a slow coverage run actionable the first
|
|
1562
|
+
time.
|
|
1531
1563
|
|
|
1532
1564
|
A stray whose `file` is not the file that failed was scheduled after the previous file's sweep —
|
|
1533
1565
|
the previous file is the one to fix.
|
|
@@ -1546,8 +1578,9 @@ arrive at the same timer from opposite ends and the quiet one wins: the sweep ca
|
|
|
1546
1578
|
Vitest collects its leaks afterwards, and a cancelled timeout is no longer referenced, so the run
|
|
1547
1579
|
reports **no leaks** for a file that leaks. Cancelling is still the right default — a callback firing
|
|
1548
1580
|
during a later file is the more expensive failure — so when both are on and no `onStrayTimers` is
|
|
1549
|
-
given, the sweep prints
|
|
1550
|
-
|
|
1581
|
+
given, the sweep prints to stderr the report `'throw'` fails with — the file, the first three timers
|
|
1582
|
+
and the test that scheduled each — closed by one sentence saying they are missing from Vitest's
|
|
1583
|
+
"Async Leaks" report; `onStrayTimers` gets all of them. Vitest's own report, with `strayTimers` off, points
|
|
1551
1584
|
its code frame at the `setTimeout` in the spec, because the library's own scheduler wrappers go
|
|
1552
1585
|
through `vi.defineHelper` and are dropped from the stack.
|
|
1553
1586
|
|
|
@@ -1662,14 +1695,17 @@ initialisation — and the `afterAll` takes off everything added since. The piec
|
|
|
1662
1695
|
`trackStrayListeners()` (idempotent, returns the undo), `baselineStrayListeners()`,
|
|
1663
1696
|
`removeStrayListeners()` (returns how many), `countStrayListeners()` (throws before
|
|
1664
1697
|
`trackStrayListeners()` has run, like the timer counter) and `describeStrayListeners()` — each
|
|
1665
|
-
stray's target, type, spec file
|
|
1666
|
-
receives when a file should fail instead:
|
|
1667
|
-
|
|
1698
|
+
stray's target, type, spec file, up to five frames and the same `test` / `outsideTest` as a timer,
|
|
1699
|
+
the list `onStrayListeners({ removed, listeners })` receives when a file should fail instead:
|
|
1700
|
+
`onStrayListeners: ({ removed }) => expect(removed).toBe(0)`, or `onStrayListeners: 'throw'`, whose
|
|
1701
|
+
message names the file and lists each as `keydown on document, added in "<test>" at <frames[0]>`.
|
|
1668
1702
|
A listener has `type` and `target` where a timer has `kind` and `delay` — a handler that prints its own
|
|
1669
|
-
message prints those
|
|
1703
|
+
message prints those fields instead.
|
|
1670
1704
|
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
|
|
1672
|
-
|
|
1705
|
+
a handler or `'throw'` alike; two failures surface as one `AggregateError`
|
|
1706
|
+
(`2 file-end checks failed when a.spec.ts ended:`, each report a numbered block with its `Docs:`
|
|
1707
|
+
line), so a throwing report never leaves a timer uncancelled. The stray-console file report runs in
|
|
1708
|
+
the same sweep.
|
|
1673
1709
|
Under jsdom a listener registered with `{ once: true }` that already fired stays counted until
|
|
1674
1710
|
something removes it: the wrapper cannot observe the firing without breaking identity-based
|
|
1675
1711
|
`removeEventListener` from the code under test, and removing an already-fired listener is a no-op
|
|
@@ -1783,7 +1819,7 @@ Every channel is closed by default; the object is for narrowing it:
|
|
|
1783
1819
|
|
|
1784
1820
|
| option | default | what it does |
|
|
1785
1821
|
| -------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
1786
|
-
| `fetch` | `true` | `fetch` rejects, naming
|
|
1822
|
+
| `fetch` | `true` | `fetch` rejects, naming the method, the URL and the test, and the `stubResponse` line that answers it |
|
|
1787
1823
|
| `xhr` | `'reject'` | `'reject'` fails the request (`status` 0, an `error` event); `'empty'` answers 200 with an empty body; `false` leaves XHR alone |
|
|
1788
1824
|
| `beacon` | `true` | `navigator.sendBeacon` answers `false` — only where the environment has one |
|
|
1789
1825
|
|
|
@@ -2134,17 +2170,21 @@ setupAutoSpy({ strayConsole: 'throw' }); // default 'off'; 'warn' prints the sam
|
|
|
2134
2170
|
Any call to a console method that writes (`log`, `info`, `warn`, `error`, `debug`, `trace`, `table`,
|
|
2135
2171
|
`dir`, `dirxml`, `timeLog`, `timeEnd`, `count`; `group` with a label; a falsy `assert`) made during a
|
|
2136
2172
|
test that nothing absorbed fails **that test**, quoting the method, the first three lines and the first
|
|
2137
|
-
frame outside `node_modules
|
|
2173
|
+
frame outside `node_modules`, and naming the spy that absorbs it:
|
|
2138
2174
|
|
|
2139
2175
|
```
|
|
2140
|
-
[vitest-auto-spy] "CartService > reports a failed load" wrote to
|
|
2176
|
+
[vitest-auto-spy] "CartService > reports a failed load" wrote to console.error 1 time and nothing absorbed it:
|
|
2141
2177
|
- console.error: Error: load failed
|
|
2142
2178
|
at CartService.load (src/app/cart.service.ts:41:15)
|
|
2179
|
+
Absorb what the test expects — installConsoleSpies() in a beforeEach, then assert consoleErrorSpy — or fix the code if the output is a defect.
|
|
2180
|
+
Docs: https://asdalexey.github.io/vitest-auto-spy/utilities/setup#_16-console-output-nothing-absorbed
|
|
2143
2181
|
```
|
|
2144
2182
|
|
|
2145
2183
|
- **Absorbed** means the call never reached the console: a `/console` spy installed for the test or
|
|
2146
2184
|
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
|
|
2185
|
+
through. A bare `vi.spyOn(console, m)` calls through and **counts** — the report says so and names
|
|
2186
|
+
`.mockImplementation(() => undefined)`; a method with no `/console` spy (`table`, `dir`) gets a
|
|
2187
|
+
silent `vi.spyOn` instead.
|
|
2148
2188
|
- **This library's own `'warn'`-grade reports are not stray console output.** `guardGlobals`,
|
|
2149
2189
|
`prototypePollution`, `unconfiguredReads`, `swallowedStrictCalls`, `misconfiguration`, the
|
|
2150
2190
|
duplicate-copy report, the skipped-teardown net and the `test.concurrent` notice write past the
|
|
@@ -2153,15 +2193,22 @@ frame outside `node_modules`:
|
|
|
2153
2193
|
channel checks that the guard's own wrapper is the installed method and steps aside when anything
|
|
2154
2194
|
sits on top of it.
|
|
2155
2195
|
- Output **outside any test** — collection/import, `beforeAll` / `afterAll`, a callback after its
|
|
2156
|
-
test ended — fails the **file** in
|
|
2196
|
+
test ended — fails the **file** at its end, in the same sweep as the timer and listener reports. The
|
|
2197
|
+
report names the phase — `while the file was being imported`, `in a beforeAll`,
|
|
2198
|
+
`after a test had ended`, or `outside any test` when they mix — and gives that phase's advice: for
|
|
2199
|
+
import-time output, the module and the line to fix. A recognised line (`NG0912`, any `NGxxxx`)
|
|
2200
|
+
adds a `Likely cause:` block. It always names the file that wrote the output: when that file's end
|
|
2201
|
+
never ran (its own `afterAll` threw), the next file reports it and says
|
|
2202
|
+
`It is reported at the end of b.spec.ts: the file-end check of a.spec.ts did not run.`
|
|
2157
2203
|
- Every console method a test replaced is put back after it; one a file replaced, after the file.
|
|
2158
2204
|
- Under the guard, **importing `vitest-auto-spy/console` installs nothing** — call
|
|
2159
2205
|
`installConsoleSpies()` in a `beforeEach` (or at the top of the file). Spies an import installed
|
|
2160
2206
|
before the guard armed are taken off.
|
|
2161
|
-
- `strayConsole: { allow: ['…', /…/] }` is the last resort, for
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2207
|
+
- `strayConsole: { allow: ['…', /…/] }` is the last resort, for a line from a dependency no spec can
|
|
2208
|
+
reach — the import-time report offers it, with the pattern, only when the quoted frame is inside
|
|
2209
|
+
`node_modules`. Output written to `process.stdout` / `process.stderr` directly, and jsdom's own
|
|
2210
|
+
virtual console, is not seen. `NG0912` is not noise to allow: it means two copies of one component
|
|
2211
|
+
in the bundle, fixed by importing the component from one place.
|
|
2165
2212
|
- The wrapper forwards every call unchanged, so Vitest's attribution and `onConsoleLog` still work.
|
|
2166
2213
|
`guardStrayConsole(reaction)` registers the same guard on its own.
|
|
2167
2214
|
|
|
@@ -2179,12 +2226,13 @@ an unconfigured call returns — a semantic switch, not a grade; the name was ta
|
|
|
2179
2226
|
`blockNetwork` (changes the code under test), `restoreMocks` (drops `beforeAll` spies), failing on
|
|
2180
2227
|
stray-timer counts (the sweep fails the file from `afterAll`, and a callback scheduled after the
|
|
2181
2228
|
previous file's sweep is charged to the next — opt in with `onStrayTimers: 'throw'`, whose message
|
|
2182
|
-
names each one
|
|
2229
|
+
names the file and the test that scheduled each one, or `onStrayTimers: ({ timers }) => expect(timers).toEqual([])`), and
|
|
2183
2230
|
`enableAngularDiagnostics()`, which lives in `/angular/diagnostics` — call it in the same setup file as the Angular half of strict.
|
|
2184
2231
|
|
|
2185
2232
|
`misconfiguration: 'throw'` on its own makes the library's misuse reports — an `onlyMethodsToSpyOn`
|
|
2186
2233
|
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
|
|
2234
|
+
instance, a `jasmine.DEFAULT_TIMEOUT_INTERVAL` write, `providedMethodNames`, a `nextWithValues()` on an
|
|
2235
|
+
observable property something already subscribed to — throw at the call site,
|
|
2188
2236
|
every occurrence. The grade is process-wide and released after the file. The printed grade of the
|
|
2189
2237
|
`injectSpy` warning is de-duplicated per token **per spec file**, no longer per worker.
|
|
2190
2238
|
|
|
@@ -2411,6 +2459,37 @@ once and leaves a working storage alone; this one replaces whatever is there, fo
|
|
|
2411
2459
|
installs even in a `node` environment because the spec asked for it. Named-property access
|
|
2412
2460
|
(`localStorage.token`, `Object.keys(localStorage)`) does not see the items — read through the API.
|
|
2413
2461
|
|
|
2462
|
+
### Animation frames and element boxes
|
|
2463
|
+
|
|
2464
|
+
Do not assign `window.requestAnimationFrame` by hand, and do not stub `getBoundingClientRect` with a
|
|
2465
|
+
partial literal. Both have a stub on `/dom-stubs`:
|
|
2466
|
+
|
|
2467
|
+
```ts
|
|
2468
|
+
import { stubAnimationFrame, stubElementRect } from 'vitest-auto-spy/dom-stubs';
|
|
2469
|
+
|
|
2470
|
+
beforeEach(() => {
|
|
2471
|
+
stubAnimationFrame(); // 'immediate': the callback runs before requestAnimationFrame returns
|
|
2472
|
+
stubElementRect(container, { width: 800, height: 600 }); // x / y default to 0
|
|
2473
|
+
});
|
|
2474
|
+
|
|
2475
|
+
it('scrolls after the next frame', () => {
|
|
2476
|
+
const frames = stubAnimationFrame({ mode: 'queued' });
|
|
2477
|
+
|
|
2478
|
+
list.scrollToSelected();
|
|
2479
|
+
expect(frames.pending).toBe(1);
|
|
2480
|
+
|
|
2481
|
+
frames.flush(); // one frame; the timestamp defaults to performance.now()
|
|
2482
|
+
expect(list.scrolled).toBe(true);
|
|
2483
|
+
});
|
|
2484
|
+
```
|
|
2485
|
+
|
|
2486
|
+
`stubAnimationFrame` stubs `cancelAnimationFrame` too, and hands both spies back as
|
|
2487
|
+
`frames.requestAnimationFrame` / `frames.cancelAnimationFrame`. A frame requested from inside a
|
|
2488
|
+
running frame is queued in either mode, so a loop that requests its own next frame advances one step
|
|
2489
|
+
per `flush()`. `stubElementRect` returns a real `DOMRect` per call, edges derived, and returns the undo.
|
|
2490
|
+
Both go through `mockValueProp`, so `restoreMockedProps()` (and `setupAutoSpy()` between tests) takes
|
|
2491
|
+
them off; `frames.restore()` does it sooner.
|
|
2492
|
+
|
|
2414
2493
|
### A module mock that did nothing
|
|
2415
2494
|
|
|
2416
2495
|
`vi.mock()` is the one thing in a ported suite that fails **silently**. Under a bundler
|
|
@@ -3246,7 +3325,7 @@ TestBed.configureTestingModule({ providers: [provideAutoSpy(MatDialog)] });
|
|
|
3246
3325
|
injectSpy(MatDialog).open.mockReturnValue(createMatDialogRef(MatDialogRef, { closedWith: 'saved' }).ref);
|
|
3247
3326
|
```
|
|
3248
3327
|
|
|
3249
|
-
|
|
3328
|
+
Six things to know:
|
|
3250
3329
|
|
|
3251
3330
|
- **`close` is the spy, and it is the same function the ref carries.** `expect(dialog.close)` and
|
|
3252
3331
|
`expect(TestBed.inject(MatDialogRef).close)` are one assertion. `emitClose(result?)` is the other
|
|
@@ -3259,7 +3338,15 @@ Five things to know:
|
|
|
3259
3338
|
- **`MAT_DIALOG_DATA` is `InjectionToken<any>` in Material**, so a hand-written `useValue: null`
|
|
3260
3339
|
compiles for a component that reads `data.name`. `provideMatDialogData<EditUserData>(…)` checks the
|
|
3261
3340
|
object; a token of your own typed `InjectionToken<EditUserData>` checks it without naming anything.
|
|
3262
|
-
|
|
3341
|
+
With the type argument named the token parameter is `InjectionToken<unknown>`, so the call passes
|
|
3342
|
+
`@typescript-eslint/no-unsafe-argument` — do not drop the type argument to silence that rule, it
|
|
3343
|
+
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.
|
|
3344
|
+
- **Name the ref type as a type argument, not as an instantiation expression.**
|
|
3345
|
+
`injectMatDialogRef(MatDialogRef<NameInputDialog, string>)` reads `Ref` off the class's `prototype`,
|
|
3346
|
+
which Material types `MatDialogRef<any, any>`, so the handle comes back `any`-typed and assigning it
|
|
3347
|
+
to a typed variable is `no-unsafe-assignment`. Write
|
|
3348
|
+
`injectMatDialogRef<MatDialogRef<NameInputDialog, string>>(MatDialogRef)` — the same for
|
|
3349
|
+
`createMatDialogRef` and `provideMatDialogRef`.
|
|
3263
3350
|
- **`componentInstance` is how the opener drives the dialog**, and it is the one member Material
|
|
3264
3351
|
keeps off the prototype, so a double that did not have it would read `undefined` rather than
|
|
3265
3352
|
fail. Hand the stand-in over and the opener works against it — checked, member by member, against
|
|
@@ -3409,6 +3496,16 @@ await setInputs(fixture, { projectId: 7 }, { label: 'the task list' }); // optio
|
|
|
3409
3496
|
// a fixture whose componentType carries no ɵcmp is refused by name too, not as a bare TypeError
|
|
3410
3497
|
// aliases resolve by CLASS FIELD name, and an input a hostDirectives entry exposes is accepted
|
|
3411
3498
|
|
|
3499
|
+
// a typed element instead of Angular's `any` — never `fixture.nativeElement as HTMLElement`
|
|
3500
|
+
const host = hostElement(fixture); // HTMLElement, checked with instanceof
|
|
3501
|
+
queryElement(fixture, '.close').click(); // HTMLElement; throws naming the selector when nothing matches
|
|
3502
|
+
queryElement(fixture.debugElement, 'input[name=q]', HTMLInputElement).value; // typed; wrong type throws
|
|
3503
|
+
// also on /bun-angular; queryElement takes an element found earlier as its root too
|
|
3504
|
+
// a null source (debugElement.query() that matched nothing) throws saying so, not 'Cannot read properties of null'
|
|
3505
|
+
expect(queryElement(fixture, '.title').textContent.trim()).toBe('Orders'); // present: read through queryElement
|
|
3506
|
+
expect(host.querySelector('.empty-state')).toBeNull(); // absent: querySelector on the typed host, no cast
|
|
3507
|
+
// NOT host.querySelector('.title')?.textContent.trim() — a miss is undefined, which toBeFalsy() accepts
|
|
3508
|
+
|
|
3412
3509
|
// build a class through DI, every unprovided token auto-spied
|
|
3413
3510
|
const { instance, spies } = createWithAutoSpies(CartService, {
|
|
3414
3511
|
providers: [{ provide: TaxService, useValue: realTax }], // explicit providers win
|
|
@@ -3903,7 +4000,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
|
|
|
3903
4000
|
| `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
4001
|
| `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
4002
|
| `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` |
|
|
4003
|
+
| `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
4004
|
| `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
4005
|
| `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
4006
|
| `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 +4207,10 @@ rules answer the same way they did — the ordering rules (`no-inject-before-ove
|
|
|
4110
4207
|
decide by range, `prefer-render-shallow` asks the template-read question once per file, and
|
|
4111
4208
|
`no-redundant-smoke-test` indexes identifiers only where a smoke test exists to judge.
|
|
4112
4209
|
|
|
4210
|
+
**A rule message names what it found and one repair, and ends in `Docs: <url>`** — the rule's own
|
|
4211
|
+
section of the rules page (`utilities/eslint-rules#<rule-name>`), which is also its `meta.docs.url`.
|
|
4212
|
+
The reasoning a message leaves out lives in that section; read it before overriding a severity.
|
|
4213
|
+
|
|
4113
4214
|
The legacy `.eslintrc` `plugins: []` form cannot work — it resolves names to `eslint-plugin-*`
|
|
4114
4215
|
packages, which a subpath export can never be.
|
|
4115
4216
|
|
|
@@ -4119,10 +4220,11 @@ packages, which a subpath export can never be.
|
|
|
4119
4220
|
|
|
4120
4221
|
| Message contains | Cause | Fix |
|
|
4121
4222
|
| -------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
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
|
|
4223
|
+
| `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
4224
|
| `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
4225
|
| `Observable spies require rxjs` | the rxjs layer was never loaded | `import 'vitest-auto-spy/rxjs';` once, in the setup file |
|
|
4125
4226
|
| `… 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) |
|
|
4227
|
+
| `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
4228
|
| ``… 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
4229
|
| `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
4230
|
| `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 +4235,51 @@ packages, which a subpath export can never be.
|
|
|
4133
4235
|
| `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
4236
|
| `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
4237
|
| 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
|
|
4238
|
+
| `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
4239
|
| `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
4240
|
| `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
|
-
| `
|
|
4241
|
+
| `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
4242
|
| `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
4243
|
| `advanceTimers() requires fake timers` | no fake timers installed | `setupFakeTimers()` or `vi.useFakeTimers()` first |
|
|
4142
4244
|
| `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
|
-
| `
|
|
4144
|
-
| `X.member was read N times
|
|
4145
|
-
| `[vitest-auto-spy] "…" wrote to
|
|
4245
|
+
| `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) |
|
|
4246
|
+
| `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) |
|
|
4247
|
+
| `[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
4248
|
| `… 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 |
|
|
4249
|
+
| `… 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 |
|
|
4250
|
+
| `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 |
|
|
4251
|
+
| `"…" 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) |
|
|
4252
|
+
| `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) |
|
|
4253
|
+
| `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) |
|
|
4254
|
+
| `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 |
|
|
4255
|
+
| `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) |
|
|
4256
|
+
| `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' })` |
|
|
4257
|
+
| `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
4258
|
| `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
4259
|
| 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
4260
|
| `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
4261
|
| `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
4262
|
| `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
4263
|
| `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
|
|
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) |
|
|
4264
|
+
| `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
4265
|
| `.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
4266
|
| `@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
4267
|
| `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
|
-
| `
|
|
4268
|
+
| `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) |
|
|
4269
|
+
| `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
4270
|
| `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
4271
|
| `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
4272
|
| `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
4273
|
| `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
4274
|
| `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
4275
|
| `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):
|
|
4276
|
+
| `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
4277
|
| `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
4278
|
| `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
4279
|
| `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
4280
|
| 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
4281
|
| `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 …`
|
|
4282
|
+
| `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
4283
|
| 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
4284
|
| `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
4285
|
| a spy is never called, no warning | the method is an instance field, not on the prototype | `instanceMethodsToSpyOn`, or `createAutoMock<T>()` |
|
|
@@ -4212,7 +4323,7 @@ packages, which a subpath export can never be.
|
|
|
4212
4323
|
| `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
4324
|
| `Cannot set base providers because it has already been called` | zone and zoneless spec files sharing one worker | `setupAngularTestEnv({ zoneless, initZone, initZoneless })` (§13) |
|
|
4214
4325
|
| 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
|
|
4326
|
+
| 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
4327
|
| 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
4328
|
| `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
4329
|
| `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 +4337,10 @@ packages, which a subpath export can never be.
|
|
|
4226
4337
|
| `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
4338
|
| `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
4339
|
| `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 …`
|
|
4230
|
-
| `setInputs: X carries no ɵcmp, so there are no inputs to set`
|
|
4340
|
+
| `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) |
|
|
4341
|
+
| `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
4342
|
| `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
|
|
4343
|
+
| `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
4344
|
| `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
4345
|
| `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
4346
|
| `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 +4360,7 @@ packages, which a subpath export can never be.
|
|
|
4249
4360
|
| 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
4361
|
| a component's `afterNextRender` state is empty | `detectChanges()` does not run the after-render phase | `await stable(fixture)` (§11) |
|
|
4251
4362
|
| 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
|
|
4363
|
+
| `--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
4364
|
| `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
4365
|
| `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
4366
|
| `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 +4368,22 @@ packages, which a subpath export can never be.
|
|
|
4257
4368
|
| `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
4369
|
| `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
4370
|
| `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
|
-
| `
|
|
4371
|
+
| `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
4372
|
| 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
4373
|
| 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
|
|
4374
|
+
| `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) |
|
|
4375
|
+
| `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) |
|
|
4376
|
+
| `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) |
|
|
4377
|
+
| `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
4378
|
| `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
|
-
| `
|
|
4379
|
+
| `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
4380
|
| `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
4381
|
| `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:
|
|
4382
|
+
| `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) |
|
|
4383
|
+
| `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
4384
|
| `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
4385
|
| `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) |
|
|
4386
|
+
| `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
4387
|
|
|
4272
4388
|
**Seven of these are library defects, not spec ones.** On anything below 3.8.0, upgrade and change
|
|
4273
4389
|
nothing in the spec: `of(double)` never emitting; `accessorSpies.setters.X` still `undefined` after
|
|
@@ -4474,8 +4590,12 @@ Full reference: <https://asdalexey.github.io/vitest-auto-spy/utilities/cli>.
|
|
|
4474
4590
|
Exit codes, as the commands implement them: **0** is "ran, nothing to report"; **1** is "ran, and
|
|
4475
4591
|
here is the finding" — `doctor` with an error, `init --check` with a stale block, `codemod` with any
|
|
4476
4592
|
note, `perf --gate` over budget; **2** is "there was nothing to judge" — no command, an unknown
|
|
4477
|
-
command, an unknown flag,
|
|
4478
|
-
|
|
4593
|
+
command, an unknown flag, a flag value it cannot use (an `--min-severity` word it does not know, a
|
|
4594
|
+
number flag given a word or a negative number, a value flag with nothing after it, an `--ignore` id
|
|
4595
|
+
`doctor` has no check for), an unreadable `--only` / `--from` value, a path matching no file, and a
|
|
4596
|
+
`perf` run that measured nothing (including a red suite, which the gate will not judge at all). An
|
|
4597
|
+
invalid flag value stops the command before it does anything: the message says so (`Nothing ran.`),
|
|
4598
|
+
and a typo in a command, a flag, `--only` or `--ignore` gets a `Did you mean`.
|
|
4479
4599
|
|
|
4480
4600
|
**Reading the output from a script: `--format json`**, on `doctor` and on `perf`. One JSON document
|
|
4481
4601
|
on stdout — `schema`, `exitCode`, `tally` (`errors`, `warnings`, `notes`), every finding with
|
|
@@ -4483,13 +4603,15 @@ on stdout — `schema`, `exitCode`, `tally` (`errors`, `warnings`, `notes`), eve
|
|
|
4483
4603
|
per candidate, `outcome` one of `confirmed`, `not reproduced`, `unconfirmed`, `single reading`,
|
|
4484
4604
|
`over budget`). Parse that rather than the text: the text is wrapped to the terminal (80 columns in a
|
|
4485
4605
|
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`.
|
|
4606
|
+
with `N errors, N warnings, N notes`. In the text, every finding ends in a `Docs:` line — its own
|
|
4607
|
+
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
4608
|
merge request note or a job summary, not for parsing.
|
|
4488
4609
|
|
|
4489
4610
|
**`doctor --ignore <check,…>`** leaves the named checks out of the report, the tally, the exit code
|
|
4490
4611
|
and the `--code-quality` file. Use it only for a finding the repository has answered in a way
|
|
4491
4612
|
`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.
|
|
4613
|
+
`angular-build-splitting-off` under a patched builder. An id `doctor` has no check for is exit 2,
|
|
4614
|
+
with the closest id suggested. `--min-severity` hides findings from the text
|
|
4493
4615
|
only; `--ignore` removes them.
|
|
4494
4616
|
|
|
4495
4617
|
### If you were asked why a suite is slow
|
|
@@ -4514,8 +4636,9 @@ assumed safe — do not treat an undecided file as a candidate, and do not add p
|
|
|
4514
4636
|
yourself; a false positive breaks someone's suite on `document is not defined`. When `import`
|
|
4515
4637
|
dominates, `perf-import` names specs that reach their subject through a barrel (`index`/`public-api`
|
|
4516
4638
|
re-exporting a whole directory). When `environment` + `setup` + `prepare` together dominate,
|
|
4517
|
-
`perf-isolation` suggests `
|
|
4518
|
-
|
|
4639
|
+
`perf-isolation` suggests `isolate: false` in the config it names, kept only if peak memory stays
|
|
4640
|
+
acceptable; its `Docs:` line leads to the check's section of the CLI page, which links this
|
|
4641
|
+
package's own memory measurements of that trade — read them before recommending the flag, since it
|
|
4519
4642
|
raises peak memory.
|
|
4520
4643
|
|
|
4521
4644
|
Two more findings are about settings rather than files. `perf-environment-engine` fires when
|
|
@@ -4523,11 +4646,14 @@ Two more findings are about settings rather than files. `perf-environment-engine
|
|
|
4523
4646
|
mentions `happy-dom`: measured on this package's own 117-file Angular suite, `happy-dom` is 23.2 s of
|
|
4524
4647
|
user CPU against jsdom's 26.5 s, and on a spec that builds a DOM and does nothing else the gap is
|
|
4525
4648
|
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
|
|
4649
|
+
platform — so take one project at a time; the finding names the config that sets `jsdom`, and the
|
|
4650
|
+
numbers stay in the docs section. `perf-workers` fires on a run over a minute of summed CPU
|
|
4527
4651
|
that declares no `maxWorkers`, and it is the one finding here about **memory**: one worker per core
|
|
4528
4652
|
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.
|
|
4530
|
-
the
|
|
4653
|
+
about 2.8 % of wall clock. The finding itself counts this machine's cores
|
|
4654
|
+
(`os.availableParallelism()`) and suggests half of them as `maxWorkers` in the config it names; the
|
|
4655
|
+
figures above are in the docs section, not in the message. Do not quote a worker count as
|
|
4656
|
+
universally right — it is a property of the machine.
|
|
4531
4657
|
|
|
4532
4658
|
**A bare `vitest run` is not every repository's suite, and `perf` refuses to pretend otherwise.**
|
|
4533
4659
|
Where the suite is assembled by something else — an Angular builder, an Nx target, a script that
|