vitest-auto-spy 5.19.0 → 5.21.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.
- package/AGENTS.md +167 -34
- package/README.md +111 -57
- package/dist/angular-diagnostics.d.ts +168 -0
- package/dist/angular-diagnostics.js +326 -0
- package/dist/angular-doubles.d.ts +254 -0
- package/dist/angular-doubles.js +203 -0
- package/dist/angular-http.js +3 -27
- package/dist/angular-matchers.d.ts +96 -0
- package/dist/angular-matchers.js +145 -0
- package/dist/angular-router.d.ts +1 -1
- package/dist/angular-router.js +1 -1
- package/dist/angular.d.ts +10 -519
- package/dist/angular.js +169 -742
- package/dist/bun-angular.d.ts +6 -6
- package/dist/bun-angular.js +16 -52
- package/dist/bun.d.ts +88 -12
- package/dist/bun.js +7 -5
- package/dist/{chunk-3LU77DBV.js → chunk-2KBFEQOV.js} +6 -4
- package/dist/chunk-3EV45V6W.js +52 -0
- package/dist/{chunk-HMECO5PK.js → chunk-4SJY7ZHM.js} +22 -2
- package/dist/{chunk-LBQWJZPS.js → chunk-BN2QV45R.js} +2 -2
- package/dist/chunk-GWEQZHMN.js +189 -0
- package/dist/{chunk-2ERHG4NE.js → chunk-I6JZIWIZ.js} +129 -72
- package/dist/{chunk-TCGO3VCY.js → chunk-ITOFGQTX.js} +2 -30
- package/dist/chunk-KOR5OK4H.js +134 -0
- package/dist/chunk-NS3Y6AQB.js +44 -0
- package/dist/{chunk-CEDA5PUY.js → chunk-QT5JIDOK.js} +1 -1
- package/dist/chunk-RPCKHTDN.js +41 -0
- package/dist/chunk-SA2QIER3.js +31 -0
- package/dist/{chunk-4JTRYOL2.js → chunk-TQAYWU5J.js} +1 -1
- package/dist/{chunk-F3OQDSKX.js → chunk-ZZSFAQT4.js} +265 -170
- package/dist/cli.js +1140 -873
- package/dist/console.d.ts +1 -1
- package/dist/console.js +5 -4
- package/dist/dom-stubs.d.ts +1 -1
- package/dist/dom-stubs.js +66 -6
- package/dist/eslint-plugin.cjs +176 -107
- package/dist/{expect-emission-QuF55ViS.d.ts → expect-emission-S5asJaJP.d.ts} +10 -3
- package/dist/index.d.ts +5 -5
- package/dist/index.js +386 -30
- package/dist/jasmine-compat.d.ts +1 -1
- package/dist/jasmine.d.ts +2 -2
- package/dist/jasmine.js +6 -5
- package/dist/nestjs.d.ts +2 -2
- package/dist/nestjs.js +8 -7
- package/dist/node.cjs +381 -26
- package/dist/node.d.cts +1 -0
- package/dist/node.d.ts +5 -5
- package/dist/node.js +381 -27
- package/dist/{package-identity-C2L4fM0k.d.ts → package-identity-Cn7yncmJ.d.ts} +1 -1
- package/dist/{prop-mock-DMqE-ldi.d.ts → prop-mock-C--a8rpU.d.ts} +3 -1
- package/dist/react.d.ts +5 -5
- package/dist/react.js +386 -30
- package/dist/rstest.d.ts +5 -5
- package/dist/rstest.js +7 -5
- package/dist/rxjs.d.ts +2 -2
- package/dist/setup.d.ts +30 -4
- package/dist/setup.js +82 -7
- package/dist/svelte.d.ts +5 -5
- package/dist/svelte.js +386 -30
- package/dist/{track-injections-BfNI9fHy.d.ts → track-injections-Dig5t28T.d.ts} +1 -1
- package/dist/{track-signal-runs-C5nd4A4_.d.ts → track-signal-runs-C3V6OIGM.d.ts} +13 -4
- package/dist/{types-B7Wjo00D.d.ts → types-BM3BcWj1.d.ts} +25 -1
- package/dist/vue.d.ts +6 -6
- package/dist/vue.js +386 -30
- package/package.json +16 -1
- package/skills/vitest-auto-spy/SKILL.md +128 -97
- package/dist/chunk-X4BVCE4V.js +0 -78
package/AGENTS.md
CHANGED
|
@@ -18,14 +18,15 @@ most of the field), `CLAUDE.md` (Claude Code) and `GEMINI.md` (Gemini CLI), plus
|
|
|
18
18
|
rule file of any tool whose own directory already exists. `--check` is the CI form. Full table:
|
|
19
19
|
<https://asdalexey.github.io/vitest-auto-spy/agents>.
|
|
20
20
|
|
|
21
|
-
| Resource | Where
|
|
22
|
-
| ----------------------- |
|
|
23
|
-
| Spec patterns at scale | <https://asdalexey.github.io/vitest-auto-spy/recipes>
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
21
|
+
| Resource | Where |
|
|
22
|
+
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
23
|
+
| Spec patterns at scale | <https://asdalexey.github.io/vitest-auto-spy/recipes> |
|
|
24
|
+
| Task recipes | <https://asdalexey.github.io/vitest-auto-spy/guides/mocking-classes> · `/guides/mocking-local-storage` · `/guides/mocking-prisma` · `/guides/storybook-angular` |
|
|
25
|
+
| Docs index for LLMs | <https://asdalexey.github.io/vitest-auto-spy/llms.txt> |
|
|
26
|
+
| Entire docs as one file | <https://asdalexey.github.io/vitest-auto-spy/llms-full.txt> |
|
|
27
|
+
| Human docs | <https://asdalexey.github.io/vitest-auto-spy/> |
|
|
28
|
+
| Source | <https://github.com/ASDAlexey/vitest-auto-spy> |
|
|
29
|
+
| Types | `node_modules/vitest-auto-spy/dist/index.d.ts` (and one per subpath) |
|
|
29
30
|
|
|
30
31
|
**Read `dist/*.d.ts` before inventing a call.** Every export is typed and documented there, and the
|
|
31
32
|
type is the authority when this file and the code disagree.
|
|
@@ -52,23 +53,31 @@ adapter installed and spies fail at runtime.
|
|
|
52
53
|
|
|
53
54
|
Add-ons, orthogonal to the runner:
|
|
54
55
|
|
|
55
|
-
| Add-on
|
|
56
|
-
|
|
|
57
|
-
| Observable spies
|
|
58
|
-
| observer-spy shim
|
|
59
|
-
| Console spies
|
|
60
|
-
| DOM stubs
|
|
61
|
-
| Run diagnostics
|
|
62
|
-
| Angular HTTP
|
|
63
|
-
| Angular router
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
|
|
|
56
|
+
| Add-on | Import | Needed for |
|
|
57
|
+
| ------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
58
|
+
| Observable spies | `import 'vitest-auto-spy/rxjs'` | `nextWith` & friends. **Side-effect import, once**, and in a file the `tsconfig` includes (§4) |
|
|
59
|
+
| 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 |
|
|
60
|
+
| Console spies | `vitest-auto-spy/console` | silent typed spies over the global `console` — `installConsoleSpies()` per test, `restoreConsole()` after |
|
|
61
|
+
| 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** |
|
|
62
|
+
| Run diagnostics | `vitest-auto-spy/diagnostics` | `compareTestRuns`, `summarizeTestRun`, `formatTestRunComparison`, `diffByField`. **Moved off the root in 4.0.0** |
|
|
63
|
+
| Angular HTTP | `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest` — `httpResource()` / `HttpClient` (§13). Optional `@angular/common` peer, this entry only |
|
|
64
|
+
| 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 |
|
|
65
|
+
| 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 6.0** so importing spies stops evaluating it |
|
|
66
|
+
| 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 6.0** |
|
|
67
|
+
| Angular matchers | `vitest-auto-spy/angular/matchers` | `registerDirectiveMatchers`, `registerResourceMatchers`, `registerSignalMatchers` (§13). Companion to `/angular` — no core re-export; **moved off `/angular` in 6.0** |
|
|
68
|
+
| 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+ |
|
|
69
|
+
| Setup helpers | `vitest-auto-spy/setup` | `setupAutoSpy()`, `setupFakeTimers()`, `blockNetwork()`, `stubResponse()`; the entry imports Vitest, so it is not for `bun test` |
|
|
70
|
+
| Zone patch | `import 'vitest-auto-spy/zone'` | `fakeAsync` / `waitForAsync` on Vitest (§14) |
|
|
71
|
+
| jasmine compat | `vitest-auto-spy/jasmine` | `.and` / `.calls` / `.withArgs`, the `jasmine` namespace (§20) |
|
|
68
72
|
|
|
69
73
|
`vitest-auto-spy/jasmine` is Vitest-only, because it registers the Vitest adapter. On `bun test` and
|
|
70
74
|
`node --test` call `enableJasmineCompat()` from `vitest-auto-spy/jasmine-compat` instead.
|
|
71
75
|
|
|
76
|
+
`vitest-auto-spy/setup` is Vitest-only as well — `setupAutoSpy()` registers Vitest hooks. On
|
|
77
|
+
`bun test` nothing restores a `spyOn`, a `mock*Prop` patch or a file-scope auto-spy between tests;
|
|
78
|
+
put `afterEach(() => { restoreMockedProps(); mock.restore(); })` in a `--preload` file, and any
|
|
79
|
+
`mock.module()` there too (inside a test it swaps bindings after the original module already ran).
|
|
80
|
+
|
|
72
81
|
The rxjs peer is **>= 7.2**, not `>=7`. The observable layer used to pull `concatMap`, `delay`,
|
|
73
82
|
`switchMap`, `take`, `takeUntil` and `takeWhile` from `rxjs/operators`; rxjs 8 removes that deep path,
|
|
74
83
|
so the specifier moved to the root `rxjs` entry — which is where rxjs re-exported them in 7.2 — and
|
|
@@ -139,7 +148,11 @@ noise scales with the number of them. Both take the same second argument (`retur
|
|
|
139
148
|
`observablePropsToSpyOn`).
|
|
140
149
|
|
|
141
150
|
`createMock<T>()` is the one to reach for on data shapes — it returns a plain `T`, so it satisfies a
|
|
142
|
-
`no-type-assertion` lint rule without an `eslint-disable` on every fixture.
|
|
151
|
+
`no-type-assertion` lint rule without an `eslint-disable` on every fixture. `createMock<T>(undefined)`
|
|
152
|
+
is the same call as `createMock<T>()` and answers `{}`, **not** `undefined` — a forwarding helper
|
|
153
|
+
with an optional `overrides` parameter relies on that. A fixture that means "no value" passes
|
|
154
|
+
`undefined` itself: `getters.profile.mockReturnValue(undefined)`, never
|
|
155
|
+
`mockReturnValue(createMock<Profile>(undefined))`.
|
|
143
156
|
|
|
144
157
|
**`createSpyFromInstance(instance, config?)` is the one case where nothing is constructed.** Every
|
|
145
158
|
factory above builds the double, which is no help once the object exists and other code already
|
|
@@ -172,6 +185,17 @@ member declared `configurable: false`, come back as this package's diagnostic ra
|
|
|
172
185
|
`sinon.createStubInstance` builds a new object from a constructor instead of patching the one you
|
|
173
186
|
hold, and `bun:test` and `node:test` have nothing.
|
|
174
187
|
|
|
188
|
+
**`passthrough: true` keeps the patched object working.** Unconfigured methods run the real
|
|
189
|
+
implementation (with the instance as `this`) and are still recorded; any configuration takes the
|
|
190
|
+
whole method over and `resetAutoSpy` hands it back. The Angular shape is
|
|
191
|
+
`createSpyFromInstance(TestBed.inject(CartService), { passthrough: true })` — dependencies, `signal()`
|
|
192
|
+
fields, `ɵprov` and the real `ngOnDestroy` keep working. Lifecycle hooks, discovered callables with an
|
|
193
|
+
API of their own (signals) and discovered classes are left real rather than spied. Do **not** combine
|
|
194
|
+
it with `strict: true` or `onUnstubbedCall` on the same call — that throws; a suite-wide strict yields
|
|
195
|
+
to it. A `calledWith(1)` miss answers `undefined`, not the real method: configuring a method hands the
|
|
196
|
+
whole method over. Use it to assert an interaction on a real collaborator; use a plain double when
|
|
197
|
+
the test must not touch the real one.
|
|
198
|
+
|
|
175
199
|
**`createFixture<T>(defaults, overrides?)` / `createFixtureFactory<T>(defaults)` are for the model
|
|
176
200
|
that more than one spec builds.** The difference from `createMock` is the `defaults` argument: it is
|
|
177
201
|
a **complete** `T`, checked in full, in one place — so a field the model dropped fails there instead
|
|
@@ -210,6 +234,16 @@ boot(asInstance(logger)); // → AppLogger, for the API under test
|
|
|
210
234
|
asSpy<AppLogger>(logger.channel('app')).info.mockReturnValue(undefined); // → the helpers
|
|
211
235
|
```
|
|
212
236
|
|
|
237
|
+
**Arrays and unmocked calls on `mockDeep`.** A member read by a numeric index (`api.items[0]`) is a
|
|
238
|
+
real `Array` of deep mocks from then on, and `map`, iteration and `toEqual` work on it. Read the
|
|
239
|
+
member again after the first index: a handle taken before the first index is still a node. To make a
|
|
240
|
+
call nobody configured fail, use `mockDeep<T>({}, { fallbackMockImplementation: () => { throw … } })`.
|
|
241
|
+
Options go in the **second** argument, not the first as in vitest-mock-extended. The precedence is
|
|
242
|
+
configuration > fallback > `selfReturning`. A `calledWith` miss answers `undefined`, not the
|
|
243
|
+
fallback; use `mustBeCalledWith` for "other arguments fail". For a callback API (`$transaction`), use
|
|
244
|
+
`method.mockImplementation((run) => run(asInstance(mock)))`; there is no option for it.
|
|
245
|
+
`vi.spyOn(mock.repo, 'find')` works on a member nobody has read and returns the node's own spy.
|
|
246
|
+
|
|
213
247
|
`asInstance` did not take a deep mock before 3.5.0, which left it with nowhere to go: this tree
|
|
214
248
|
sends you to `mockDeep` when the calls chain, and the result then fitted nothing that expected `T`.
|
|
215
249
|
|
|
@@ -394,6 +428,15 @@ from inside the placeholder's getter; the spy is kept beside the double instead,
|
|
|
394
428
|
a stable mock and `mockReturnValue` on it works. On a merely sealed double an assignment still
|
|
395
429
|
reaches the member.
|
|
396
430
|
|
|
431
|
+
`vi.spyOn(double, 'load')` on a method nobody has read yet works again. Vitest reads an accessor by
|
|
432
|
+
calling its getter with no receiver, and 5.19.0's shared getter answered that with
|
|
433
|
+
`TypeError: Invalid value used as weak map key`. The call now wraps a forwarder: a configured
|
|
434
|
+
`mockReturnValue` answers, an unconfigured call reaches the double's own spy (strict guard included),
|
|
435
|
+
and `mockRestore()` / `vi.restoreAllMocks()` hand back that same spy with the calls it recorded. It
|
|
436
|
+
is still redundant — the member already is a spy, so `double.load.mockReturnValue(…)` is the line to
|
|
437
|
+
write — and a wrapped method called off its double (`const { load } = double; load()`) throws a
|
|
438
|
+
message saying so.
|
|
439
|
+
|
|
397
440
|
**Symbol-keyed methods are discovered and spied.** A method under a symbol the project owns —
|
|
398
441
|
`[SERIALIZE]()`, `Symbol.for('app.render')` — is a method like any other, resets with the rest and
|
|
399
442
|
shows up in `Reflect.ownKeys`. The runtime's own protocol symbols are deliberately left alone
|
|
@@ -816,7 +859,14 @@ walking the prototype chain would let one registration change doubles in files n
|
|
|
816
859
|
A second registration for the same class replaces the first, because two of them in one suite is the
|
|
817
860
|
drift this removes rather than a merge to perform. `clearAutoSpyDefaults(Class)` drops one,
|
|
818
861
|
`clearAutoSpyDefaults()` the lot. `createSpyFromInstance(obj)` reads the registration of the class
|
|
819
|
-
`obj.constructor` names, merged the same way; an object literal resolves none.
|
|
862
|
+
`obj.constructor` names, merged the same way; an object literal resolves none. One exception, because
|
|
863
|
+
an instance is real: when the call site lists `onlyMethodsToSpyOn`, the rest of the object stays real,
|
|
864
|
+
so the registration contributes only `strict`, `onUnstubbedCall`, `onUnstubbedRead` and the
|
|
865
|
+
`returns` / `selfReturning` entries of the listed methods — not its accessor lists, its other method
|
|
866
|
+
lists or its `overrides`. `registerAutoSpyDefaults(Router, { gettersToSpyOn: ['url'] })` therefore
|
|
867
|
+
leaves `router.url` live under `createSpyFromInstance(router, { onlyMethodsToSpyOn: ['navigateByUrl'] })`.
|
|
868
|
+
A `returns` or `selfReturning` name the call site itself wrote for a method it left real is reported
|
|
869
|
+
as a misconfiguration and skipped.
|
|
820
870
|
|
|
821
871
|
A setup file that registers more than a handful of classes can say them as one table instead of one
|
|
822
872
|
call each. Rows apply in order, and each is checked against **its own** class — a key `Router` does
|
|
@@ -1012,6 +1062,15 @@ const config = asSpy<FeatureFlagService>(TestBed.inject(FeatureFlagService)); //
|
|
|
1012
1062
|
const config = injectSpy<FeatureFlagService>(FeatureFlagService); // ✅
|
|
1013
1063
|
```
|
|
1014
1064
|
|
|
1065
|
+
`injectSpy(X)` without the argument keeps a declared default when the constructor does not take the
|
|
1066
|
+
type parameter. When it does — `constructor(public data: T, …)`, the shape of most modal refs — 5.19.0
|
|
1067
|
+
inferred `X<never>`, and with the typed `accessorSpies` bag `Spy<X<never>>` no longer assigns to
|
|
1068
|
+
`Spy<X<unknown>>`. Such a class is now read at its **constraint**: `ModalRef<T = unknown>` gives
|
|
1069
|
+
`Spy<ModalRef<unknown>>`, `ConfigService<T extends Config = Defaults>` gives
|
|
1070
|
+
`Spy<ConfigService<Config>>`. A constructor cannot hand TypeScript the default here, so spell the
|
|
1071
|
+
argument out whenever the default or a particular instantiation is what the spec means:
|
|
1072
|
+
`injectSpy<ConfigService>(ConfigService)`, `injectSpy<ModalRef<PurchaseOptions>>(ModalRef)`.
|
|
1073
|
+
|
|
1015
1074
|
It applies to `createSpyFromClass` with a configuration too, in one combination: an accessor list
|
|
1016
1075
|
(or `overrides`) **and** `returns` on a generic class. TypeScript checks a generic class argument
|
|
1017
1076
|
after the configuration, reads `T` back from `gettersToSpyOn: ['remoteConfig']` as
|
|
@@ -1530,6 +1589,13 @@ does, which is why `vi.clearAllMocks()`, `vi.resetAllMocks()` and the `clearMock
|
|
|
1530
1589
|
(new in Vitest 5) clear these doubles exactly as they clear the runner's own. Nothing in a spec
|
|
1531
1590
|
changes, and the peer range still starts at 2.1 — one install spans Vitest 2.1 through 5.x.
|
|
1532
1591
|
|
|
1592
|
+
What each config flag does to an auto-spy, measured on both spy engines: `clearMocks` empties the
|
|
1593
|
+
calls and keeps the configuration; `mockReset` empties the calls and drops `mockReturnValue` /
|
|
1594
|
+
`mockImplementation` but **keeps `calledWith` rules**; `restoreMocks` never touches an auto-spy (it
|
|
1595
|
+
only undoes `vi.spyOn`). Do not claim the flags skip auto-spies — a sweep sentinel in the Vitest
|
|
1596
|
+
adapter routes the first two. `resetAutoSpy(spy)` is the call that drops everything, `calledWith`
|
|
1597
|
+
included. `mockReset()` on a `vi.spyOn(…)` runs the **real** method again (Vitest ≥ 3).
|
|
1598
|
+
|
|
1533
1599
|
What Vitest 5 does break is the runner's own doing, not this library's: with `clearMocks` on by
|
|
1534
1600
|
default, a test asserting on a call that an **earlier** test or a `beforeAll` recorded now reads
|
|
1535
1601
|
zero. Count it in a plain variable rather than in the spy, or set `clearMocks: false`.
|
|
@@ -1589,6 +1655,16 @@ nothing listening on that port.`WebSocket`and`EventSource`are left alone: their
|
|
|
1589
1655
|
event on an object the code keeps and reconnects, so there is no blanket answer that is not itself
|
|
1590
1656
|
a behaviour change —`stubConstructor(globalThis, 'WebSocket', …)` is the tool for a spec with one.
|
|
1591
1657
|
|
|
1658
|
+
- `stubResponse(init?)` (`/setup`) builds a real `Response` — never write `{ ok, json } as Response`.
|
|
1659
|
+
Plain data in `body` goes out as JSON with `application/json`; an `ok` that disagrees with
|
|
1660
|
+
`status` throws. A body can be read once: for a stub answering several calls use
|
|
1661
|
+
`mockImplementation(async () => stubResponse(…))`, not `mockResolvedValue`.
|
|
1662
|
+
- `blockNetwork` leaves `fetch` alone while `Symbol.for('fetch-interceptor')` is on `globalThis`
|
|
1663
|
+
(MSW `setupServer`, nock 14), so MSW handlers keep answering; XHR stays blocked for what MSW does
|
|
1664
|
+
not handle. MSW's `onUnhandledRequest: 'error'` exempts asset-looking URLs (`.svg`, `.json`,
|
|
1665
|
+
fonts…); a last `http.all('*', () => HttpResponse.error())` is the hard floor. MSW's browser
|
|
1666
|
+
`setupWorker` has not been checked.
|
|
1667
|
+
|
|
1592
1668
|
`restoreTimerGlobals` is on by default and needs no thought unless you turn it off: uninstalling
|
|
1593
1669
|
fake timers under happy-dom **deletes** `Date` instead of restoring it (the global is inherited from
|
|
1594
1670
|
the realm, not owned by `globalThis`), and with `isolate: false` the next file dies inside Vitest's
|
|
@@ -1918,7 +1994,7 @@ an unconfigured call returns — a semantic switch, not a grade; the name was ta
|
|
|
1918
1994
|
stray-timer counts (the sweep fails the file from `afterAll`, and a callback scheduled after the
|
|
1919
1995
|
previous file's sweep is charged to the next — opt in with
|
|
1920
1996
|
`onStrayTimers: ({ timers }) => expect(timers).toEqual([])`, whose diff names each one's file), and
|
|
1921
|
-
`enableAngularDiagnostics()`, which lives in `/angular` — call it in the same setup file as the Angular half of strict.
|
|
1997
|
+
`enableAngularDiagnostics()`, which lives in `/angular/diagnostics` — call it in the same setup file as the Angular half of strict.
|
|
1922
1998
|
|
|
1923
1999
|
`misconfiguration: 'throw'` on its own makes the library's misuse reports — an `onlyMethodsToSpyOn`
|
|
1924
2000
|
typo, `gettersToSpyOn` naming a method, a `returns` key no spy answers to, `injectSpy` handed a real
|
|
@@ -2168,6 +2244,24 @@ gives the probing dependency `dayjsStub`, where it used to be replaced by the na
|
|
|
2168
2244
|
the default export silently became the wrong object. Only a factory without one gets
|
|
2169
2245
|
`default: <the namespace>`, which is the interop shape it was there for; the return type follows.
|
|
2170
2246
|
|
|
2247
|
+
A factory's `vi.fn()` comes back typed as the real function, without `calledWith` or `resolveWith`.
|
|
2248
|
+
`adoptMock` takes it over in place — same object, history kept, typed from the export's signature —
|
|
2249
|
+
and an unconfigured call keeps answering what the mock answered before:
|
|
2250
|
+
|
|
2251
|
+
```ts
|
|
2252
|
+
import { loadUser } from './api';
|
|
2253
|
+
|
|
2254
|
+
vi.mock('./api', () => ({ loadUser: vi.fn() }));
|
|
2255
|
+
adoptMock(loadUser).calledWith(7).resolveWith({ id: 7, name: 'Ada' });
|
|
2256
|
+
```
|
|
2257
|
+
|
|
2258
|
+
To keep the real module and configure one case, spy it through:
|
|
2259
|
+
`vi.mock('./api', async (importOriginal) => moduleNamespace(await importOriginal(), { passthrough: true }))`.
|
|
2260
|
+
Every function export runs for real and is recorded until configured; classes and values stay real;
|
|
2261
|
+
calls one export makes to another inside the module are not recorded. A `vi.spyOn` / `{ spy: true }`
|
|
2262
|
+
mock calls an original it does not report, so adopting it makes an unconfigured call answer
|
|
2263
|
+
`undefined`; `node:test`'s `mock.fn()` is refused.
|
|
2264
|
+
|
|
2171
2265
|
There is no `mockModule(…)` helper here, and there cannot be: Vitest hoists the literal `vi.mock`
|
|
2172
2266
|
call, so a wrapper around it would be hoisted as a call to a function that does not exist yet. Share
|
|
2173
2267
|
a fixture between the factory and the tests with `vi.hoisted()`.
|
|
@@ -2191,6 +2285,13 @@ const myService = injectSpy(MyService); // Spy<MyService>
|
|
|
2191
2285
|
zone.js alike. The entry needs **Angular >= 20** (§1); on 16 or 17 it does not link at all, because
|
|
2192
2286
|
`ɵSIGNAL` is not there to import.
|
|
2193
2287
|
|
|
2288
|
+
**Niche Angular helpers ship in narrow companion subpaths, never in `/angular`** — the pattern of
|
|
2289
|
+
`/angular-http`, `/angular-router` and `/signal-forms`, and since 6.0 of `/angular/diagnostics`,
|
|
2290
|
+
`/angular/doubles` and `/angular/matchers`. An import of `/angular` evaluates its whole graph, so a
|
|
2291
|
+
helper a suite names once in a setup file must not ride along with every `provideAutoSpy` import;
|
|
2292
|
+
the diagnostics, doubles and matcher registrars left on that rule (`trackInjections` stays —
|
|
2293
|
+
`createWithAutoSpies` needs its module).
|
|
2294
|
+
|
|
2194
2295
|
### The same thing as fixtures — `extendWithAutoSpies` (Vitest 4.1+)
|
|
2195
2296
|
|
|
2196
2297
|
The block above, written once instead of once per dependency, with the types inferred rather than
|
|
@@ -2534,7 +2635,7 @@ replace a stack inside `@angular/core` with a line naming what is missing.
|
|
|
2534
2635
|
```ts
|
|
2535
2636
|
// vitest.setup.ts — AFTER getTestBed().initTestEnvironment(…), because Vitest runs
|
|
2536
2637
|
// afterEach hooks in reverse registration order and this one must run before the teardown.
|
|
2537
|
-
import { enableAngularDiagnostics } from 'vitest-auto-spy/angular';
|
|
2638
|
+
import { enableAngularDiagnostics } from 'vitest-auto-spy/angular/diagnostics';
|
|
2538
2639
|
|
|
2539
2640
|
enableAngularDiagnostics(); // { ngModuleScopes, deadSchemas, unspiedProviders, pendingRequests }
|
|
2540
2641
|
```
|
|
@@ -2795,7 +2896,7 @@ that owns them (assert through `component.form.tags()`), and custom controls —
|
|
|
2795
2896
|
### `window` and `document` over the real ones — `provideWindowDouble` / `provideDocumentDouble`
|
|
2796
2897
|
|
|
2797
2898
|
```ts
|
|
2798
|
-
import { provideDocumentDouble, provideWindowDouble } from 'vitest-auto-spy/angular';
|
|
2899
|
+
import { provideDocumentDouble, provideWindowDouble } from 'vitest-auto-spy/angular/doubles';
|
|
2799
2900
|
|
|
2800
2901
|
TestBed.configureTestingModule({
|
|
2801
2902
|
providers: [
|
|
@@ -2854,7 +2955,8 @@ Five things to know:
|
|
|
2854
2955
|
|
|
2855
2956
|
```ts
|
|
2856
2957
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
|
2857
|
-
import { expectEmission
|
|
2958
|
+
import { expectEmission } from 'vitest-auto-spy/angular';
|
|
2959
|
+
import { injectMatDialogRef, provideMatDialogData, provideMatDialogRef } from 'vitest-auto-spy/angular/doubles';
|
|
2858
2960
|
|
|
2859
2961
|
TestBed.configureTestingModule({
|
|
2860
2962
|
providers: [provideMatDialogData<EditUserData>(MAT_DIALOG_DATA, { id: 7, name: 'Ada' }), provideMatDialogRef(MatDialogRef)],
|
|
@@ -3079,11 +3181,11 @@ service.products.set([edited]); // the component's own optimistic write — stat
|
|
|
3079
3181
|
expect(products.reload).toHaveBeenCalled(); // reload is spied, answers true, and re-issues nothing
|
|
3080
3182
|
|
|
3081
3183
|
// signal assertions
|
|
3082
|
-
registerSignalMatchers(); // once, in the setup file
|
|
3184
|
+
registerSignalMatchers(); // once, in the setup file — /angular/matchers
|
|
3083
3185
|
expect(component.total).toHaveSignalValue(3);
|
|
3084
3186
|
|
|
3085
3187
|
// resource assertions — value AND status, which is the whole point
|
|
3086
|
-
registerResourceMatchers(); // once, in the setup file
|
|
3188
|
+
registerResourceMatchers(); // once, in the setup file — /angular/matchers
|
|
3087
3189
|
expect(component.products).toBeLoading();
|
|
3088
3190
|
expect(component.products).toHaveResourceValue([product]);
|
|
3089
3191
|
expect(component.products).toHaveResourceError(/503/);
|
|
@@ -3147,7 +3249,7 @@ now throws, naming what it received.
|
|
|
3147
3249
|
Per-file timing, to find which specs actually pay for `TestBed`:
|
|
3148
3250
|
|
|
3149
3251
|
```ts
|
|
3150
|
-
import { enableTestBedDiagnostics } from 'vitest-auto-spy/angular';
|
|
3252
|
+
import { enableTestBedDiagnostics } from 'vitest-auto-spy/angular/diagnostics';
|
|
3151
3253
|
|
|
3152
3254
|
if (process.env['SPEC_TIMING']) {
|
|
3153
3255
|
enableTestBedDiagnostics();
|
|
@@ -3177,7 +3279,8 @@ It exports the core, `provideAutoSpy` / `injectSpy`, `renderShallow`, `createWit
|
|
|
3177
3279
|
`expect.extend` and the TestBed diagnostics its suite-level hooks; the overrides, `extendWithAutoSpies`,
|
|
3178
3280
|
`provideAutoSpyForToken`, `trackInjections`, `setupAngularTestEnv`, the stub factories and the
|
|
3179
3281
|
`mock*Prop`, platform and dialog doubles are simply not routed to Bun. Import them from `/angular`
|
|
3180
|
-
under Vitest
|
|
3282
|
+
under Vitest, and the registrars, doubles and diagnostics from their companion entries
|
|
3283
|
+
(`/angular/matchers`, `/angular/doubles`, `/angular/diagnostics`).
|
|
3181
3284
|
|
|
3182
3285
|
---
|
|
3183
3286
|
|
|
@@ -3536,6 +3639,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
|
|
|
3536
3639
|
| `prefer-render-shallow` | `warn` | suggest | `TestBed.createComponent` in a file that never reads the template → `renderShallow(X)`; 0.24× the per-test cycle at 100 children |
|
|
3537
3640
|
| `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` |
|
|
3538
3641
|
| `prefer-observer-stub` | `error` | — | a hand-rolled observer global → `stubIntersectionObserver()` / `stubResizeObserver()` / `stubMutationObserver()`; the manual save-and-restore goes too, `restoreMockedProps()` runs the undo |
|
|
3642
|
+
| `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` |
|
|
3539
3643
|
| `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 |
|
|
3540
3644
|
| `no-passthrough-console-spy` | `error` | suggest | `vi.spyOn(console, m)` nothing gives an implementation — it calls through and prints → `installConsoleSpies()` + `consoleXSpy`, or `.mockImplementation(() => undefined)` |
|
|
3541
3645
|
| `no-console-in-spec` | `error` | — | a spec calling `console.x(…)` itself, or `console.x = …`, which nothing restores → absorb the code's output through `vitest-auto-spy/console` |
|
|
@@ -3553,8 +3657,8 @@ blanket downgrade so those keep their severity; do not copy the two names into a
|
|
|
3553
3657
|
| `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()` — a no-op here, so the spec silently asserts on post-mutation state |
|
|
3554
3658
|
| `prefer-native-spy-api` | `error` | `--fix` / suggest | `.and` / `.calls` where the spy's own API says the same thing — turn it on for the last mile off the jasmine shim |
|
|
3555
3659
|
|
|
3556
|
-
Thirty-
|
|
3557
|
-
`no-stub-class-double`, `no-structural-double`, `no-instance-lifecycle-spy` and `prefer-set-inputs`**; four fix on their own, thirteen offer suggestions. Thirty-
|
|
3660
|
+
Thirty-nine rules, **every one an `error` since 4.0.0 except `prefer-render-shallow`,
|
|
3661
|
+
`no-stub-class-double`, `no-structural-double`, `no-instance-lifecycle-spy` and `prefer-set-inputs`**; four fix on their own, thirteen offer suggestions. Thirty-six are syntactic; `no-private-member-access`, `no-mistyped-use-value` and
|
|
3558
3662
|
`no-unknown-use-value-key` read types, and all three report nothing at all without `parserOptions.project` / `projectService`
|
|
3559
3663
|
rather than guessing. `no-compile-components` waits the same way for a fact no file holds — which
|
|
3560
3664
|
builder the project has — and reports nothing until `{ builder: 'inline-resources' }` states it. The config used to be a graded mix of `error` / `warn` / `off`, which decided for the
|
|
@@ -3814,6 +3918,11 @@ packages, which a subpath export can never be.
|
|
|
3814
3918
|
| a `vi.mock()` factory that never applies, only sometimes | under `isolate: false` the module was already in the worker's graph | do not mock it; inject the dependency, or `vi.hoisted()` + a real seam |
|
|
3815
3919
|
| a `vi.mock()` of a workspace alias that never applies at all | a bundler inlined the module before the mock could be installed | `assertMocked(ns, { specifier })` to prove it, then inject instead of mocking |
|
|
3816
3920
|
| `No "default" export is defined on the mock`, thrown inside a dependency | a factory returning bare named exports; the dep probes `default` | `vi.mock('x', () => moduleNamespace({ … }))` |
|
|
3921
|
+
| `adoptMock() needs a mock that can report its implementation` | a `node:test` `mock.fn()` handed to `adoptMock` | build the double with `createFunctionSpy()` and pass that to the module mock |
|
|
3922
|
+
| `Cannot spy on export "x". Module namespace is not configurable in ESM.` | `vi.spyOn(namespace, 'x')` under Vitest browser mode or a native ESM loader — the namespace is sealed | spy the class / prototype, or `vi.mock(path, { spy: true })` / `moduleNamespace(await importOriginal(), { passthrough: true })` |
|
|
3923
|
+
| `[Function] is not a spy or a call to a spy!` in a Storybook `play` | `expect` from `storybook/test` wraps a function with no own enumerable keys, and an auto-spy keeps its `mock*` methods on a shared prototype | import `expect` from `vitest` in that story file, or `setSpyEngine('runner')` from `/setup` |
|
|
3924
|
+
| `object is not iterable` destructuring a mocked hook tuple | an auto-mock stands for an object, and a Proxy double is not iterable | write the tuple — `mockReturnValue([data, false, null])`; auto-mocks are for the object a hook returns |
|
|
3925
|
+
| `The property "x" is not defined on the object` from `vi.spyOn(Class.prototype, 'x')` | `x` is an instance field (arrow function, signal), not a prototype method | `createSpyFromClass(C, { instanceMethodsToSpyOn: ['x'] })` |
|
|
3817
3926
|
| `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 |
|
|
3818
3927
|
| `Cannot set base providers because it has already been called` | zone and zoneless spec files sharing one worker | `setupAngularTestEnv({ zoneless, initZone, initZoneless })` (§13) |
|
|
3819
3928
|
| 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…())` |
|
|
@@ -3980,6 +4089,21 @@ meant to show, with the test still green. Carry `vi.fn(() => x)` over as
|
|
|
3980
4089
|
`mockImplementation(() => x)`, and keep `mockReturnValue` for a literal. Worth saying out loud to
|
|
3981
4090
|
anyone writing a codemod, because the rename looks like the safest edit in the file.
|
|
3982
4091
|
|
|
4092
|
+
### Advice that circulates and is wrong on Vitest
|
|
4093
|
+
|
|
4094
|
+
Jest-era tutorials and cheat sheets repeat a handful of lines that fail on Vitest, or pass and leak.
|
|
4095
|
+
Each was checked on Vitest 5.0.0:
|
|
4096
|
+
|
|
4097
|
+
| Circulating advice | What happens | Write instead |
|
|
4098
|
+
| ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
4099
|
+
| `const load = vi.fn(); vi.mock('./api', () => ({ load }))` | `vi.mock` is hoisted above every import and `const`: `ReferenceError: Cannot access 'load' before initialization`, wrapped in `[vitest] There was an error when mocking a module` | `const mocks = vi.hoisted(() => ({ load: vi.fn() }))`, then `() => ({ load: mocks.load })` |
|
|
4100
|
+
| "name it `mockLoad` and the factory may read it" | Jest's exemption for `mock`-prefixed names does not exist in Vitest — same `ReferenceError` | `vi.hoisted` |
|
|
4101
|
+
| `vi.requireActual('./api')` / `jest.requireActual` | `vi.requireActual` is `undefined` | `vi.mock('./api', async (importOriginal) => ({ ...(await importOriginal<typeof import('./api')>()), load: vi.fn() }))` |
|
|
4102
|
+
| `import { jest } from 'vitest'` | `vitest` exports no `jest`; the binding is `undefined` | `vi` — `vi.fn()`, `vi.spyOn()`, `vi.mock()` |
|
|
4103
|
+
| `import { userEvent } from '@testing-library/user-event'` | the named export exists only from 14.5.0; the default export works on every 14.x, and every 14.x method returns a promise | `import userEvent from '@testing-library/user-event'`; `const user = userEvent.setup(); await user.click(el)` |
|
|
4104
|
+
| `vi.restoreAllMocks()` "to undo fake timers" | `restoreAllMocks`, `resetAllMocks` and `clearAllMocks` leave fake timers installed — `vi.isFakeTimers()` is still `true` | `vi.useRealTimers()` in `afterEach` |
|
|
4105
|
+
| `global.fetch = vi.fn()` in a test | survives `vi.restoreAllMocks()` and `vi.unstubAllGlobals()`, and answers every later test of the file | `mockValueProp(globalThis, 'fetch', …)` or `vi.stubGlobal` + `unstubGlobals: true`; `blockNetwork()` to stay offline; the `no-hand-assigned-global` rule reports the bare form |
|
|
4106
|
+
|
|
3983
4107
|
---
|
|
3984
4108
|
|
|
3985
4109
|
## 19. Before you report success
|
|
@@ -4036,7 +4160,8 @@ Full reference: <https://asdalexey.github.io/vitest-auto-spy/utilities/cli>.
|
|
|
4036
4160
|
worktrees under it listed every file twice, so `doctor` reported each import graph in duplicate and
|
|
4037
4161
|
`codemod --write` would have rewritten specs on another branch. A `.git` entry is a stop, whether
|
|
4038
4162
|
it is a directory (a nested clone) or a file (a worktree). Past 50 000 files the scan still
|
|
4039
|
-
truncates and
|
|
4163
|
+
truncates, and `doctor` reports that as a `scan-cap-reached` warning (exit 1) rather than a clean
|
|
4164
|
+
result; `VITEST_AUTO_SPY_SCAN_CAP` raises the cap.
|
|
4040
4165
|
- **A path that matches no file is an error, exit 2.** _Nothing left to migrate_ off a path nobody
|
|
4041
4166
|
read is not a clean result. Absolute paths and `./`-style ones resolve against `--cwd`.
|
|
4042
4167
|
|
|
@@ -4046,6 +4171,14 @@ note, `perf --gate` over budget; **2** is "there was nothing to judge" — no co
|
|
|
4046
4171
|
command, an unknown flag, an unreadable `--only` / `--from` value, a path matching no file, and a
|
|
4047
4172
|
`perf` run that measured nothing (including a red suite, which the gate will not judge at all).
|
|
4048
4173
|
|
|
4174
|
+
**Reading the output from a script: `--format json`**, on `doctor` and on `perf`. One JSON document
|
|
4175
|
+
on stdout — `schema`, `exitCode`, `tally` (`errors`, `warnings`, `notes`), every finding with
|
|
4176
|
+
`check`, `severity`, `file`, `message`, `fix`; `perf` adds `run`, `budgets` and `gate.verdicts` (one row
|
|
4177
|
+
per candidate, `outcome` one of `confirmed`, `not reproduced`, `unconfirmed`, `single reading`,
|
|
4178
|
+
`over budget`). Parse that rather than the text: the text is wrapped to the terminal (80 columns in a
|
|
4179
|
+
pipe), groups one cause found in many files into one block, and ends in a tally line that starts
|
|
4180
|
+
with `N errors, N warnings, N notes`.
|
|
4181
|
+
|
|
4049
4182
|
### If you were asked why a suite is slow
|
|
4050
4183
|
|
|
4051
4184
|
```bash
|