vitest-auto-spy 4.6.0 → 5.0.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 +51 -6
- package/README.md +131 -65
- package/dist/angular-http.js +16 -6
- package/dist/angular.d.ts +3 -3
- package/dist/angular.js +88 -36
- package/dist/bun-angular.d.ts +1 -1
- package/dist/bun-angular.js +8 -8
- package/dist/bun.js +4 -4
- package/dist/{chunk-4OANQSWG.js → chunk-5UF3IVFC.js} +1 -1
- package/dist/{chunk-557TSVD5.js → chunk-7U6CKTXA.js} +1 -1
- package/dist/{chunk-5BPM3BLI.js → chunk-ALFMIDWW.js} +1 -7
- package/dist/{chunk-FFKN6QKI.js → chunk-E7HYS4K4.js} +6 -4
- package/dist/{chunk-4VNMT3ZU.js → chunk-GNBP7CNT.js} +1 -1
- package/dist/{chunk-3GK5IJTV.js → chunk-KJREOXX6.js} +38 -23
- package/dist/{chunk-7NI7XB6R.js → chunk-VZP5MG5N.js} +2 -2
- package/dist/{chunk-D5LDT3W4.js → chunk-ZLF2YBXV.js} +2 -2
- package/dist/cli.js +103 -20
- package/dist/console.js +2 -2
- package/dist/dom-stubs.js +54 -11
- package/dist/index.js +44 -27
- package/dist/jasmine.js +4 -4
- package/dist/nestjs.js +5 -5
- package/dist/node.cjs +43 -26
- package/dist/node.js +4 -4
- package/dist/react.js +5 -5
- package/dist/rxjs.js +1 -2
- package/dist/setup.js +4 -4
- package/dist/svelte.js +5 -5
- package/dist/vue.js +6 -6
- package/dist/{zoneless-LsBDcSNZ.d.ts → zoneless-BFq3ve6Q.d.ts} +13 -2
- package/package.json +36 -17
- package/skills/vitest-auto-spy/SKILL.md +7 -1
package/AGENTS.md
CHANGED
|
@@ -66,6 +66,23 @@ Add-ons, orthogonal to the runner:
|
|
|
66
66
|
`vitest-auto-spy/jasmine` is Vitest-only, because it registers the Vitest adapter. On `bun test` and
|
|
67
67
|
`node --test` call `enableJasmineCompat()` from `vitest-auto-spy/jasmine-compat` instead.
|
|
68
68
|
|
|
69
|
+
The rxjs peer is **>= 7.2**, not `>=7`. The observable layer used to pull `concatMap`, `delay`,
|
|
70
|
+
`switchMap`, `take`, `takeUntil` and `takeWhile` from `rxjs/operators`; rxjs 8 removes that deep path,
|
|
71
|
+
so the specifier moved to the root `rxjs` entry — which is where rxjs re-exported them in 7.2 — and
|
|
72
|
+
the floor moved with it. Any Angular project already satisfies it: Angular 16 through 22 all peer on
|
|
73
|
+
`^6.5.3 || ^7.4.0`.
|
|
74
|
+
|
|
75
|
+
The Angular entries need **Angular >= 20** — `@angular/core`, `@angular/common` and
|
|
76
|
+
`@angular/platform-browser`, all optional peers on the same range. Below 20 the failure is a link
|
|
77
|
+
error, not a missing helper: `/angular` imports `ɵSIGNAL` (Angular 18+) as a value on the first line
|
|
78
|
+
of its bundle, so on 16 and 17 the whole entry fails to load, and `/bun-angular` imports
|
|
79
|
+
`provideZonelessChangeDetection` (Angular 20+; called `provideExperimentalZonelessChangeDetection`
|
|
80
|
+
in 18 and 19), so on anything older the preload throws before the first spec. Angular 19 and
|
|
81
|
+
everything under it is out of Angular's own support window as well, so the floor cuts nothing that
|
|
82
|
+
still gets fixes. `@angular/platform-browser` is a declared peer since this major — `By` in the
|
|
83
|
+
directive matchers and `platformBrowserTesting()` in the Bun preload are both value imports of it,
|
|
84
|
+
and under pnpm's isolated layout it never resolved before.
|
|
85
|
+
|
|
69
86
|
The package is **ESM**. Only `vitest-auto-spy/node` and `vitest-auto-spy/eslint-plugin` also ship a
|
|
70
87
|
CommonJS build; every other subpath is ESM-only (a `require()` of a Vitest-backed entry always threw —
|
|
71
88
|
Vitest refuses to be required).
|
|
@@ -1135,7 +1152,7 @@ setSpyEngine('runner'); // every double built afterwards is `vi.fn()` per method
|
|
|
1135
1152
|
```
|
|
1136
1153
|
|
|
1137
1154
|
**On Vitest 5 the registry story changes, and this library follows it.** Vitest 5 keeps registered
|
|
1138
|
-
mocks behind `WeakRef`s and its `clearAllMocks()` visits only the ones
|
|
1155
|
+
mocks behind `WeakRef`s and its `clearAllMocks()` visits only the ones _called_ since the last
|
|
1139
1156
|
sweep — so a spy that is not a `vi.fn()` has to make itself reachable to that sweep deliberately. It
|
|
1140
1157
|
does, which is why `vi.clearAllMocks()`, `vi.resetAllMocks()` and the `clearMocks: true` default
|
|
1141
1158
|
(new in Vitest 5) clear these doubles exactly as they clear the runner's own. Nothing in a spec
|
|
@@ -1145,6 +1162,12 @@ What Vitest 5 does break is the runner's own doing, not this library's: with `cl
|
|
|
1145
1162
|
default, a test asserting on a call that an **earlier** test or a `beforeAll` recorded now reads
|
|
1146
1163
|
zero. Count it in a plain variable rather than in the spy, or set `clearMocks: false`.
|
|
1147
1164
|
|
|
1165
|
+
**The one exception to "nothing in a spec changes".** `setupAutoSpy({ pruneMockRegistry: true })`
|
|
1166
|
+
discovers long-lived mocks by walking the runner's registry, which Vitest 5 no longer exposes — the
|
|
1167
|
+
automatic half is a no-op there. `keepMockRegistered(mock)` works on every version and is the fix;
|
|
1168
|
+
recommend it whenever a suite on Vitest 5 loses a shared mock's implementation to
|
|
1169
|
+
`vi.resetAllMocks()`.
|
|
1170
|
+
|
|
1148
1171
|
`getSpyEngine()` reports the current one, `'auto-spy'` is the default, and doubles already built keep
|
|
1149
1172
|
the engine they were built with. The switch is Vitest-only: on Bun and `node:test` the runner's own
|
|
1150
1173
|
matchers recognise only the runner's own mocks, so those adapters keep using them.
|
|
@@ -1479,6 +1502,14 @@ For the three observers, prefer the purpose-built stubs (§13). For `AbortContro
|
|
|
1479
1502
|
in a jsdom run for a reason involving none of the three parties in the stack trace — use
|
|
1480
1503
|
`stubAbortController()`.
|
|
1481
1504
|
|
|
1505
|
+
The stub carries `AbortSignal.abort()`, `AbortSignal.timeout()` and `AbortSignal.any()` as well —
|
|
1506
|
+
the three statics are how modern code makes a signal without a controller, `fetch(url, { signal:
|
|
1507
|
+
AbortSignal.timeout(5_000) })` most of all. `timeout()` aborts through `setTimeout`, so
|
|
1508
|
+
`vi.useFakeTimers()` drives it exactly as it drives the platform's, and it aborts with a
|
|
1509
|
+
`TimeoutError` rather than an `AbortError` because that is the distinction the platform draws. A
|
|
1510
|
+
signal's `reason` is the platform's `DOMException`, so code branching on
|
|
1511
|
+
`signal.reason.name === 'AbortError'` takes the same branch it takes in a browser.
|
|
1512
|
+
|
|
1482
1513
|
### `<video>` and `<audio>`
|
|
1483
1514
|
|
|
1484
1515
|
jsdom implements them as a shell: `play()` throws, `duration` is `NaN` and is an accessor with no
|
|
@@ -1499,6 +1530,10 @@ expect(media.play).toHaveBeenCalledTimes(1);
|
|
|
1499
1530
|
|
|
1500
1531
|
State is per element, so an ad and the content report different durations.
|
|
1501
1532
|
|
|
1533
|
+
`currentTime` is a get/set pair, so a player restarting itself with `video.currentTime = 0` reaches
|
|
1534
|
+
the record and fires `timeupdate` too — `media.set()` is not the only way in, and the component's own
|
|
1535
|
+
handler runs where it used to stay unrun while the assertion read the new value.
|
|
1536
|
+
|
|
1502
1537
|
### A module mock that did nothing
|
|
1503
1538
|
|
|
1504
1539
|
`vi.mock()` is the one thing in a ported suite that fails **silently**. Under a bundler
|
|
@@ -1541,7 +1576,8 @@ const myService = injectSpy(MyService); // Spy<MyService>
|
|
|
1541
1576
|
|
|
1542
1577
|
`provideAutoSpy` defaults to `lazySpies: true` (the plain `createSpyFromClass` does not). Pass
|
|
1543
1578
|
`{ lazySpies: false }` to opt out. The spies never touch `NgZone`, so they work zoneless and with
|
|
1544
|
-
zone.js alike.
|
|
1579
|
+
zone.js alike. The entry needs **Angular >= 20** (§1); on 16 or 17 it does not link at all, because
|
|
1580
|
+
`ɵSIGNAL` is not there to import.
|
|
1545
1581
|
|
|
1546
1582
|
### The same thing as fixtures — `extendWithAutoSpies` (Vitest 4.1+)
|
|
1547
1583
|
|
|
@@ -2066,6 +2102,11 @@ if (process.env['SPEC_TIMING']) {
|
|
|
2066
2102
|
preload = ["vitest-auto-spy/bun-angular"]
|
|
2067
2103
|
```
|
|
2068
2104
|
|
|
2105
|
+
The preload imports `provideZonelessChangeDetection` and `platformBrowserTesting` as values, so it
|
|
2106
|
+
wants **Angular >= 20** and a declared `@angular/platform-browser`; on 18 or 19 it throws while
|
|
2107
|
+
loading, because the zoneless provider was still called
|
|
2108
|
+
`provideExperimentalZonelessChangeDetection` there.
|
|
2109
|
+
|
|
2069
2110
|
It re-exports everything in this section except `registerSignalMatchers`,
|
|
2070
2111
|
`registerResourceMatchers`, `mockSignalProp` / `mockResourceProp` and the TestBed diagnostics — the
|
|
2071
2112
|
matchers and diagnostics need the runner's `expect.extend` and suite-level hooks, and the `mock*Prop`
|
|
@@ -2222,7 +2263,7 @@ Spies created **before** the call stay there too. `mock.reset()` in `afterEach`
|
|
|
2222
2263
|
|
|
2223
2264
|
**A `node:test` spy now prints under its method name.** `mock.fn()` takes no name and has no
|
|
2224
2265
|
`mockName()`, so a spy inherited the library's internal dispatcher and read back as
|
|
2225
|
-
`[Function: dispatch]` everywhere. The adapter names the
|
|
2266
|
+
`[Function: dispatch]` everywhere. The adapter names the _implementation_ at creation and lets
|
|
2226
2267
|
`mock.fn()` carry that name onto the mock, which is where a `node:test` mock takes its `name` from;
|
|
2227
2268
|
`displayName` is set on the mock alongside it. The name survives `mock.reset()`, `mock.restore()`,
|
|
2228
2269
|
`resetCalls()` and a `mockImplementation()` swap, and it is what `node:assert` diffs, what
|
|
@@ -2480,8 +2521,8 @@ packages, which a subpath export can never be.
|
|
|
2480
2521
|
| `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) |
|
|
2481
2522
|
| `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)` |
|
|
2482
2523
|
| 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) |
|
|
2483
|
-
| `
|
|
2484
|
-
| `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>`
|
|
2524
|
+
| `names method(s) that are not on the class prototype` | typo, or an instance-field callable | fix the name, or move it to `instanceMethodsToSpyOn` |
|
|
2525
|
+
| `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) |
|
|
2485
2526
|
| `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) |
|
|
2486
2527
|
| `was configured with 'mustBeCalledWith'` | the code called the spy with other arguments | that is the assertion firing — fix the code, or relax to `calledWith` |
|
|
2487
2528
|
| `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 |
|
|
@@ -2509,7 +2550,7 @@ packages, which a subpath export can never be.
|
|
|
2509
2550
|
| a spy is never called, no warning | the method is an instance field, not on the prototype | `instanceMethodsToSpyOn`, or `createAutoMock<T>()` |
|
|
2510
2551
|
| `Cannot access '__vi_import_N__' before initialization` | `vi.mock()` on `@angular/core` or a relative path | you cannot mock it — the specs are bundled. Assert the result instead |
|
|
2511
2552
|
| `AggregateError at Object.dispatchError`, for a request nothing asserts on | jsdom really served an `XMLHttpRequest` — `blockNetwork` used to cover only `fetch` | `setupAutoSpy({ blockNetwork: true })`, or `{ xhr: 'empty' }` for tracker pings (§10) |
|
|
2512
|
-
| `localStorage.setItem is not a function`, or `localStorage` undefined | Node's own Web Storage made Vitest's global filter skip the environment's — Node 25 and up, jsdom and happy-dom alike
|
|
2553
|
+
| `localStorage.setItem is not a function`, or `localStorage` undefined | Node's own Web Storage made Vitest's global filter skip the environment's — Node 25 and up, jsdom and happy-dom alike | on by default in `setupAutoSpy()`; `restoreWebStorage()` on its own (§10) |
|
|
2513
2554
|
| `Schedulers cannot synchronously execute watches while scheduling` | a timer from a **previous** file, under `isolate: false` | track and cancel pending timers/frames in the setup file (§10) |
|
|
2514
2555
|
| `signal read during notification phase` | same — a stray `requestAnimationFrame` callback | same |
|
|
2515
2556
|
| an assertion error printed to stderr, every test green and the run exiting 0 | zone.js swallowed a rejection nobody handled | `setupAutoSpy({ strayRejections: true })` fails the test it surfaced in (§10) |
|
|
@@ -2740,6 +2781,10 @@ migrated by hand. Run it after `--write`, and again after any manual clean-up. `
|
|
|
2740
2781
|
select transforms by id, `--list` prints them. Full reference:
|
|
2741
2782
|
<https://asdalexey.github.io/vitest-auto-spy/utilities/codemod>.
|
|
2742
2783
|
|
|
2784
|
+
Past 50 000 files the repository scan truncates and the run says so — *Nothing left to migrate* off a
|
|
2785
|
+
truncated list is a claim about a tree the tool never looked at. `VITEST_AUTO_SPY_SCAN_CAP` raises
|
|
2786
|
+
the cap.
|
|
2787
|
+
|
|
2743
2788
|
### If you are writing a codemod over specs
|
|
2744
2789
|
|
|
2745
2790
|
Two traps, both found the hard way on rxjs-heavy code.
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ faster at suite scale ([benchmarks](#benchmarks)) — and for
|
|
|
23
23
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
24
24
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
25
25
|
[](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
|
|
26
|
-
[](#install)
|
|
27
27
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
28
28
|
[](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
|
|
29
29
|
[](./LICENSE)
|
|
@@ -86,6 +86,23 @@ faster at suite scale ([benchmarks](#benchmarks)) — and for
|
|
|
86
86
|
- 🤖 Built for AI agents too — one `npx vitest-auto-spy init` writes the pointer into the files your agents actually read and specialises it for this repository, backed by an offline [`AGENTS.md`](#using-this-library-with-an-ai-agent) inside the package, a [per-agent map](#which-file-your-agent-reads) for **Claude Code**, **OpenAI Codex**, **GLM/z.ai**, **Cursor**, **Copilot**, **Gemini CLI** and the rest, `llms.txt` on the docs site, a Claude Code skill, and errors that name their own fix
|
|
87
87
|
- 🟢 100% test coverage, **zero runtime dependencies** (in-tree arg serializer, no `javascript-stringify`)
|
|
88
88
|
|
|
89
|
+
## New in 5.0
|
|
90
|
+
|
|
91
|
+
A major that changes **no helper, no option and no runtime behaviour**. What it changes is what the
|
|
92
|
+
package claims to run on, because both old peer ranges were claims the code could not keep — the
|
|
93
|
+
full list, with what to do about each, is in
|
|
94
|
+
[Upgrading to 5.0](https://asdalexey.github.io/vitest-auto-spy/upgrading-5).
|
|
95
|
+
|
|
96
|
+
| What you get | Verified |
|
|
97
|
+
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
98
|
+
| **The Angular range stops being false.** `>=16.0.0` admitted four majors on which an entry point cannot link at all: `ɵSIGNAL` is a value import and arrived in 18, `provideZonelessChangeDetection` in 20. The floor is now `>=20` | Angular 16 through 22 downloaded, their real `export { … }` lists parsed out of `fesm2022/*.mjs`; Angular 19 went EOL on 2026-05-19 |
|
|
99
|
+
| **`@angular/platform-browser` is a declared peer.** `/angular` has always imported `By` from it as a value, and under pnpm's isolated layout that never resolved | undeclared → optional peer on the same `>=20` range |
|
|
100
|
+
| **The rxjs range stops promising rxjs 8.** Six operators came from `rxjs/operators`, a path rxjs 8 removes; they now come from the root entry, where rxjs re-exported them in 7.2, and the floor moved with the specifier | read out of rxjs 7.2.0's own `dist/types/index.d.ts`; every Angular major from 16 to 22 already peers above it |
|
|
101
|
+
| **`flushEffects()` is one line.** The `ApplicationRef.tick()` fallback and the spec that deleted `TestBed.tick` at runtime to cover it are both gone | `TestBed.tick()` direct; coverage still 100 % |
|
|
102
|
+
|
|
103
|
+
For almost everyone the whole upgrade is a version number: every Angular still supported by Angular
|
|
104
|
+
satisfies the new floor, and only a project pinning `rxjs@7.0` or `7.1` on purpose has to move.
|
|
105
|
+
|
|
89
106
|
## New in 4.0
|
|
90
107
|
|
|
91
108
|
A major with **one job**: take out of your project the weight this library was making it carry.
|
|
@@ -210,8 +227,9 @@ npm i -D vitest-auto-spy
|
|
|
210
227
|
|
|
211
228
|
| Tool | Minimum |
|
|
212
229
|
| ---------- | --------------------------------------------------------------------------------- |
|
|
213
|
-
| Node.js | ≥ 18
|
|
230
|
+
| Node.js | ≥ 22 — 18 and 20 are EOL; CI exercises 22, 24 and 26 |
|
|
214
231
|
| Vitest | ≥ 2.1 (required peer) — one install covers 2.1 through 5.x |
|
|
232
|
+
| Angular | ≥ 20 for the Angular entry points — optional peer, nothing else needs it |
|
|
215
233
|
| Bun | ≥ 1.4 for `vitest-auto-spy/bun-angular`; any recent Bun for `vitest-auto-spy/bun` |
|
|
216
234
|
| TypeScript | ≥ 4.7 for the typed helpers (plain JS works too, just untyped) |
|
|
217
235
|
|
|
@@ -228,7 +246,12 @@ mocks that were called, so a spy engine that is not `vi.fn()` had to make itself
|
|
|
228
246
|
sweep; and the `Matchers` interface grew a second type parameter, so the bundled matchers declare
|
|
229
247
|
themselves on Chai's `Assertion` instead and keep typing on 4 and 5 alike. The one thing Vitest 5
|
|
230
248
|
can still break is its own doing: `clearMocks` is on by default there, so a spec asserting on a call
|
|
231
|
-
recorded by an
|
|
249
|
+
recorded by an _earlier_ test now reads zero. Record it in a counter, not in the spy.
|
|
250
|
+
|
|
251
|
+
One feature of this package does need a line changed. `setupAutoSpy({ pruneMockRegistry: true })`
|
|
252
|
+
finds long-lived mocks by walking the runner's own registry, and Vitest 5 no longer exposes it — so
|
|
253
|
+
on Vitest 5 the automatic half does nothing and a mock that must survive `vi.resetAllMocks()` has to
|
|
254
|
+
say so: `keepMockRegistered(vi.fn().mockReturnThis())`. Everything after that mark behaves as before.
|
|
232
255
|
|
|
233
256
|
Measured 2026-09-06 on 40 spec files, 800 tests, 400 spied methods per file, v8 coverage on,
|
|
234
257
|
Node v24.19.0, median of five runs: the same suite takes **1383 ms on Vitest 4.1.11 and 1276 ms on
|
|
@@ -237,11 +260,12 @@ shipped since 4.1 is worth another **−6.1 % on Vitest 4 and −8.1 % on Vitest
|
|
|
237
260
|
every method with the runner's own `vi.fn()` (`setSpyEngine('runner')`). End to end, the slowest
|
|
238
261
|
pairing to the fastest is 1473 ms → 1276 ms, **−13.4 %**.
|
|
239
262
|
|
|
240
|
-
Node **≥
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
263
|
+
Node **≥ 22** is the library's own floor now — Node 18 and 20 are both past end of life, and every
|
|
264
|
+
runner this library supports already needed more: Vitest 4 pulls in Vite 7 (`^20.19.0 ||
|
|
265
|
+
>=22.12.0`) and calls `crypto.hash` (added in Node 20.12), so Node 18 died with `TypeError:
|
|
266
|
+
crypto.hash is not a function` before a spec loads. Vitest 5 raises its own floor to `^22.12.0 ||
|
|
267
|
+
^24.0.0 || >=26.0.0`, and `@angular/build` 22 needs `^22.22.3 || ^24.15.0 || >=26.0.0`. CI tests
|
|
268
|
+
Node 22, 24 and 26, so 22 is what is actually exercised. Which version to run is measured in
|
|
245
269
|
[Performance → Which Node version](https://vitest-auto-spy.dev/core/performance#which-node-version):
|
|
246
270
|
the break is between 22 and 24, where a cold import more than halves.
|
|
247
271
|
|
|
@@ -255,14 +279,54 @@ the published package roughly in half.
|
|
|
255
279
|
|
|
256
280
|
### Peer dependencies
|
|
257
281
|
|
|
258
|
-
All peers are **provided by your project**; `rxjs` and `@angular
|
|
259
|
-
them only for the matching entry point. The package itself has **zero runtime
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
|
263
|
-
|
|
|
264
|
-
| `
|
|
265
|
-
|
|
|
282
|
+
All peers are **provided by your project**; `rxjs` and the three `@angular/*` packages are
|
|
283
|
+
**optional** — install them only for the matching entry point. The package itself has **zero runtime
|
|
284
|
+
dependencies**.
|
|
285
|
+
|
|
286
|
+
| Peer | Needed for | Optional? |
|
|
287
|
+
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | --------- |
|
|
288
|
+
| `vitest` | the default runner | no |
|
|
289
|
+
| `rxjs` | `vitest-auto-spy/rxjs` observable spies — `>=7.2`, **no upper bound** (the rxjs 8 line included); not needed to type-check a spy since 4.0.0 | yes |
|
|
290
|
+
| `@angular/core` | `vitest-auto-spy/angular` and `vitest-auto-spy/bun-angular` helpers — `>=20` | yes |
|
|
291
|
+
| `@angular/common` | `vitest-auto-spy/angular-http` — `>=20`, that entry only | yes |
|
|
292
|
+
| `@angular/platform-browser` | `By` and the directive matchers on `vitest-auto-spy/angular`, and the Bun preload's platform — `>=20` | yes |
|
|
293
|
+
|
|
294
|
+
**rxjs ≥ 7.2**, and that number moved because an import specifier did. The observable layer builds
|
|
295
|
+
its subjects out of six operators — `concatMap`, `delay`, `switchMap`, `take`, `takeUntil`,
|
|
296
|
+
`takeWhile` — and it took them from `rxjs/operators`, the legacy deep path. **rxjs 8 removes that
|
|
297
|
+
path entirely**, so the old open-ended `>=7.0.0` was promising a version the code could not have
|
|
298
|
+
served. The operators now come from the root `rxjs` entry, which is where rxjs itself re-exported
|
|
299
|
+
them in **7.2** — checked against rxjs 7.2.0's own `dist/types/index.d.ts`, not against its release
|
|
300
|
+
notes. `firstValueFrom`, which is what justified the old 7.0 floor, is still there; 7.2 is simply the
|
|
301
|
+
first version where every symbol this package imports exists at the specifier it imports it from.
|
|
302
|
+
|
|
303
|
+
It costs an Angular consumer nothing. Every Angular major from 16 to 22 declares its own rxjs peer
|
|
304
|
+
as `^6.5.3 || ^7.4.0`, which is already above what this asks — so only a project that pins `rxjs@7.0`
|
|
305
|
+
or `7.1` on purpose has anything to do.
|
|
306
|
+
|
|
307
|
+
**Angular ≥ 20**, and two symbols the shipped code imports **as values** are what set the number. A
|
|
308
|
+
missing value import is a link error, so the symptom is never one unavailable helper — it is an
|
|
309
|
+
entry that does not load:
|
|
310
|
+
|
|
311
|
+
- **`ɵSIGNAL` exists from Angular 18.** `runEffect()` reads it, and that import is the first line of
|
|
312
|
+
the `/angular` bundle, evaluated eagerly. On Angular 16 or 17 the entry fails to link, taking
|
|
313
|
+
`provideAutoSpy`, `injectSpy` and the rest with it — not just `runEffect`.
|
|
314
|
+
- **`provideZonelessChangeDetection` exists from Angular 20.** In 18 and 19 the same function was
|
|
315
|
+
named `provideExperimentalZonelessChangeDetection`; in 16 and 17 there was nothing to name.
|
|
316
|
+
`vitest-auto-spy/bun-angular` imports it by name, so below 20 `bun test` dies loading the preload,
|
|
317
|
+
before any spec file.
|
|
318
|
+
|
|
319
|
+
Angular 20 is also the oldest Angular that Angular still supports — six months active plus twelve
|
|
320
|
+
months LTS put 19 past end of life on **2026-05-19**, and 18, 17 and 16 before it. The technical
|
|
321
|
+
floor and the supported floor are the same number, so nothing still receiving fixes is cut off; the
|
|
322
|
+
range this replaces said `>=16.0.0`, which was a promise on versions where the main entry could not
|
|
323
|
+
link. No upper bound is set on purpose — one would force a release per Angular major and hand
|
|
324
|
+
`ERESOLVE` to whoever upgraded first.
|
|
325
|
+
|
|
326
|
+
`@angular/platform-browser` is declared for the first time here. `vitest-auto-spy/angular` has
|
|
327
|
+
always imported `By` from it as a value, and the Bun preload boots through `platformBrowserTesting()`.
|
|
328
|
+
Under npm's hoisted `node_modules` it resolved by accident — every Angular workspace has it — while
|
|
329
|
+
under pnpm's isolated layout it did not resolve at all. Declaring it makes the accident a contract.
|
|
266
330
|
|
|
267
331
|
## The CLI — `doctor`, `perf`, `codemod` and `init`
|
|
268
332
|
|
|
@@ -412,7 +476,9 @@ enough for `--from auto` to classify a file, for exactly the same reason — tha
|
|
|
412
476
|
|
|
413
477
|
**Dry-run by default**, so the first thing a repository sees is a diff it can reject. `--write`
|
|
414
478
|
applies it, `--only` / `--skip` select transforms by id, `--list` prints them together with the
|
|
415
|
-
generated entry-point table.
|
|
479
|
+
generated entry-point table. Past 50 000 files the scan truncates and says so, because *Nothing left
|
|
480
|
+
to migrate* off a truncated list is a claim about a tree it never looked at;
|
|
481
|
+
`VITEST_AUTO_SPY_SCAN_CAP` raises the cap.
|
|
416
482
|
|
|
417
483
|
What it deliberately does not do is guess. A `jest.*` member with no `vi` twin — `requireMock`,
|
|
418
484
|
`replaceProperty`, `createMockFromModule`, `jest.setTimeout`, `requireActual` — is **left exactly as
|
|
@@ -1079,26 +1145,26 @@ the conditional types that pick helpers from a return type — see
|
|
|
1079
1145
|
The library ships a framework-agnostic core plus runtime and framework layers, so a plain
|
|
1080
1146
|
Node / Bun / React / Vue project pulls **neither rxjs nor Angular into its runtime bundle**:
|
|
1081
1147
|
|
|
1082
|
-
| Import | Provides | Pulls in
|
|
1083
|
-
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1084
|
-
| `vitest-auto-spy` | `createSpyFromClass`, `createAutoMock`, `createFunctionSpy`, sync + promise + accessor spies, `errorHandler`, types | `vitest`
|
|
1085
|
-
| `vitest-auto-spy/rxjs` | observable spies (`nextWith`, `nextWithValues`, `observablePropsToSpyOn`, …) and `createObservableWithValues` | `rxjs`
|
|
1086
|
-
| `vitest-auto-spy/angular` | `provideAutoSpy`, `injectSpy`, `renderShallow`, `createWithAutoSpies`, `stable`/`flushEffects`, `settleResource`, `mockResourceProp`, the `mock*Prop` helpers, signal & resource matchers, TestBed diagnostics | `@angular/core` | ✅ |
|
|
1087
|
-
| `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest`, `expectNoRequest`, `verifyNoPendingRequests` — the `httpResource()` / `HttpClient` recipe in two lines, settling included | `@angular/common`
|
|
1088
|
-
| `vitest-auto-spy/bun` | the same core, driven by Bun's `bun:test` mocks | `bun:test`
|
|
1089
|
-
| `vitest-auto-spy/bun-angular` | Angular's `TestBed` under `bun test` — DOM, JIT `templateUrl` resolution and a zoneless environment, from one preload | `bun:test`, `@angular/core` | ✅ |
|
|
1090
|
-
| `vitest-auto-spy/node` | the same core, driven by `node:test`'s `mock.fn()`, plus `trackNodeMocks()` — a private `MockTracker` so dropped spies are actually freed | `node:test`
|
|
1091
|
-
| `vitest-auto-spy/nestjs` | `provideAutoSpy`, `injectSpy` for `Test.createTestingModule` | — (your `@nestjs/*`)
|
|
1092
|
-
| `vitest-auto-spy/react` | the core, with a natural import for React Testing Library suites | — (your `react`)
|
|
1093
|
-
| `vitest-auto-spy/vue` | `provideAutoSpy` for `global.provide` + Pinia store spying | — (your `vue`/`pinia`)
|
|
1094
|
-
| `vitest-auto-spy/svelte` | the core, with a natural import for Svelte suites | — (your `svelte`)
|
|
1095
|
-
| `vitest-auto-spy/console` | `consoleInfoSpy` & friends — silent typed spies over the global `console`, installed on import | `vitest`
|
|
1096
|
-
| `vitest-auto-spy/jasmine` | the drop-in surface for a `jasmine-auto-spies` suite — `.and` / `.calls` / `.withArgs` on every spy, `createSpyObj`, the `jasmine` namespace, `registerJasmineMatchers` | `vitest`
|
|
1097
|
-
| `vitest-auto-spy/jasmine-compat` | `enableJasmineCompat()` alone — the same `.and` / `.calls` layer, registering no adapter, for `bun test` and `node --test` | — (your runner)
|
|
1098
|
-
| `vitest-auto-spy/observer-spy` | `subscribeSpyTo` / `ObserverSpy` / `SubscriberSpy` — the `@hirez_io/observer-spy` surface, so `/rxjs` does not carry it | `rxjs`
|
|
1099
|
-
| `vitest-auto-spy/setup` | `setupAutoSpy()` — property restore, duplicate-copy detection and mock-registry hygiene in one call; `setupFakeTimers()` / `advanceTimers()` | `vitest`
|
|
1100
|
-
| `vitest-auto-spy/zone` | `fakeAsync` / `waitForAsync` on Vitest — the ProxyZone patch `zone.js/testing` does not ship. Reads the `zone.js` **you** loaded; imports none of it | — (your `zone.js`)
|
|
1101
|
-
| `vitest-auto-spy/eslint-plugin` | the lint rules that steer a suite onto these helpers | — (your `eslint`)
|
|
1148
|
+
| Import | Provides | Pulls in | Status |
|
|
1149
|
+
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | :----: |
|
|
1150
|
+
| `vitest-auto-spy` | `createSpyFromClass`, `createAutoMock`, `createFunctionSpy`, sync + promise + accessor spies, `errorHandler`, types | `vitest` | ✅ |
|
|
1151
|
+
| `vitest-auto-spy/rxjs` | observable spies (`nextWith`, `nextWithValues`, `observablePropsToSpyOn`, …) and `createObservableWithValues` | `rxjs` | ✅ |
|
|
1152
|
+
| `vitest-auto-spy/angular` | `provideAutoSpy`, `injectSpy`, `renderShallow`, `createWithAutoSpies`, `stable`/`flushEffects`, `settleResource`, `mockResourceProp`, the `mock*Prop` helpers, signal & resource matchers, TestBed diagnostics | `@angular/core`, `@angular/platform-browser` | ✅ |
|
|
1153
|
+
| `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest`, `expectNoRequest`, `verifyNoPendingRequests` — the `httpResource()` / `HttpClient` recipe in two lines, settling included | `@angular/common` | ✅ |
|
|
1154
|
+
| `vitest-auto-spy/bun` | the same core, driven by Bun's `bun:test` mocks | `bun:test` | ✅ |
|
|
1155
|
+
| `vitest-auto-spy/bun-angular` | Angular's `TestBed` under `bun test` — DOM, JIT `templateUrl` resolution and a zoneless environment, from one preload | `bun:test`, `@angular/core`, `@angular/platform-browser` | ✅ |
|
|
1156
|
+
| `vitest-auto-spy/node` | the same core, driven by `node:test`'s `mock.fn()`, plus `trackNodeMocks()` — a private `MockTracker` so dropped spies are actually freed | `node:test` | ✅ |
|
|
1157
|
+
| `vitest-auto-spy/nestjs` | `provideAutoSpy`, `injectSpy` for `Test.createTestingModule` | — (your `@nestjs/*`) | ✅ |
|
|
1158
|
+
| `vitest-auto-spy/react` | the core, with a natural import for React Testing Library suites | — (your `react`) | ✅ |
|
|
1159
|
+
| `vitest-auto-spy/vue` | `provideAutoSpy` for `global.provide` + Pinia store spying | — (your `vue`/`pinia`) | ✅ |
|
|
1160
|
+
| `vitest-auto-spy/svelte` | the core, with a natural import for Svelte suites | — (your `svelte`) | ✅ |
|
|
1161
|
+
| `vitest-auto-spy/console` | `consoleInfoSpy` & friends — silent typed spies over the global `console`, installed on import | `vitest` | ✅ |
|
|
1162
|
+
| `vitest-auto-spy/jasmine` | the drop-in surface for a `jasmine-auto-spies` suite — `.and` / `.calls` / `.withArgs` on every spy, `createSpyObj`, the `jasmine` namespace, `registerJasmineMatchers` | `vitest` | ✅ |
|
|
1163
|
+
| `vitest-auto-spy/jasmine-compat` | `enableJasmineCompat()` alone — the same `.and` / `.calls` layer, registering no adapter, for `bun test` and `node --test` | — (your runner) | ✅ |
|
|
1164
|
+
| `vitest-auto-spy/observer-spy` | `subscribeSpyTo` / `ObserverSpy` / `SubscriberSpy` — the `@hirez_io/observer-spy` surface, so `/rxjs` does not carry it | `rxjs` | ✅ |
|
|
1165
|
+
| `vitest-auto-spy/setup` | `setupAutoSpy()` — property restore, duplicate-copy detection and mock-registry hygiene in one call; `setupFakeTimers()` / `advanceTimers()` | `vitest` | ✅ |
|
|
1166
|
+
| `vitest-auto-spy/zone` | `fakeAsync` / `waitForAsync` on Vitest — the ProxyZone patch `zone.js/testing` does not ship. Reads the `zone.js` **you** loaded; imports none of it | — (your `zone.js`) | ✅ |
|
|
1167
|
+
| `vitest-auto-spy/eslint-plugin` | the lint rules that steer a suite onto these helpers | — (your `eslint`) | ✅ |
|
|
1102
1168
|
|
|
1103
1169
|
✅ all entry points published (see [Availability](#availability)).
|
|
1104
1170
|
|
|
@@ -1341,26 +1407,26 @@ time budget — these cases allocate doubles by the tens of thousands, and GC co
|
|
|
1341
1407
|
created rather than elapsed time, so a fixed time budget would have handed a faster arm more
|
|
1342
1408
|
allocations and made it pay for its own speed.
|
|
1343
1409
|
|
|
1344
|
-
**Every micro-benchmark table is this library's**, and the margin is against the
|
|
1410
|
+
**Every micro-benchmark table is this library's**, and the margin is against the _best_ other arm in
|
|
1345
1411
|
that table. The three class sizes are measured project profiles, not round numbers — across four
|
|
1346
1412
|
private Angular suites (~2 700 spec files, 2 742 doubles built from a class) the median service has
|
|
1347
1413
|
5-8 methods and the spec touches 1 of them:
|
|
1348
1414
|
|
|
1349
|
-
| Case (micro)
|
|
1350
|
-
|
|
|
1351
|
-
| small project — 6 methods, 1 called
|
|
1352
|
-
| medium project — 14 methods, 2 called
|
|
1353
|
-
| large project — 45 methods, 2 called
|
|
1354
|
-
| worst case — all 14 of 14 methods called
|
|
1355
|
-
| worst case — all 45 of 45 methods called
|
|
1356
|
-
| double from a type — 2 / 10 / 40 members
|
|
1357
|
-
| deep double, 3 levels, leaf called
|
|
1358
|
-
| configure a return + 3 calls — class / type
|
|
1359
|
-
| `calledWith` dispatch, 2 configured + 1 miss |
|
|
1415
|
+
| Case (micro) | 4.0 | 4.1 | best other arm | lead |
|
|
1416
|
+
| -------------------------------------------- | -------------------------------: | -------------------------: | -------------------------------------------: | ------------: |
|
|
1417
|
+
| small project — 6 methods, 1 called | — | **1.42 µs** | 6.83 µs hand-written | 4.82x |
|
|
1418
|
+
| medium project — 14 methods, 2 called | — | **2.67 µs** | 15.92 µs hand-written | 5.97x |
|
|
1419
|
+
| large project — 45 methods, 2 called | — | **5.79 µs** | 52.79 µs hand-written | 9.11x |
|
|
1420
|
+
| worst case — all 14 of 14 methods called | 18.92 µs (0.66x, a loss) | **8.17 µs** | 17.92 µs hand-written | 2.19x |
|
|
1421
|
+
| worst case — all 45 of 45 methods called | 75.33 µs (0.71x, a loss) | **26.12 µs** | 62.04 µs hand-written | 2.37x |
|
|
1422
|
+
| double from a type — 2 / 10 / 40 members | 3.58 / 18.17 / 72.88 µs (a loss) | **1.00 / 4.67 / 18.92 µs** | 2.79 / 13.83 / 56.79 µs vitest-mock-extended | 2.79-3.00x |
|
|
1423
|
+
| deep double, 3 levels, leaf called | 8.83 µs (0.61x, a loss) | **2.29 µs** | 5.46 µs vitest-mock-extended | 2.38x |
|
|
1424
|
+
| configure a return + 3 calls — class / type | 3.29 / 2.08 µs (type a loss) | **2.21 / 0.71 µs** | 16.38 µs hand-written / 1.58 µs @golevelup | 7.41x / 2.24x |
|
|
1425
|
+
| `calledWith` dispatch, 2 configured + 1 miss | 0.54 µs (parity) | **0.17 µs** | 0.54 µs vitest-mock-extended | 3.25x |
|
|
1360
1426
|
|
|
1361
1427
|
Six of those rows were losses and one was parity. What changed is that a method spy is no longer a
|
|
1362
1428
|
`vi.fn()` — see [The spy engine](#the-spy-engine) — plus one thing that is not the engine: a
|
|
1363
|
-
`calledWith(x)` of a single primitive argument used to be rendered into a string key on
|
|
1429
|
+
`calledWith(x)` of a single primitive argument used to be rendered into a string key on _every call_
|
|
1364
1430
|
of that spy, where a `Map` keyed by the value does the same lookup with no allocation.
|
|
1365
1431
|
|
|
1366
1432
|
The three class sizes changed in the same release, from round numbers to measured profiles, so the
|
|
@@ -1373,10 +1439,10 @@ vi.fn() per method` arm is in the table precisely so its size stays visible.
|
|
|
1373
1439
|
the 4.1 build, 20-method class, `isolate: true`, coverage on, 3 rounds per cell after a discarded
|
|
1374
1440
|
warm-up, medians of the three):
|
|
1375
1441
|
|
|
1376
|
-
| Comparison
|
|
1377
|
-
|
|
|
1378
|
-
| vitest-auto-spy
|
|
1379
|
-
| hand-written `vi.fn()`
|
|
1442
|
+
| Comparison | 1 000 tests | 3 000 tests | 10 000 tests |
|
|
1443
|
+
| --------------------------- | -------------: | -------------: | --------------: |
|
|
1444
|
+
| vitest-auto-spy | 1.33 s | 2.86 s | 10.02 s |
|
|
1445
|
+
| hand-written `vi.fn()` | 1.31 s (0.99×) | 2.85 s (1.00×) | 9.25 s (0.92×) |
|
|
1380
1446
|
| @bugsplat/vitest-auto-spies | 2.00 s (1.50×) | 4.61 s (1.61×) | 15.45 s (1.54×) |
|
|
1381
1447
|
|
|
1382
1448
|
Hand-written doubles are still cheaper — **about 3% at the median**, down from 10-15% before 4.1,
|
|
@@ -1387,11 +1453,11 @@ suite swamps it.
|
|
|
1387
1453
|
|
|
1388
1454
|
**Memory, `isolate: false`, 100-method class, 10 000 tests, peak RSS:**
|
|
1389
1455
|
|
|
1390
|
-
| Arm
|
|
1391
|
-
|
|
|
1392
|
-
| hand-written `vi.fn()`
|
|
1393
|
-
| vitest-auto-spy, default
|
|
1394
|
-
| vitest-auto-spy, `lazySpies: 'proxy'` |
|
|
1456
|
+
| Arm | Peak RSS |
|
|
1457
|
+
| ------------------------------------- | -------: |
|
|
1458
|
+
| hand-written `vi.fn()` | 6366 MB |
|
|
1459
|
+
| vitest-auto-spy, default | 2103 MB |
|
|
1460
|
+
| vitest-auto-spy, `lazySpies: 'proxy'` | 1851 MB |
|
|
1395
1461
|
|
|
1396
1462
|
Hand-written doubles use 3.0x the default's peak RSS here — the difference between finishing and an
|
|
1397
1463
|
OOM'd worker on a large suite.
|
|
@@ -1413,7 +1479,7 @@ Full write-up, methodology and the complete suite-scale tables:
|
|
|
1413
1479
|
|
|
1414
1480
|
### Reproducing the numbers
|
|
1415
1481
|
|
|
1416
|
-
Node **>=
|
|
1482
|
+
Node **>=22** (the project floor); the numbers above were captured on Node v24.19.0. The steps are
|
|
1417
1483
|
identical on Windows, macOS and Linux — same Node, same npm, same commands, nothing OS-specific to
|
|
1418
1484
|
call out:
|
|
1419
1485
|
|
|
@@ -2508,7 +2574,7 @@ single-purpose utility you can pick up independently — they all ride on the sa
|
|
|
2508
2574
|
| `installPerTest(install)` | `/setup` | Re-install a stub before every test of the block — a `describe`-level stub is restored away after the first |
|
|
2509
2575
|
| `setupAngularTestEnv(opts)` | `/angular` | Zone and zoneless spec files in one worker, switching platforms per file |
|
|
2510
2576
|
| `restoreTimerGlobals()` | `/setup` | Put back timer globals that uninstalling the fakes deleted rather than restored |
|
|
2511
|
-
| `restoreWebStorage(options?)`
|
|
2577
|
+
| `restoreWebStorage(options?)` | `/setup` | Give `globalThis` a `localStorage` / `sessionStorage` that work when the runner's copy never arrived ([details](#test-run-hygiene)) |
|
|
2512
2578
|
| `trackMockRegistry()` / `keepMockRegistered(mock)` / `restoreLongLivedImplementations()` | `/setup` | Keep @vitest/spy's mock registry to the mocks that outlive a file; mark one the split would miss; put back an implementation a cross-file `vi.resetAllMocks()` dropped ([details](#test-run-hygiene)) |
|
|
2513
2579
|
| `trackNodeMocks()` / `pruneNodeMocks()` / `countNodeMocks()` | `/node` | Give this library its own `node:test` `MockTracker` so a dropped spy is freed — 21× less retained heap; sweep by hand, and read the count back |
|
|
2514
2580
|
| `setSpyEngine(engine)` / `getSpyEngine()` | `/setup` | Build method spies from this library's own mock (`'auto-spy'`, the default) or from `vi.fn()` (`'runner'`) ([details](#the-spy-engine)) |
|
|
@@ -2752,10 +2818,10 @@ expensive to diagnose when it is missing. The first three are on by default:
|
|
|
2752
2818
|
so the 404 is never written and a routing mistake is reported as a slow test.
|
|
2753
2819
|
11. **Web Storage the runner never handed over.** On by default, because it only ever repairs.
|
|
2754
2820
|
Vitest copies a DOM environment's globals onto `globalThis` behind `if (k in global) return
|
|
2755
|
-
|
|
2821
|
+
KEYS.includes(k)`, and neither `localStorage` nor `sessionStorage` is in `KEYS` — they used to
|
|
2756
2822
|
arrive only because Node put neither on `globalThis`, so the first half was false. Node's own
|
|
2757
2823
|
Web Storage made the key exist, and now the environment's storage never arrives: `setItem is
|
|
2758
|
-
|
|
2824
|
+
not a function` on Node 25, `undefined` on Node 26, under jsdom and happy-dom alike, since the
|
|
2759
2825
|
filter runs before either. A suite stays green with this broken — only the specs that touch
|
|
2760
2826
|
storage fail — so it arrives as "CI moved to a new Node and eleven unrelated specs died". The
|
|
2761
2827
|
repair decides by using the storage rather than looking at it, replaces only one that cannot
|
|
@@ -2773,7 +2839,7 @@ expensive to diagnose when it is missing. The first three are on by default:
|
|
|
2773
2839
|
| `guardGlobals` | `'off'` | Report a test that redefines a global property as non-configurable |
|
|
2774
2840
|
| `globalFakeTimers` | `false` | Fake timers for every test **and between them** — Jest's `enableGlobally` |
|
|
2775
2841
|
| `restoreTimerGlobals` | `true` | Put back timer globals that uninstalling the fakes deleted |
|
|
2776
|
-
| `restoreWebStorage` | `true` | Give the run a `localStorage` / `sessionStorage` that work
|
|
2842
|
+
| `restoreWebStorage` | `true` | Give the run a `localStorage` / `sessionStorage` that work |
|
|
2777
2843
|
| `pruneMockRegistry` | `false` | Keep @vitest/spy's ever-growing mock registry to the mocks that outlive a file |
|
|
2778
2844
|
| `hookTimeoutHint` | `true` | Explain a hook that ran out of `hookTimeout` while `testTimeout` is larger |
|
|
2779
2845
|
| `frozenClockHint` | `true` | Explain a timeout that happened because nothing advanced the fake clock |
|
|
@@ -3192,7 +3258,7 @@ another, and the next one is in that same file.
|
|
|
3192
3258
|
| `asInstance(spy)` / `asSpy(instance)` | The two named views between `Spy<T>` / `DeepMockProxy<T>` and `T`, instead of `as any` |
|
|
3193
3259
|
| `createSpyClass(Class, config?)` | A spy that can be called with `new`; records `calls` and `instances` |
|
|
3194
3260
|
| `mockConstructor(factory, name?)` / `stubConstructor(obj, key, factory)` | A runner mock that is also a constructor — for a global or an SDK with no class at runtime |
|
|
3195
|
-
| `stubAbortController()` | A realm-consistent `AbortController` / `AbortSignal` for jsdom + zone.js
|
|
3261
|
+
| `stubAbortController()` | A realm-consistent `AbortController` / `AbortSignal` for jsdom + zone.js, statics (`abort`, `timeout`, `any`) included |
|
|
3196
3262
|
| `flushEventLoop(turns?)` / `settleDynamicImport(load, turns?)` | Real event-loop turns under fake timers; wait for a dynamic `import()` |
|
|
3197
3263
|
| `autoMocked<T>(overrides?)` | `createAutoMock` typed as `T & Spy<T>` |
|
|
3198
3264
|
| `mockSystemTime` / `withSystemTime` / `mockNow` / `useCountingClock` _(`/setup`)_ | Clock control that survives fake timers being re-installed per test |
|
package/dist/angular-http.js
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
|
-
import { flushEffects } from './chunk-
|
|
1
|
+
import { flushEffects } from './chunk-ALFMIDWW.js';
|
|
2
2
|
import { withDocs, DOCS_LINKS } from './chunk-B45HLBA4.js';
|
|
3
3
|
import { provideHttpClient } from '@angular/common/http';
|
|
4
4
|
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
|
|
5
5
|
import { TestBed } from '@angular/core/testing';
|
|
6
6
|
import { afterEach } from 'vitest';
|
|
7
7
|
|
|
8
|
+
function verifyOnTeardown(check) {
|
|
9
|
+
try {
|
|
10
|
+
check();
|
|
11
|
+
} catch (error) {
|
|
12
|
+
TestBed.resetTestingModule();
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
performance.now.bind(performance);
|
|
17
|
+
|
|
18
|
+
// src/lib/angular-http.ts
|
|
8
19
|
var SETTLE_ROUNDS = 2;
|
|
9
|
-
var
|
|
20
|
+
var verifyPolicy = false;
|
|
10
21
|
function provideHttpTesting(options = {}) {
|
|
11
|
-
|
|
22
|
+
verifyPolicy = options.verifyOnTeardown ?? true;
|
|
12
23
|
return [provideHttpClient(), provideHttpClientTesting()];
|
|
13
24
|
}
|
|
14
25
|
function describeRequest(request) {
|
|
@@ -136,11 +147,10 @@ Answer it (\`await expectRequest('/url').flush(body)\`), assert it is absent (\`
|
|
|
136
147
|
);
|
|
137
148
|
}
|
|
138
149
|
afterEach(() => {
|
|
139
|
-
if (!
|
|
150
|
+
if (!verifyPolicy) {
|
|
140
151
|
return;
|
|
141
152
|
}
|
|
142
|
-
|
|
143
|
-
verifyNoPendingRequests();
|
|
153
|
+
verifyOnTeardown(verifyNoPendingRequests);
|
|
144
154
|
});
|
|
145
155
|
|
|
146
156
|
export { expectNoRequest, expectRequest, provideHttpTesting, verifyNoPendingRequests };
|
package/dist/angular.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { l as AngularTokenProvider, A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, R as RenderShallowOptions, c as ResourceStatusLike, S as SettleResourceOptions, d as ShallowRender, e as SpyRegistry, f as StableOptions, g as createWithAutoSpies, h as flushEffects, i as injectSpy, p as provideAutoSpy, m as provideAutoSpyForToken, r as renderShallow, j as runEffect, s as settleResource, k as stable } from './zoneless-
|
|
1
|
+
export { l as AngularTokenProvider, A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, R as RenderShallowOptions, c as ResourceStatusLike, S as SettleResourceOptions, d as ShallowRender, e as SpyRegistry, f as StableOptions, g as createWithAutoSpies, h as flushEffects, i as injectSpy, p as provideAutoSpy, m as provideAutoSpyForToken, r as renderShallow, j as runEffect, s as settleResource, k as stable } from './zoneless-BFq3ve6Q.js';
|
|
2
2
|
import { InjectionToken, Provider, Type, Signal, WritableSignal } from '@angular/core';
|
|
3
3
|
import { TestAPI } from 'vitest';
|
|
4
4
|
import { C as ClassType, S as Spy, k as ClassSpyConfiguration, o as OnlyMethodKeysOf, g as AddSpyMethodsByReturnTypes } from './types-CQLPjw8z.js';
|
|
@@ -57,8 +57,8 @@ interface ExtendWithAutoSpiesOptions {
|
|
|
57
57
|
* Providers registered alongside the spies, in the same `configureTestingModule` call — the
|
|
58
58
|
* component under test, a real service the spec deliberately keeps, `provideHttpClient()`.
|
|
59
59
|
*
|
|
60
|
-
* They go in *
|
|
61
|
-
* would otherwise be made for the same token.
|
|
60
|
+
* They go in *after* the generated ones, so a provider named here wins over the auto-spy that
|
|
61
|
+
* would otherwise be made for the same token — Angular resolves duplicate providers last-one-wins.
|
|
62
62
|
*/
|
|
63
63
|
providers?: Provider[];
|
|
64
64
|
}
|