vitest-auto-spy 5.19.0 → 5.20.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 +135 -14
- package/README.md +67 -18
- package/dist/angular-router.d.ts +1 -1
- package/dist/angular-router.js +1 -1
- package/dist/angular.d.ts +6 -6
- package/dist/angular.js +43 -10
- package/dist/bun-angular.d.ts +6 -6
- package/dist/bun-angular.js +7 -7
- package/dist/bun.d.ts +88 -12
- package/dist/bun.js +3 -3
- package/dist/{chunk-3LU77DBV.js → chunk-2KBFEQOV.js} +6 -4
- package/dist/{chunk-HMECO5PK.js → chunk-4SJY7ZHM.js} +22 -2
- package/dist/{chunk-4JTRYOL2.js → chunk-IIZTARIO.js} +1 -1
- package/dist/{chunk-LBQWJZPS.js → chunk-LE6IB3DK.js} +1 -1
- package/dist/{chunk-F3OQDSKX.js → chunk-QB5VKPTI.js} +263 -20
- package/dist/{chunk-2ERHG4NE.js → chunk-SXR7EAUS.js} +42 -8
- package/dist/cli.js +881 -614
- package/dist/console.d.ts +1 -1
- package/dist/console.js +3 -3
- package/dist/dom-stubs.d.ts +1 -1
- package/dist/dom-stubs.js +5 -3
- package/dist/eslint-plugin.cjs +176 -107
- package/dist/{expect-emission-QuF55ViS.d.ts → expect-emission-CjwVMb2W.d.ts} +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +326 -27
- package/dist/jasmine-compat.d.ts +1 -1
- package/dist/jasmine.d.ts +2 -2
- package/dist/jasmine.js +4 -4
- package/dist/nestjs.d.ts +2 -2
- package/dist/nestjs.js +6 -6
- package/dist/node.cjs +321 -23
- package/dist/node.d.cts +1 -0
- package/dist/node.d.ts +5 -5
- package/dist/node.js +321 -24
- package/dist/{package-identity-C2L4fM0k.d.ts → package-identity-Cn7yncmJ.d.ts} +1 -1
- package/dist/{prop-mock-DMqE-ldi.d.ts → prop-mock-DMEpvaAU.d.ts} +1 -1
- package/dist/react.d.ts +5 -5
- package/dist/react.js +326 -27
- package/dist/rstest.d.ts +5 -5
- package/dist/rstest.js +4 -4
- package/dist/rxjs.d.ts +2 -2
- package/dist/setup.d.ts +30 -4
- package/dist/setup.js +47 -5
- package/dist/svelte.d.ts +5 -5
- package/dist/svelte.js +326 -27
- 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-CErdcgTA.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 +326 -27
- package/package.json +1 -1
- package/skills/vitest-auto-spy/SKILL.md +127 -96
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.
|
|
@@ -62,13 +63,18 @@ Add-ons, orthogonal to the runner:
|
|
|
62
63
|
| Angular HTTP | `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest` — `httpResource()` / `HttpClient` (§13). Optional `@angular/common` peer, this entry only |
|
|
63
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 |
|
|
64
65
|
| 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+ |
|
|
65
|
-
| Setup helpers | `vitest-auto-spy/setup` | `setupAutoSpy()`, `setupFakeTimers()`
|
|
66
|
+
| Setup helpers | `vitest-auto-spy/setup` | `setupAutoSpy()`, `setupFakeTimers()`, `blockNetwork()`, `stubResponse()`; the entry imports Vitest, so it is not for `bun test` |
|
|
66
67
|
| Zone patch | `import 'vitest-auto-spy/zone'` | `fakeAsync` / `waitForAsync` on Vitest (§14) |
|
|
67
68
|
| jasmine compat | `vitest-auto-spy/jasmine` | `.and` / `.calls` / `.withArgs`, the `jasmine` namespace (§20) |
|
|
68
69
|
|
|
69
70
|
`vitest-auto-spy/jasmine` is Vitest-only, because it registers the Vitest adapter. On `bun test` and
|
|
70
71
|
`node --test` call `enableJasmineCompat()` from `vitest-auto-spy/jasmine-compat` instead.
|
|
71
72
|
|
|
73
|
+
`vitest-auto-spy/setup` is Vitest-only as well — `setupAutoSpy()` registers Vitest hooks. On
|
|
74
|
+
`bun test` nothing restores a `spyOn`, a `mock*Prop` patch or a file-scope auto-spy between tests;
|
|
75
|
+
put `afterEach(() => { restoreMockedProps(); mock.restore(); })` in a `--preload` file, and any
|
|
76
|
+
`mock.module()` there too (inside a test it swaps bindings after the original module already ran).
|
|
77
|
+
|
|
72
78
|
The rxjs peer is **>= 7.2**, not `>=7`. The observable layer used to pull `concatMap`, `delay`,
|
|
73
79
|
`switchMap`, `take`, `takeUntil` and `takeWhile` from `rxjs/operators`; rxjs 8 removes that deep path,
|
|
74
80
|
so the specifier moved to the root `rxjs` entry — which is where rxjs re-exported them in 7.2 — and
|
|
@@ -139,7 +145,11 @@ noise scales with the number of them. Both take the same second argument (`retur
|
|
|
139
145
|
`observablePropsToSpyOn`).
|
|
140
146
|
|
|
141
147
|
`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.
|
|
148
|
+
`no-type-assertion` lint rule without an `eslint-disable` on every fixture. `createMock<T>(undefined)`
|
|
149
|
+
is the same call as `createMock<T>()` and answers `{}`, **not** `undefined` — a forwarding helper
|
|
150
|
+
with an optional `overrides` parameter relies on that. A fixture that means "no value" passes
|
|
151
|
+
`undefined` itself: `getters.profile.mockReturnValue(undefined)`, never
|
|
152
|
+
`mockReturnValue(createMock<Profile>(undefined))`.
|
|
143
153
|
|
|
144
154
|
**`createSpyFromInstance(instance, config?)` is the one case where nothing is constructed.** Every
|
|
145
155
|
factory above builds the double, which is no help once the object exists and other code already
|
|
@@ -172,6 +182,17 @@ member declared `configurable: false`, come back as this package's diagnostic ra
|
|
|
172
182
|
`sinon.createStubInstance` builds a new object from a constructor instead of patching the one you
|
|
173
183
|
hold, and `bun:test` and `node:test` have nothing.
|
|
174
184
|
|
|
185
|
+
**`passthrough: true` keeps the patched object working.** Unconfigured methods run the real
|
|
186
|
+
implementation (with the instance as `this`) and are still recorded; any configuration takes the
|
|
187
|
+
whole method over and `resetAutoSpy` hands it back. The Angular shape is
|
|
188
|
+
`createSpyFromInstance(TestBed.inject(CartService), { passthrough: true })` — dependencies, `signal()`
|
|
189
|
+
fields, `ɵprov` and the real `ngOnDestroy` keep working. Lifecycle hooks, discovered callables with an
|
|
190
|
+
API of their own (signals) and discovered classes are left real rather than spied. Do **not** combine
|
|
191
|
+
it with `strict: true` or `onUnstubbedCall` on the same call — that throws; a suite-wide strict yields
|
|
192
|
+
to it. A `calledWith(1)` miss answers `undefined`, not the real method: configuring a method hands the
|
|
193
|
+
whole method over. Use it to assert an interaction on a real collaborator; use a plain double when
|
|
194
|
+
the test must not touch the real one.
|
|
195
|
+
|
|
175
196
|
**`createFixture<T>(defaults, overrides?)` / `createFixtureFactory<T>(defaults)` are for the model
|
|
176
197
|
that more than one spec builds.** The difference from `createMock` is the `defaults` argument: it is
|
|
177
198
|
a **complete** `T`, checked in full, in one place — so a field the model dropped fails there instead
|
|
@@ -210,6 +231,16 @@ boot(asInstance(logger)); // → AppLogger, for the API under test
|
|
|
210
231
|
asSpy<AppLogger>(logger.channel('app')).info.mockReturnValue(undefined); // → the helpers
|
|
211
232
|
```
|
|
212
233
|
|
|
234
|
+
**Arrays and unmocked calls on `mockDeep`.** A member read by a numeric index (`api.items[0]`) is a
|
|
235
|
+
real `Array` of deep mocks from then on, and `map`, iteration and `toEqual` work on it. Read the
|
|
236
|
+
member again after the first index: a handle taken before the first index is still a node. To make a
|
|
237
|
+
call nobody configured fail, use `mockDeep<T>({}, { fallbackMockImplementation: () => { throw … } })`.
|
|
238
|
+
Options go in the **second** argument, not the first as in vitest-mock-extended. The precedence is
|
|
239
|
+
configuration > fallback > `selfReturning`. A `calledWith` miss answers `undefined`, not the
|
|
240
|
+
fallback; use `mustBeCalledWith` for "other arguments fail". For a callback API (`$transaction`), use
|
|
241
|
+
`method.mockImplementation((run) => run(asInstance(mock)))`; there is no option for it.
|
|
242
|
+
`vi.spyOn(mock.repo, 'find')` works on a member nobody has read and returns the node's own spy.
|
|
243
|
+
|
|
213
244
|
`asInstance` did not take a deep mock before 3.5.0, which left it with nowhere to go: this tree
|
|
214
245
|
sends you to `mockDeep` when the calls chain, and the result then fitted nothing that expected `T`.
|
|
215
246
|
|
|
@@ -394,6 +425,15 @@ from inside the placeholder's getter; the spy is kept beside the double instead,
|
|
|
394
425
|
a stable mock and `mockReturnValue` on it works. On a merely sealed double an assignment still
|
|
395
426
|
reaches the member.
|
|
396
427
|
|
|
428
|
+
`vi.spyOn(double, 'load')` on a method nobody has read yet works again. Vitest reads an accessor by
|
|
429
|
+
calling its getter with no receiver, and 5.19.0's shared getter answered that with
|
|
430
|
+
`TypeError: Invalid value used as weak map key`. The call now wraps a forwarder: a configured
|
|
431
|
+
`mockReturnValue` answers, an unconfigured call reaches the double's own spy (strict guard included),
|
|
432
|
+
and `mockRestore()` / `vi.restoreAllMocks()` hand back that same spy with the calls it recorded. It
|
|
433
|
+
is still redundant — the member already is a spy, so `double.load.mockReturnValue(…)` is the line to
|
|
434
|
+
write — and a wrapped method called off its double (`const { load } = double; load()`) throws a
|
|
435
|
+
message saying so.
|
|
436
|
+
|
|
397
437
|
**Symbol-keyed methods are discovered and spied.** A method under a symbol the project owns —
|
|
398
438
|
`[SERIALIZE]()`, `Symbol.for('app.render')` — is a method like any other, resets with the rest and
|
|
399
439
|
shows up in `Reflect.ownKeys`. The runtime's own protocol symbols are deliberately left alone
|
|
@@ -816,7 +856,14 @@ walking the prototype chain would let one registration change doubles in files n
|
|
|
816
856
|
A second registration for the same class replaces the first, because two of them in one suite is the
|
|
817
857
|
drift this removes rather than a merge to perform. `clearAutoSpyDefaults(Class)` drops one,
|
|
818
858
|
`clearAutoSpyDefaults()` the lot. `createSpyFromInstance(obj)` reads the registration of the class
|
|
819
|
-
`obj.constructor` names, merged the same way; an object literal resolves none.
|
|
859
|
+
`obj.constructor` names, merged the same way; an object literal resolves none. One exception, because
|
|
860
|
+
an instance is real: when the call site lists `onlyMethodsToSpyOn`, the rest of the object stays real,
|
|
861
|
+
so the registration contributes only `strict`, `onUnstubbedCall`, `onUnstubbedRead` and the
|
|
862
|
+
`returns` / `selfReturning` entries of the listed methods — not its accessor lists, its other method
|
|
863
|
+
lists or its `overrides`. `registerAutoSpyDefaults(Router, { gettersToSpyOn: ['url'] })` therefore
|
|
864
|
+
leaves `router.url` live under `createSpyFromInstance(router, { onlyMethodsToSpyOn: ['navigateByUrl'] })`.
|
|
865
|
+
A `returns` or `selfReturning` name the call site itself wrote for a method it left real is reported
|
|
866
|
+
as a misconfiguration and skipped.
|
|
820
867
|
|
|
821
868
|
A setup file that registers more than a handful of classes can say them as one table instead of one
|
|
822
869
|
call each. Rows apply in order, and each is checked against **its own** class — a key `Router` does
|
|
@@ -1012,6 +1059,15 @@ const config = asSpy<FeatureFlagService>(TestBed.inject(FeatureFlagService)); //
|
|
|
1012
1059
|
const config = injectSpy<FeatureFlagService>(FeatureFlagService); // ✅
|
|
1013
1060
|
```
|
|
1014
1061
|
|
|
1062
|
+
`injectSpy(X)` without the argument keeps a declared default when the constructor does not take the
|
|
1063
|
+
type parameter. When it does — `constructor(public data: T, …)`, the shape of most modal refs — 5.19.0
|
|
1064
|
+
inferred `X<never>`, and with the typed `accessorSpies` bag `Spy<X<never>>` no longer assigns to
|
|
1065
|
+
`Spy<X<unknown>>`. Such a class is now read at its **constraint**: `ModalRef<T = unknown>` gives
|
|
1066
|
+
`Spy<ModalRef<unknown>>`, `ConfigService<T extends Config = Defaults>` gives
|
|
1067
|
+
`Spy<ConfigService<Config>>`. A constructor cannot hand TypeScript the default here, so spell the
|
|
1068
|
+
argument out whenever the default or a particular instantiation is what the spec means:
|
|
1069
|
+
`injectSpy<ConfigService>(ConfigService)`, `injectSpy<ModalRef<PurchaseOptions>>(ModalRef)`.
|
|
1070
|
+
|
|
1015
1071
|
It applies to `createSpyFromClass` with a configuration too, in one combination: an accessor list
|
|
1016
1072
|
(or `overrides`) **and** `returns` on a generic class. TypeScript checks a generic class argument
|
|
1017
1073
|
after the configuration, reads `T` back from `gettersToSpyOn: ['remoteConfig']` as
|
|
@@ -1530,6 +1586,13 @@ does, which is why `vi.clearAllMocks()`, `vi.resetAllMocks()` and the `clearMock
|
|
|
1530
1586
|
(new in Vitest 5) clear these doubles exactly as they clear the runner's own. Nothing in a spec
|
|
1531
1587
|
changes, and the peer range still starts at 2.1 — one install spans Vitest 2.1 through 5.x.
|
|
1532
1588
|
|
|
1589
|
+
What each config flag does to an auto-spy, measured on both spy engines: `clearMocks` empties the
|
|
1590
|
+
calls and keeps the configuration; `mockReset` empties the calls and drops `mockReturnValue` /
|
|
1591
|
+
`mockImplementation` but **keeps `calledWith` rules**; `restoreMocks` never touches an auto-spy (it
|
|
1592
|
+
only undoes `vi.spyOn`). Do not claim the flags skip auto-spies — a sweep sentinel in the Vitest
|
|
1593
|
+
adapter routes the first two. `resetAutoSpy(spy)` is the call that drops everything, `calledWith`
|
|
1594
|
+
included. `mockReset()` on a `vi.spyOn(…)` runs the **real** method again (Vitest ≥ 3).
|
|
1595
|
+
|
|
1533
1596
|
What Vitest 5 does break is the runner's own doing, not this library's: with `clearMocks` on by
|
|
1534
1597
|
default, a test asserting on a call that an **earlier** test or a `beforeAll` recorded now reads
|
|
1535
1598
|
zero. Count it in a plain variable rather than in the spy, or set `clearMocks: false`.
|
|
@@ -1589,6 +1652,16 @@ nothing listening on that port.`WebSocket`and`EventSource`are left alone: their
|
|
|
1589
1652
|
event on an object the code keeps and reconnects, so there is no blanket answer that is not itself
|
|
1590
1653
|
a behaviour change —`stubConstructor(globalThis, 'WebSocket', …)` is the tool for a spec with one.
|
|
1591
1654
|
|
|
1655
|
+
- `stubResponse(init?)` (`/setup`) builds a real `Response` — never write `{ ok, json } as Response`.
|
|
1656
|
+
Plain data in `body` goes out as JSON with `application/json`; an `ok` that disagrees with
|
|
1657
|
+
`status` throws. A body can be read once: for a stub answering several calls use
|
|
1658
|
+
`mockImplementation(async () => stubResponse(…))`, not `mockResolvedValue`.
|
|
1659
|
+
- `blockNetwork` leaves `fetch` alone while `Symbol.for('fetch-interceptor')` is on `globalThis`
|
|
1660
|
+
(MSW `setupServer`, nock 14), so MSW handlers keep answering; XHR stays blocked for what MSW does
|
|
1661
|
+
not handle. MSW's `onUnhandledRequest: 'error'` exempts asset-looking URLs (`.svg`, `.json`,
|
|
1662
|
+
fonts…); a last `http.all('*', () => HttpResponse.error())` is the hard floor. MSW's browser
|
|
1663
|
+
`setupWorker` has not been checked.
|
|
1664
|
+
|
|
1592
1665
|
`restoreTimerGlobals` is on by default and needs no thought unless you turn it off: uninstalling
|
|
1593
1666
|
fake timers under happy-dom **deletes** `Date` instead of restoring it (the global is inherited from
|
|
1594
1667
|
the realm, not owned by `globalThis`), and with `isolate: false` the next file dies inside Vitest's
|
|
@@ -2168,6 +2241,24 @@ gives the probing dependency `dayjsStub`, where it used to be replaced by the na
|
|
|
2168
2241
|
the default export silently became the wrong object. Only a factory without one gets
|
|
2169
2242
|
`default: <the namespace>`, which is the interop shape it was there for; the return type follows.
|
|
2170
2243
|
|
|
2244
|
+
A factory's `vi.fn()` comes back typed as the real function, without `calledWith` or `resolveWith`.
|
|
2245
|
+
`adoptMock` takes it over in place — same object, history kept, typed from the export's signature —
|
|
2246
|
+
and an unconfigured call keeps answering what the mock answered before:
|
|
2247
|
+
|
|
2248
|
+
```ts
|
|
2249
|
+
import { loadUser } from './api';
|
|
2250
|
+
|
|
2251
|
+
vi.mock('./api', () => ({ loadUser: vi.fn() }));
|
|
2252
|
+
adoptMock(loadUser).calledWith(7).resolveWith({ id: 7, name: 'Ada' });
|
|
2253
|
+
```
|
|
2254
|
+
|
|
2255
|
+
To keep the real module and configure one case, spy it through:
|
|
2256
|
+
`vi.mock('./api', async (importOriginal) => moduleNamespace(await importOriginal(), { passthrough: true }))`.
|
|
2257
|
+
Every function export runs for real and is recorded until configured; classes and values stay real;
|
|
2258
|
+
calls one export makes to another inside the module are not recorded. A `vi.spyOn` / `{ spy: true }`
|
|
2259
|
+
mock calls an original it does not report, so adopting it makes an unconfigured call answer
|
|
2260
|
+
`undefined`; `node:test`'s `mock.fn()` is refused.
|
|
2261
|
+
|
|
2171
2262
|
There is no `mockModule(…)` helper here, and there cannot be: Vitest hoists the literal `vi.mock`
|
|
2172
2263
|
call, so a wrapper around it would be hoisted as a call to a function that does not exist yet. Share
|
|
2173
2264
|
a fixture between the factory and the tests with `vi.hoisted()`.
|
|
@@ -3536,6 +3627,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
|
|
|
3536
3627
|
| `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
3628
|
| `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
3629
|
| `prefer-observer-stub` | `error` | — | a hand-rolled observer global → `stubIntersectionObserver()` / `stubResizeObserver()` / `stubMutationObserver()`; the manual save-and-restore goes too, `restoreMockedProps()` runs the undo |
|
|
3630
|
+
| `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
3631
|
| `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
3632
|
| `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
3633
|
| `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 +3645,8 @@ blanket downgrade so those keep their severity; do not copy the two names into a
|
|
|
3553
3645
|
| `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()` — a no-op here, so the spec silently asserts on post-mutation state |
|
|
3554
3646
|
| `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
3647
|
|
|
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-
|
|
3648
|
+
Thirty-nine rules, **every one an `error` since 4.0.0 except `prefer-render-shallow`,
|
|
3649
|
+
`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
3650
|
`no-unknown-use-value-key` read types, and all three report nothing at all without `parserOptions.project` / `projectService`
|
|
3559
3651
|
rather than guessing. `no-compile-components` waits the same way for a fact no file holds — which
|
|
3560
3652
|
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 +3906,11 @@ packages, which a subpath export can never be.
|
|
|
3814
3906
|
| 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
3907
|
| 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
3908
|
| `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({ … }))` |
|
|
3909
|
+
| `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 |
|
|
3910
|
+
| `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 })` |
|
|
3911
|
+
| `[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` |
|
|
3912
|
+
| `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 |
|
|
3913
|
+
| `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
3914
|
| `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
3915
|
| `Cannot set base providers because it has already been called` | zone and zoneless spec files sharing one worker | `setupAngularTestEnv({ zoneless, initZone, initZoneless })` (§13) |
|
|
3819
3916
|
| 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 +4077,21 @@ meant to show, with the test still green. Carry `vi.fn(() => x)` over as
|
|
|
3980
4077
|
`mockImplementation(() => x)`, and keep `mockReturnValue` for a literal. Worth saying out loud to
|
|
3981
4078
|
anyone writing a codemod, because the rename looks like the safest edit in the file.
|
|
3982
4079
|
|
|
4080
|
+
### Advice that circulates and is wrong on Vitest
|
|
4081
|
+
|
|
4082
|
+
Jest-era tutorials and cheat sheets repeat a handful of lines that fail on Vitest, or pass and leak.
|
|
4083
|
+
Each was checked on Vitest 5.0.0:
|
|
4084
|
+
|
|
4085
|
+
| Circulating advice | What happens | Write instead |
|
|
4086
|
+
| ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
4087
|
+
| `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 })` |
|
|
4088
|
+
| "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` |
|
|
4089
|
+
| `vi.requireActual('./api')` / `jest.requireActual` | `vi.requireActual` is `undefined` | `vi.mock('./api', async (importOriginal) => ({ ...(await importOriginal<typeof import('./api')>()), load: vi.fn() }))` |
|
|
4090
|
+
| `import { jest } from 'vitest'` | `vitest` exports no `jest`; the binding is `undefined` | `vi` — `vi.fn()`, `vi.spyOn()`, `vi.mock()` |
|
|
4091
|
+
| `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)` |
|
|
4092
|
+
| `vi.restoreAllMocks()` "to undo fake timers" | `restoreAllMocks`, `resetAllMocks` and `clearAllMocks` leave fake timers installed — `vi.isFakeTimers()` is still `true` | `vi.useRealTimers()` in `afterEach` |
|
|
4093
|
+
| `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 |
|
|
4094
|
+
|
|
3983
4095
|
---
|
|
3984
4096
|
|
|
3985
4097
|
## 19. Before you report success
|
|
@@ -4036,7 +4148,8 @@ Full reference: <https://asdalexey.github.io/vitest-auto-spy/utilities/cli>.
|
|
|
4036
4148
|
worktrees under it listed every file twice, so `doctor` reported each import graph in duplicate and
|
|
4037
4149
|
`codemod --write` would have rewritten specs on another branch. A `.git` entry is a stop, whether
|
|
4038
4150
|
it is a directory (a nested clone) or a file (a worktree). Past 50 000 files the scan still
|
|
4039
|
-
truncates and
|
|
4151
|
+
truncates, and `doctor` reports that as a `scan-cap-reached` warning (exit 1) rather than a clean
|
|
4152
|
+
result; `VITEST_AUTO_SPY_SCAN_CAP` raises the cap.
|
|
4040
4153
|
- **A path that matches no file is an error, exit 2.** _Nothing left to migrate_ off a path nobody
|
|
4041
4154
|
read is not a clean result. Absolute paths and `./`-style ones resolve against `--cwd`.
|
|
4042
4155
|
|
|
@@ -4046,6 +4159,14 @@ note, `perf --gate` over budget; **2** is "there was nothing to judge" — no co
|
|
|
4046
4159
|
command, an unknown flag, an unreadable `--only` / `--from` value, a path matching no file, and a
|
|
4047
4160
|
`perf` run that measured nothing (including a red suite, which the gate will not judge at all).
|
|
4048
4161
|
|
|
4162
|
+
**Reading the output from a script: `--format json`**, on `doctor` and on `perf`. One JSON document
|
|
4163
|
+
on stdout — `schema`, `exitCode`, `tally` (`errors`, `warnings`, `notes`), every finding with
|
|
4164
|
+
`check`, `severity`, `file`, `message`, `fix`; `perf` adds `run`, `budgets` and `gate.verdicts` (one row
|
|
4165
|
+
per candidate, `outcome` one of `confirmed`, `not reproduced`, `unconfirmed`, `single reading`,
|
|
4166
|
+
`over budget`). Parse that rather than the text: the text is wrapped to the terminal (80 columns in a
|
|
4167
|
+
pipe), groups one cause found in many files into one block, and ends in a tally line that starts
|
|
4168
|
+
with `N errors, N warnings, N notes`.
|
|
4169
|
+
|
|
4049
4170
|
### If you were asked why a suite is slow
|
|
4050
4171
|
|
|
4051
4172
|
```bash
|