vitest-auto-spy 5.41.0 → 5.43.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/CHANGELOG.md +148 -1
- package/README.md +83 -51
- package/agent-docs/angular.md +10 -8
- package/agent-docs/checklist.md +20 -4
- package/agent-docs/configuration.md +6 -2
- package/agent-docs/errors.md +1 -0
- package/agent-docs/factories.md +7 -6
- package/dist/angular-diagnostics.js +3 -3
- package/dist/angular-doubles.d.ts +1 -1
- package/dist/angular-doubles.js +3 -3
- package/dist/angular-matchers.d.ts +11 -3
- package/dist/angular-matchers.js +16 -9
- package/dist/angular-router.d.ts +11 -1
- package/dist/angular-router.js +12 -3
- package/dist/angular.d.ts +7 -7
- package/dist/angular.js +1 -1
- package/dist/bun-angular.d.ts +5 -5
- package/dist/bun-angular.js +6 -6
- package/dist/bun.d.ts +5 -5
- package/dist/bun.js +3 -3
- package/dist/{chunk-HUS2MHK5.js → chunk-CENALW7R.js} +24 -21
- package/dist/{chunk-T7EGA4G4.js → chunk-EYXARTWD.js} +152 -98
- package/dist/{chunk-IE4NI2OQ.js → chunk-J2C3KRTC.js} +39 -17
- package/dist/{chunk-6ZGH56G4.js → chunk-J4IJCTHT.js} +1 -1
- package/dist/{chunk-OWDUWUAO.js → chunk-MHN6GMXP.js} +1 -1
- package/dist/{chunk-DG5EP3PO.js → chunk-RYEBG2KB.js} +2 -2
- package/dist/{chunk-O55MVK7N.js → chunk-XXAGPAZR.js} +2 -2
- package/dist/cli.js +1312 -472
- package/dist/console.d.ts +1 -1
- package/dist/console.js +3 -3
- package/dist/dom-stubs.d.ts +2 -2
- package/dist/dom-stubs.js +38 -16
- package/dist/{expect-emission-CwosjG6t.d.ts → expect-emission-D7DaPI5H.d.ts} +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +214 -135
- 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 +174 -117
- package/dist/node.d.ts +4 -4
- package/dist/node.js +175 -118
- package/dist/{package-identity-CBgwC1T6.d.ts → package-identity-C362z4w-.d.ts} +1 -1
- package/dist/{prop-mock-C8PJ-Vo8.d.ts → prop-mock-e7qpfqY1.d.ts} +1 -1
- package/dist/react.d.ts +4 -4
- package/dist/rstest.d.ts +4 -4
- package/dist/rstest.js +4 -4
- package/dist/rxjs.d.ts +2 -2
- package/dist/setup.d.ts +3 -3
- package/dist/setup.js +38 -16
- package/dist/svelte.d.ts +4 -4
- package/dist/{track-injections-Bp8TgLjG.d.ts → track-injections-CkVuJHr_.d.ts} +10 -4
- package/dist/{track-signal-runs-DUZUFWf-.d.ts → track-signal-runs-B3noexlc.d.ts} +3 -3
- package/dist/{types-DJ0OGc3C.d.ts → types-CaGn5uvo.d.ts} +18 -15
- package/dist/vue.d.ts +5 -5
- package/package.json +1 -1
- package/skills/vitest-auto-spy/SKILL.md +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,151 @@ The latest released version here must always match the one published on
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [5.43.0] - 2026-09-27
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`trackInjections(...).names({ clean: true })`.** `names()` returns each token's `Function.name`
|
|
18
|
+
as is, so under esbuild a decorated `FeatureFlagService` came back as `_FeatureFlagService` while
|
|
19
|
+
`get()` and every message printed the clean name. The option takes the rename off (the leading `_`,
|
|
20
|
+
Rollup's `$1`); without it the result is unchanged.
|
|
21
|
+
- **`toHaveSignalValue(expected, { strict: true })`.** The matcher compares the way `toEqual` does, so
|
|
22
|
+
`signal({ color: undefined })` passed against `{}` and a class instance against a plain object; a
|
|
23
|
+
spec on `expect(sig()).toStrictEqual(x)` lost precision by moving to it. `strict` counts
|
|
24
|
+
`undefined` properties, array holes and the object's class, as `toStrictEqual` does. The options
|
|
25
|
+
type is exported from `/angular/matchers` as `SignalValueOptions`. `/angular/matchers` is +137 B
|
|
26
|
+
min+gzip (2662 → 2799 B, +5.1 %) and +0.6 kB to import (22349 → 22906 B): the strict comparison,
|
|
27
|
+
its class tester, and the structural-directive message below.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **`angular-build-istanbul-module-cache` puts its −46 % in range.** A second suite, 862 spec files on
|
|
32
|
+
`@angular/build` 22.2 and Vitest 5.0.2, gained −19 % from the cache (48 → 39 s), while switching it
|
|
33
|
+
to v8 ran in 19–22 s, about twice as fast as istanbul with the cache, on 7.5–8 GB RSS against
|
|
34
|
+
10–11 GB. The finding now gives −46 % as "up to", adds that data point, says the cache pays off on
|
|
35
|
+
CI only when CI keeps `node_modules/.vitest-cache`, and its fix offers `coverage.provider: 'v8'` as
|
|
36
|
+
the alternative. Still an info.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- **`toHaveDirectiveApplied(Structural, selector)` no longer sends a structural directive to
|
|
41
|
+
`detectChanges()`.** When the directive rendered nothing the selector names, the failure said no
|
|
42
|
+
element matches and to run `fixture.detectChanges()`, which changes nothing. It now says the
|
|
43
|
+
directive is on a template anchor, that the element exists only in a view the directive chose to
|
|
44
|
+
render, and to assert without a selector.
|
|
45
|
+
- **Docs that disagreed with 5.41–5.42.** The performance table and README called the
|
|
46
|
+
`lazySpies: true` path "the default" (a class of 8+ methods now gets `'proxy'`) and said lazy spies
|
|
47
|
+
change no behaviour; the width is now defined as the methods the double spies, after
|
|
48
|
+
`onlyMethodsToSpyOn` / `methodsToSpyOn`. The Russian Angular page carried the pre-4.1 speed table.
|
|
49
|
+
The strict-mode and control-helper pages now say a delegating `mockImplementation` and `vi.when` are
|
|
50
|
+
not reported, and a `vi.when` over a `mockReturnValue` is. The CLI page counts `ng-test` among the
|
|
51
|
+
commands, says it exits with `ng`'s code, lists its exit-2 cases, that `--related` takes a
|
|
52
|
+
comma-separated list and that the builder compiles only included specs from `@angular/build` 22.2
|
|
53
|
+
(also in `--help`); the Russian CLI page gains the exit-code table. `RouteResources` is `never` up
|
|
54
|
+
to router 22.1, not only on 20 and 21.
|
|
55
|
+
|
|
56
|
+
## [5.42.0] - 2026-09-27
|
|
57
|
+
|
|
58
|
+
### Added
|
|
59
|
+
|
|
60
|
+
- **`npx vitest-auto-spy ng-test` — sharding and changed-only runs under the Angular unit-test
|
|
61
|
+
builder.** `ng test` has no pass-through for Vitest's flags: `startVitest()` receives an object the
|
|
62
|
+
builder built, and Vitest's own `--changed` / `--related` find nothing there because its module graph
|
|
63
|
+
holds the builder's bundles, not your sources. `ng-test --shard 2/4` lists the target's specs with
|
|
64
|
+
`ng test --list-tests` (`@angular/build` 21+), splits them the way Vitest does and hands the shard to
|
|
65
|
+
the builder as `--include` paths, so it compiles only that shard; `ng-test --changed [ref]` and
|
|
66
|
+
`--related <files>` run the specs whose imports reach a changed file (relative imports,
|
|
67
|
+
`compilerOptions.paths` aliases, a template or stylesheet through the component naming it; a config,
|
|
68
|
+
a lockfile or a change the setup files reach runs everything). Everything after `--` goes to
|
|
69
|
+
`ng run <project>:<target>`. On a 700-spec workspace with coverage and the builder cache off, as in
|
|
70
|
+
CI, the slower of two `ng-test` shards took 7.58 s against 11.21 s for the whole run, and 9.38 s for
|
|
71
|
+
a `test.shard` set in the runner config, which still compiles every spec.
|
|
72
|
+
- **`provideActivatedRoute({ resources })` / `createActivatedRoute({ resources })`.** Angular 22.2
|
|
73
|
+
gave a route a `resources` record (developer preview), and a component reading
|
|
74
|
+
`route.resources['user']` got `undefined` from the double. The record now lands on
|
|
75
|
+
`route.resources` and on every snapshot's `resources` — the same object, carried into each new
|
|
76
|
+
snapshot as a navigation that keeps the route does. Arrange a value with
|
|
77
|
+
`mockResourceProp(resources, 'user', undefined)` on that record. On Angular 20 and 21 the key
|
|
78
|
+
exists in the type as `never`, so nothing can be passed where the router has no such field.
|
|
79
|
+
- **`doctor` points at the module cache where the unit-test builder runs istanbul.**
|
|
80
|
+
`angular-build-istanbul-module-cache` (info) fires for an `@angular/build:unit-test` target on 21+
|
|
81
|
+
and Vitest 5 whose coverage provider resolves to istanbul and whose runner config lacks
|
|
82
|
+
`fsModuleCache`; on a 700-file Angular 22.2 suite the cache took the run from
|
|
83
|
+
24.55 s to 13.20 s (−46 %), −41 % with the builder cache off as on CI. The fix names the runner
|
|
84
|
+
config, or asks for `"runnerConfig"` on the target, and adds the CI cache step when no CI config
|
|
85
|
+
keeps `node_modules/.vitest-cache`. Quiet with v8, where the cache gained nothing, and for a
|
|
86
|
+
`browsers` target unless its runner config names istanbul itself: browser mode was not measured.
|
|
87
|
+
- **`doctor` warns before `fsModuleCache` breaks an Analog suite.** `analog-module-cache-inline-styles`
|
|
88
|
+
(warning): with the Analog plugin in JIT mode and a component with inline `styles`, the warm run
|
|
89
|
+
fails with `Cannot find module '/@id/__x00__virtual:angular:jit:style:inline;<hash>'` (verified on
|
|
90
|
+
Analog 2.7.5, Angular 22.2, Vitest 5.0.0).
|
|
91
|
+
- **`doctor` warns when Analog `fastCompile` drops constructor types.**
|
|
92
|
+
`analog-fast-compile-ctor-injection` (warning): with `fastCompile` in JIT mode, an `@Injectable`
|
|
93
|
+
class that takes a constructor parameter known only by its type gets no `ctorParameters`, so it
|
|
94
|
+
throws NG0202 in `TestBed.inject` and `createWithAutoSpies` alike. The finding lists each class and
|
|
95
|
+
the fixes: `inject()`, `@Inject(X)`, or `fastCompile` off.
|
|
96
|
+
- **`doctor` notes happy-dom for builder users.** `angular-build-happy-dom` (info): on
|
|
97
|
+
`@angular/build` 21+ the builder picks happy-dom itself once installed; −4.6 % wall and −6 % RSS on
|
|
98
|
+
the 700-file suite.
|
|
99
|
+
|
|
100
|
+
### Changed
|
|
101
|
+
|
|
102
|
+
- **`createSpyFromClass` builds a double of a class with 8 or more methods as a `Proxy`.** When
|
|
103
|
+
`lazySpies` is not set, a class of 8+ methods gets `lazySpies: 'proxy'`; a narrower one keeps the
|
|
104
|
+
accessor placeholders it had. Why upgrade: a wide double a test touches holds 21–68 % less and is
|
|
105
|
+
built 2–6× faster — measured per double with one and two methods called: 2 110 → 1 417 B and
|
|
106
|
+
3 269 → 2 573 B at 8–16 methods, 4 412 → 1 417 B and 5 570 → 2 577 B at 45; create and call 2.2 →
|
|
107
|
+
1.1 µs at 8 methods, 6.5 → 1.0 µs at 45. The accessor placeholders put a touched double into a
|
|
108
|
+
property dictionary as wide as the class; the proxy defines nothing until a method is read. What a
|
|
109
|
+
wide class's double now does differently: every read of a member costs ~20 ns more;
|
|
110
|
+
`util.types.isProxy(double)` is `true`; `console.log` shows only the methods already read;
|
|
111
|
+
`vi.spyOn` on a method nobody has read returns the double's own spy, so `mockRestore()` resets it;
|
|
112
|
+
`Object.create(double).method` builds the spy on the double; an untouched double is ~100 B heavier.
|
|
113
|
+
Snapshots, `toEqual`, `Object.keys` and spread are unchanged. `lazySpies: true` brings the
|
|
114
|
+
placeholders back for one class, `registerAutoSpyDefaults(Class, { lazySpies: true })` for all its
|
|
115
|
+
doubles. `provideAutoSpy` and `injectSpy` follow the same rule.
|
|
116
|
+
- **`lazySpies: 'proxy'` holds ~0.26 kB per untouched double at any width**, down from 1 860 B at 10
|
|
117
|
+
methods and 4 097 B at 100 (`bench:memory`, 258 and 259 B): one trap handler per double and one
|
|
118
|
+
method-name set per class.
|
|
119
|
+
- **`createSpyFromClass` without `returns`, `selfReturning` or `overrides` no longer builds its message
|
|
120
|
+
label** — eight regex replacements per call, −0.2 µs per double in every mode.
|
|
121
|
+
- **A double retains less.** A materialised method spy no longer allocates an empty `Once` queue, a
|
|
122
|
+
closure context for its callable, a separate state object, or a six-slot context for its dispatch
|
|
123
|
+
(−128 B per method); the empty `accessorSpies` bag of a class without spied accessors is built from
|
|
124
|
+
objects sized to hold nothing (−64 B per double, identical to `{}` to every reader); and
|
|
125
|
+
`createAutoMock` / `mockDeep` nodes create their accessor and tombstone collections on first write
|
|
126
|
+
(−336 B each). `bench:memory` on the `lazySpies: true` arm (the default before this release):
|
|
127
|
+
untouched 243 → 178 B at 100 methods, all called 128 956 → 116 082 B (−10 %); `createAutoMock`
|
|
128
|
+
untouched 705 → 369 B (−48 %). Construction and call timings are unchanged within noise.
|
|
129
|
+
|
|
130
|
+
### Fixed
|
|
131
|
+
|
|
132
|
+
- **`vi.when` on a method spy no longer reports the `calledWith()` as deciding nothing.** Vitest 5's
|
|
133
|
+
`vi.when(spy)` reads the spy's implementation and installs a wrapper that answers its own rows and
|
|
134
|
+
passes every other call through to it — to the library's dispatch, so the `calledWith` chain keeps
|
|
135
|
+
deciding. Both orders printed the "replaced the dispatch" warning, and under
|
|
136
|
+
`setupAutoSpy({ preset: 'strict' })` it threw. An implementation installed right after
|
|
137
|
+
`getMockImplementation()` handed out the current one is now treated as a wrapper; a `vi.when` over
|
|
138
|
+
a `mockReturnValue` is still reported.
|
|
139
|
+
- **A `calledWith` after a `mockReturnValue` that was the spy's first touch since `vi.resetAllMocks()`
|
|
140
|
+
is reported again.** The pending reset was applied after the replacement was recorded and erased it.
|
|
141
|
+
- **`perf` stops giving advice that fails under `@angular/build:unit-test` and Analog.** Under the
|
|
142
|
+
builder the settings findings (`perf-transform`, `perf-workers`, `perf-isolation`,
|
|
143
|
+
`perf-environment-engine`) name the runner config the run reads — `--runner-config` in `--command`,
|
|
144
|
+
then the target's `runnerConfig` — instead of the first `vite(st).config.*` in the tree, say to add
|
|
145
|
+
`"runnerConfig": "vitest-base.config.mts"` to the target when it names none, and say the setting is
|
|
146
|
+
not available on `@angular/build` 20.x, which reads no runner config. `perf-vitest-doctor` no longer
|
|
147
|
+
sends a builder workspace to `npx vitest doctor`, which fails there on every file with
|
|
148
|
+
`describe is not defined`; it gives an A/B recipe through
|
|
149
|
+
`ng run <project>:<target> --runner-config=<variant>` instead. `isolate` and `environment` the
|
|
150
|
+
builder passes to Vitest itself no longer count as the user's choice, and the DOM-engine advice
|
|
151
|
+
there becomes "install `happy-dom`": from 21 the builder picks it by itself. The barrel advice is
|
|
152
|
+
off under the builder, and the confirmed-finding card names a spec's `spec-*.js` bundle after the
|
|
153
|
+
spec and drops `chunk-*.js`, neither of which exists on disk. Under `@analogjs/vite-plugin-angular`
|
|
154
|
+
2.7.5 and older with an inline-styled component, `perf-transform` is a warning not to turn
|
|
155
|
+
`fsModuleCache` on: the warm run fails with
|
|
156
|
+
`Cannot find module '/@id/__x00__virtual:angular:jit:style:inline;<hash>'`.
|
|
157
|
+
|
|
13
158
|
## [5.41.0] - 2026-09-27
|
|
14
159
|
|
|
15
160
|
### Changed
|
|
@@ -5590,7 +5735,9 @@ moved under 1 %: `/angular` +192 B (+1.0 %) for the override queue and the HTTP
|
|
|
5590
5735
|
This copy ships with the package and carries 5.x only. Releases before 5.0.0 are in the
|
|
5591
5736
|
[full changelog](https://github.com/ASDAlexey/vitest-auto-spy/blob/master/CHANGELOG.md).
|
|
5592
5737
|
|
|
5593
|
-
[Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.
|
|
5738
|
+
[Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.43.0...HEAD
|
|
5739
|
+
[5.43.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.42.0...v5.43.0
|
|
5740
|
+
[5.42.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.41.0...v5.42.0
|
|
5594
5741
|
[5.41.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.40.1...v5.41.0
|
|
5595
5742
|
[5.40.1]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.40.0...v5.40.1
|
|
5596
5743
|
[5.40.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.39.0...v5.40.0
|
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)
|
|
@@ -83,7 +83,7 @@ faster at suite scale ([benchmarks](#benchmarks)) — and for
|
|
|
83
83
|
- 🚚 A migration you can verify — `vitest-auto-spy/diagnostics`: `compareTestRuns` on the two JSON reports, `summarizeTestRun` / `formatTestRunComparison` to read the answer, `diffByField` for the assertion the reporter collapses, `explainSpy` for a double that answered something you did not configure
|
|
84
84
|
- 📏 Lint rules and one-line test-run hygiene — forty-nine rules in `vitest-auto-spy/eslint-plugin` (six `--fix`, nineteen suggestions, four of them for a suite mid-migration off jasmine), `setupAutoSpy()` — with `preset: 'strict'` for every guard at its strictest grade, and under `isolate: false` `strayListeners` / `restoreGlobals` for the listeners and raw global assignments a file leaves the next one, `restoreStorageSpies` (on by default) for the storage spy happy-dom will not let `mockRestore()` remove
|
|
85
85
|
- 🩺 [Editor diagnostics](#editor-diagnostics--webstorm--vs-code) — the same anti-patterns underlined while you type: native ESLint inspections in **WebStorm** and the other JetBrains IDEs, the ESLint extension in **VS Code**, no extra plugin either way
|
|
86
|
-
- 🔎 [`npx vitest-auto-spy doctor`](#the-cli--doctor-perf-codemod-and-
|
|
86
|
+
- 🔎 [`npx vitest-auto-spy doctor`](#the-cli--doctor-perf-codemod-init-and-ng-test) — suite-level defects **that never fail a run**: a `tsconfig` `include` matching no file, a production module importing a spec, a `@jest-environment` pragma the runner never reads, config left behind for a runner that is gone. Read-only, no config, exits 1 in CI
|
|
87
87
|
- ⏱️ [`npx vitest-auto-spy perf`](#perf--where-the-cpu-time-actually-goes) — where a suite's CPU time actually goes, phase by phase, and which spec files to act on: the ones that reach no DOM and could run under `node`, the ones that import a barrel. Runs Vitest once with a reporter this package ships, reads `TestModule.diagnostic()`, names files, states the rule behind each finding
|
|
88
88
|
- 🚚 [`npx vitest-auto-spy codemod`](#codemod--migrating-a-suite-off-jest-auto-spies) — thirteen transforms that move a suite off `jest-auto-spies` and Jest, or off `jasmine-auto-spies` and jasmine (`--from jasmine`), dry-run by default, with a `--verify` pass that also checks a file somebody edited by hand
|
|
89
89
|
- 🔇 Console spies — `import { consoleInfoSpy } from 'vitest-auto-spy/console'` silences `console` and asserts its calls; [`setupAutoSpy({ strayConsole: 'throw' })`](#how-to-mock-the-console) fails any test whose console output nothing absorbed
|
|
@@ -126,7 +126,7 @@ faster at suite scale ([benchmarks](#benchmarks)) — and for
|
|
|
126
126
|
- [A jasmine suite mid-migration](#how-to-mock-a-jasmine-suite-mid-migration)
|
|
127
127
|
- [New in 5](#new-in-5)
|
|
128
128
|
- [New in 4](#new-in-4)
|
|
129
|
-
- [The CLI — `doctor`, `perf`, `codemod` and `
|
|
129
|
+
- [The CLI — `doctor`, `perf`, `codemod`, `init` and `ng-test`](#the-cli--doctor-perf-codemod-init-and-ng-test)
|
|
130
130
|
- [`doctor` — defects that never fail](#doctor--defects-that-never-fail)
|
|
131
131
|
- [`perf` — where the CPU time actually goes](#perf--where-the-cpu-time-actually-goes)
|
|
132
132
|
- [`codemod` — migrating a suite off `jest-auto-spies`](#codemod--migrating-a-suite-off-jest-auto-spies)
|
|
@@ -989,7 +989,7 @@ a `.d.ts` your `tsconfig` includes — that one import is what keeps `returnSubj
|
|
|
989
989
|
own `Subject<T>`. From `@angular/build:unit-test` 22.2.0 the test program is the specs, the setup
|
|
990
990
|
files and the `.d.ts` files, so a plain `.ts` listed only in the spec `tsconfig`'s `include` is out.
|
|
991
991
|
|
|
992
|
-
## The CLI — `doctor`, `perf`, `codemod` and `
|
|
992
|
+
## The CLI — `doctor`, `perf`, `codemod`, `init` and `ng-test`
|
|
993
993
|
|
|
994
994
|
The package ships one executable, with no dependencies and nothing to configure:
|
|
995
995
|
|
|
@@ -998,10 +998,16 @@ npx vitest-auto-spy doctor # read-only. Exits 1 when it finds something
|
|
|
998
998
|
npx vitest-auto-spy perf # where the suite's CPU time goes. --gate fails a budget and says why
|
|
999
999
|
npx vitest-auto-spy codemod # prints the migration diff. Writes nothing without --write
|
|
1000
1000
|
npx vitest-auto-spy init # writes the agent instructions pointer
|
|
1001
|
+
npx vitest-auto-spy ng-test # ng test with --shard / --changed the Angular builder does not pass through
|
|
1001
1002
|
```
|
|
1002
1003
|
|
|
1004
|
+
[`ng-test`](https://asdalexey.github.io/vitest-auto-spy/utilities/cli#ng-test-—-sharding-and-changed-only-runs-under-the-angular-builder)
|
|
1005
|
+
shards a suite under `@angular/build:unit-test`, or runs only the specs a change reaches: `ng test`
|
|
1006
|
+
passes neither Vitest's `--shard` nor its `--changed` through, so it picks the specs itself and hands
|
|
1007
|
+
them to the builder as `--include` paths.
|
|
1008
|
+
|
|
1003
1009
|
**Exit `0` means "ran, nothing to report"; `1` means "ran, and here is the finding"; `2` means
|
|
1004
|
-
"there was nothing to judge".** The third one is the reason to read this line: an unknown flag for a
|
|
1010
|
+
"there was nothing to judge".** (`ng-test` passes `ng`'s own exit code through once it runs.) The third one is the reason to read this line: an unknown flag for a
|
|
1005
1011
|
known command is an error and **nothing runs** — `init --dryrun` used to write the files and
|
|
1006
1012
|
`perf --gat` used to pass with no gate at all, which is a typo that reads as a clean CI step. A path
|
|
1007
1013
|
that matches no file is the same kind of error, because _Nothing left to migrate_ off a tree nobody
|
|
@@ -1035,31 +1041,35 @@ error tsconfig-glob-matches-nothing libs/users/tsconfig.spec.json
|
|
|
1035
1041
|
3 errors, 4 warnings, 1 note
|
|
1036
1042
|
```
|
|
1037
1043
|
|
|
1038
|
-
| Check
|
|
1039
|
-
|
|
|
1040
|
-
| `tsconfig-glob-matches-nothing`
|
|
1041
|
-
| `tsconfig-file-missing`
|
|
1042
|
-
| `spec-imported-by-non-spec`
|
|
1043
|
-
| `spec-exports-fixture`
|
|
1044
|
-
| `foreign-runner-pragma`
|
|
1045
|
-
| `dead-runner-config`
|
|
1046
|
-
| `orphan-runner-file`
|
|
1047
|
-
| `angular-build-splitting-off`
|
|
1048
|
-
| `angular-build-splitting-deprecated`
|
|
1049
|
-
| `analog-behind-angular-build`
|
|
1050
|
-
| `
|
|
1051
|
-
| `
|
|
1052
|
-
| `
|
|
1053
|
-
| `
|
|
1054
|
-
| `
|
|
1055
|
-
| `
|
|
1056
|
-
| `
|
|
1057
|
-
| `vitest-5-
|
|
1058
|
-
| `
|
|
1059
|
-
| `
|
|
1060
|
-
| `
|
|
1061
|
-
| `
|
|
1062
|
-
| `
|
|
1044
|
+
| Check | What it finds |
|
|
1045
|
+
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1046
|
+
| `tsconfig-glob-matches-nothing` | An `include` pattern that matches no file — so it type-checks nothing; `info` when nothing beside the config is there to miss yet |
|
|
1047
|
+
| `tsconfig-file-missing` | A `files` entry naming a file that is gone |
|
|
1048
|
+
| `spec-imported-by-non-spec` | A production module importing a `*.spec.ts` |
|
|
1049
|
+
| `spec-exports-fixture` | A spec importing another spec, whose hooks then run in a foreign file |
|
|
1050
|
+
| `foreign-runner-pragma` | `@jest-environment` left in a spec, which Vitest never reads |
|
|
1051
|
+
| `dead-runner-config` | `jest.config.*` / `karma.conf.*` for a runner that is not installed |
|
|
1052
|
+
| `orphan-runner-file` | A setup file only that dead config referenced |
|
|
1053
|
+
| `angular-build-splitting-off` | `@angular/build` in `[22.1.5, 22.1.7)` — the OOM under `--coverage` |
|
|
1054
|
+
| `angular-build-splitting-deprecated` | `"splitting"` on a unit-test target under `@angular/build` 22.2+, which deprecates it — `true` is a no-op, `false` still costs the memory |
|
|
1055
|
+
| `analog-behind-angular-build` | `@analogjs/vite-plugin-angular` below 2.7.5 next to `@angular/build` 22.2+ — `TypeError: cache.has is not a function` at startup |
|
|
1056
|
+
| `analog-fast-compile-ctor-injection` | Analog `fastCompile` in JIT mode with an `@Injectable` whose constructor parameter is known only by its type — NG0202 at injection |
|
|
1057
|
+
| `analog-module-cache-inline-styles` | `fsModuleCache` on under the Analog plugin with a component that has inline `styles` — the warm run fails on `virtual:angular:jit:style:inline` |
|
|
1058
|
+
| `angular-build-istanbul-module-cache` | istanbul coverage under the unit-test builder (not in `browsers`) without `fsModuleCache` — up to −46 %; v8 beat istanbul with the cache ~2x. Info |
|
|
1059
|
+
| `angular-build-happy-dom` | jsdom only because `happy-dom` is not installed; the builder picks it by itself from 21 — −4.6 % wall, −6 % RSS. Info |
|
|
1060
|
+
| `coverage-all-removed` | `coverage.all` in a config, on a Vitest that stopped reading the key |
|
|
1061
|
+
| `coverage-include-misses-bundle` | A `coverage.include` of sources only, in a runner config over a bundle |
|
|
1062
|
+
| `coverage-include-recompiles-globs` | A coverage scope large enough that `picomatch` recompiling it per file costs more than the coverage. Info |
|
|
1063
|
+
| `vitest-5-removed` | What Vitest 5 removed — `vitest/reporters`-style imports, `.sequential`, `--outputJson`/`--compare`; error on 5, note on 4. Also `poolOptions` (warning, ignored since 4) |
|
|
1064
|
+
| `vitest-5-deprecated` | `experimental_clearCache` / `experimental_parseSpecifications` on Vitest 5, which nothing warns about at run time. Info |
|
|
1065
|
+
| `vitest-5-clear-mocks` | `clearMocks: true` restating the Vitest 5 default, or a Vitest 4 suite that never decided and will have mocks cleared after the upgrade. Info |
|
|
1066
|
+
| `mock-reset-config-unread` | `no-redundant-mock-reset` given a `configFile` whose default export is a factory or `mergeConfig` call and no flag beside it — the rule reads none of the flags that call sets. Info |
|
|
1067
|
+
| `vitest-5-available` | Vitest 4 with nothing holding back 5 — what it buys (−46 % v8 / −35.5 % istanbul on a 700-file Angular suite with coverage) — or what holds it back. Info |
|
|
1068
|
+
| `fs-module-cache-not-persisted` | `fsModuleCache` on, and no CI config caches its directory — every CI run starts cold |
|
|
1069
|
+
| `jasmine-era-project` | `jasmine-core`, `@types/jasmine`, `karma.conf.*` or `@hirez_io/observer-spy` still installed. Info, not an error |
|
|
1070
|
+
| `no-agent-instructions` | No instruction file names the package. A note, not an error |
|
|
1071
|
+
| `helper-from-wrong-entry` | A named import taken from an entry that does not export it — `provideAutoSpy` from the root |
|
|
1072
|
+
| `no-unawaited-helper` | An `expectEmission` / `stable` / `flushEventLoop` call dropped as a bare statement, so nothing awaits it |
|
|
1063
1073
|
|
|
1064
1074
|
The check that motivated the tool: a spec showing `Cannot find name 'vi'` in the editor while
|
|
1065
1075
|
`tsc --noEmit` reported zero errors. A migration codemod editing `include` had eaten a `/**`,
|
|
@@ -1879,7 +1889,8 @@ multipliers do not transfer to suite scale: building a double is on the order of
|
|
|
1879
1889
|
so a 10× win on the double shows up as a few per cent on the run, and nearly everything else in a
|
|
1880
1890
|
suite swamps it.
|
|
1881
1891
|
|
|
1882
|
-
**Memory, `isolate: false`, 100-method class, 10 000 tests, peak RSS
|
|
1892
|
+
**Memory, `isolate: false`, 100-method class, 10 000 tests, peak RSS** — measured before 2026-09-27,
|
|
1893
|
+
on the accessor-placeholder default and the proxy mode of that time:
|
|
1883
1894
|
|
|
1884
1895
|
| Arm | Peak RSS |
|
|
1885
1896
|
| ------------------------------------- | -------: |
|
|
@@ -1891,19 +1902,35 @@ Hand-written doubles use 3.0x the default's peak RSS here — the difference bet
|
|
|
1891
1902
|
OOM'd worker on a large suite.
|
|
1892
1903
|
|
|
1893
1904
|
That gap traces down to a single double: on the same 100-method class, untouched, a hand-written
|
|
1894
|
-
double retains **414 462 B**.
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
rather than minted per double — and `lazySpies: 'proxy'` retains **4 090 B**, nineteen times more.
|
|
1899
|
-
Full retained-memory tables:
|
|
1905
|
+
double retains **414 462 B**. Re-measured 2026-09-27 (`npm run bench:memory`, Node 24, Vitest 5), the
|
|
1906
|
+
default on that class retains **259 B** per untouched double and `lazySpies: true` **178 B** — the lazy
|
|
1907
|
+
placeholder's accessor pair is shared by method name rather than minted per double. Full
|
|
1908
|
+
retained-memory tables:
|
|
1900
1909
|
[Performance](https://asdalexey.github.io/vitest-auto-spy/core/performance#retained-memory-per-double).
|
|
1901
1910
|
|
|
1911
|
+
The first read is where the accessor placeholders pay: it drops the double into a property
|
|
1912
|
+
dictionary as wide as the class. That dictionary is why a class of 8 methods or more gets `'proxy'`
|
|
1913
|
+
when `lazySpies` is not set. The proxy answers every method from one trap handler and one name set
|
|
1914
|
+
per class and defines nothing, so there is no shared map to leave and no dictionary to grow. Bytes
|
|
1915
|
+
per double with one and two methods called (1 000 doubles held at once, 2026-09-27):
|
|
1916
|
+
|
|
1917
|
+
| Methods in the class | `true`, 1 / 2 touched | `'proxy'`, 1 / 2 touched | Saving |
|
|
1918
|
+
| -------------------: | --------------------: | -----------------------: | ------------: |
|
|
1919
|
+
| 4–6 | 1 743 / 2 887 B | 1 415 / 2 573 B | −19 % / −11 % |
|
|
1920
|
+
| 8–16 | 2 110 / 3 269 B | 1 417 / 2 573 B | −33 % / −21 % |
|
|
1921
|
+
| 20–30 | 2 877 / 4 036 B | 1 416 / 2 573 B | −51 % / −36 % |
|
|
1922
|
+
| 45 | 4 412 / 5 570 B | 1 417 / 2 577 B | −68 % / −54 % |
|
|
1923
|
+
|
|
1924
|
+
The steps are V8's: the dictionary holds `pow2ceil(1.5·(width + 3))` slots. Building and touching the
|
|
1925
|
+
double is cheaper too — 2.2 → 1.1 µs at 8 methods, 6.5 → 1.0 µs at 45. The price is a trap on every
|
|
1926
|
+
read of a member for the life of the double, about 25 ns against 5 ns for a plain property, ~100 B more
|
|
1927
|
+
on a double nobody touches, and a double a spec can see is a `Proxy`. Eight methods is the smallest
|
|
1928
|
+
width where the proxy saves at least 15 % on both profiles; below it the default stays a plain object.
|
|
1929
|
+
`lazySpies: true` opts a wide class out.
|
|
1930
|
+
|
|
1902
1931
|
**Guidance keyed to suite size:** `isolate: false` is worth about 4x on its own — more than any
|
|
1903
|
-
library choice measured here — and it makes memory, not wall-clock, the binding constraint.
|
|
1904
|
-
|
|
1905
|
-
is still faster at, building a double of a very wide class, and it taxes every read for the life of
|
|
1906
|
-
that double (53 ns against 7 ns).
|
|
1932
|
+
library choice measured here — and it makes memory, not wall-clock, the binding constraint. Leave
|
|
1933
|
+
`lazySpies` alone: the width picks the lighter double.
|
|
1907
1934
|
|
|
1908
1935
|
Full write-up, methodology and the complete suite-scale tables:
|
|
1909
1936
|
[Performance](https://asdalexey.github.io/vitest-auto-spy/core/performance).
|
|
@@ -2395,7 +2422,9 @@ expect(() => myService.getName(2)).toThrow();
|
|
|
2395
2422
|
install an implementation on the host mock, and the dispatch that reads a `calledWith` chain **is**
|
|
2396
2423
|
the implementation they replace — so the one written later wins outright and the other decides
|
|
2397
2424
|
nothing, in either order, without failing. Both orders are now reported as a misconfiguration (a
|
|
2398
|
-
warning, a throw under the `strict` preset)
|
|
2425
|
+
warning, a throw under the `strict` preset) — except a wrapper that delegates, `vi.when(spy)` on
|
|
2426
|
+
Vitest 5 or a `mockImplementation` built from `spy.getMockImplementation()`, which the chain still
|
|
2427
|
+
answers through. Where both are wanted, the fallback goes in the spy's
|
|
2399
2428
|
own container, which a chain still wins over: the `returns` option where the double is built, or
|
|
2400
2429
|
`resolveWith` / `nextWith` / `failWith`. The report comes from this library's own spy engine — so
|
|
2401
2430
|
Vitest and Rstest, not Bun or `node:test`, and not under `setSpyEngine('runner')` — and the `Once`
|
|
@@ -2629,11 +2658,12 @@ beforeEach(() => {
|
|
|
2629
2658
|
> `@angular/build` 22.2 or newer, both `@analogjs` packages need 2.7.5 or newer — older ones crash at
|
|
2630
2659
|
> startup with `TypeError: cache.has is not a function`.
|
|
2631
2660
|
|
|
2632
|
-
> **Lazy by default
|
|
2633
|
-
> (`
|
|
2661
|
+
> **Lazy by default.** `createSpyFromClass` and `provideAutoSpy` build each method spy on first access
|
|
2662
|
+
> (accessor placeholders below 8 methods, a `Proxy` from 8), since Angular tests typically spy a wide service but call
|
|
2634
2663
|
> only a few of its methods — and the margin widens as the class does: **1.4×** on a ten-method
|
|
2635
|
-
> class a test calls twice, **1.9×** on a forty-method one it calls three times.
|
|
2636
|
-
>
|
|
2664
|
+
> class a test calls twice, **1.9×** on a forty-method one it calls three times. Calls, `Object.keys`,
|
|
2665
|
+
> spread and snapshots behave the same; a wide class's double is a `Proxy`, so pass
|
|
2666
|
+
> `{ lazySpies: true }` for a plain object, or `{ lazySpies: false }` to build every spy eagerly.
|
|
2637
2667
|
|
|
2638
2668
|
#### Signal / readonly property mocking (bonus)
|
|
2639
2669
|
|
|
@@ -2903,8 +2933,9 @@ expect(component.items).toHaveSignalValue([{ id: 1 }]);
|
|
|
2903
2933
|
```
|
|
2904
2934
|
|
|
2905
2935
|
`expect(component.total).toBeTruthy()` passes for every signal ever created — a signal is a
|
|
2906
|
-
function. The matcher reads it, deep-compares
|
|
2907
|
-
|
|
2936
|
+
function. The matcher reads it, deep-compares (as `toEqual` does; `{ strict: true }` as
|
|
2937
|
+
`toStrictEqual` does), and rejects anything that is not a zero-argument getter, so the
|
|
2938
|
+
missing-parentheses mistake fails instead of quietly passing.
|
|
2908
2939
|
|
|
2909
2940
|
#### Where a spec spends its time
|
|
2910
2941
|
|
|
@@ -3005,7 +3036,7 @@ collaborators.get(FeatureFlagService).isOn.mockReturnValue(true);
|
|
|
3005
3036
|
|
|
3006
3037
|
TestBed.inject(CheckoutFacade).start();
|
|
3007
3038
|
|
|
3008
|
-
expect(collaborators.names()).toEqual(['FeatureFlagService']); // analytics was never asked for
|
|
3039
|
+
expect(collaborators.names({ clean: true })).toEqual(['FeatureFlagService']); // analytics was never asked for
|
|
3009
3040
|
```
|
|
3010
3041
|
|
|
3011
3042
|
The assertion behind most `vi.mock('@app/services')` calls is not "this module was replaced" — it is
|
|
@@ -3034,7 +3065,8 @@ injectActivatedRoute().setParams({ id: '8' }); // params and paramMap emit; snap
|
|
|
3034
3065
|
has neither, and a hand-written `useValue` usually has one half — the component that reads the other
|
|
3035
3066
|
gets `undefined`, or a spec tests a route no navigation can produce. The double here is Angular's own
|
|
3036
3067
|
`ActivatedRoute` over one record: `params`, `queryParams`, `data`, `fragment`, `url`, both
|
|
3037
|
-
`ParamMap`s and `snapshot` come from the same values
|
|
3068
|
+
`ParamMap`s and `snapshot` come from the same values (plus `title`, `children` and, on Angular 22.2+,
|
|
3069
|
+
the `resources` record), and each setter (`setParams`,
|
|
3038
3070
|
`setQueryParams`, `setData`, `setFragment`, `setUrl`, or `set({ … })` for several at once) rebuilds
|
|
3039
3071
|
the snapshot first and then emits what changed, in the router's order and with its equality. It is a
|
|
3040
3072
|
real route to the real `Router` too: `relativeTo` resolves against its `url`. `createActivatedRoute()`
|
|
@@ -4336,7 +4368,7 @@ as `methodsToSpyOn`, named for callables that live on the instance — `signal()
|
|
|
4336
4368
|
`gettersToSpyOn`, `settersToSpyOn`, `autoSpyAccessors` (discover every getter/setter),
|
|
4337
4369
|
`fillMissing` (answer a name the prototype never carried with a spy — for a **partially** abstract
|
|
4338
4370
|
class, where `abstract` members are erased and the empty-prototype fallback no longer fires),
|
|
4339
|
-
`lazySpies` (
|
|
4371
|
+
`lazySpies` (build each method spy on first read. Unset: accessor placeholders below 8 methods, `'proxy'` from 8 — a `Proxy` that defines nothing per method, 1.4 kB against 2.1 kB for a 8–16-method double with one method called and 1.4 against 4.4 kB at 45, at ~20 ns per read. `true` keeps a plain object at any width, `false` builds every spy up front — see [Performance](https://asdalexey.github.io/vitest-auto-spy/core/performance#retained-memory-per-double))
|
|
4340
4372
|
|
|
4341
4373
|
`ValueConfig` (for `nextWithValues`): `{ value, delay? }` | `{ errorValue, delay? }` | `{ complete?, delay? }`.
|
|
4342
4374
|
|
package/agent-docs/angular.md
CHANGED
|
@@ -14,8 +14,8 @@ TestBed.configureTestingModule({
|
|
|
14
14
|
const myService = injectSpy(MyService); // Spy<MyService>
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
`provideAutoSpy`
|
|
18
|
-
`{ lazySpies: false }` to opt out. The spies never touch `NgZone`, so they work zoneless and with
|
|
17
|
+
`provideAutoSpy` is lazy by default, as `createSpyFromClass` is: an unset `lazySpies` is `true` below 8
|
|
18
|
+
methods and `'proxy'` from 8. Pass `{ lazySpies: false }` to opt out. The spies never touch `NgZone`, so they work zoneless and with
|
|
19
19
|
zone.js alike. The entry needs **Angular >= 20** (§1); on 16 or 17 it does not link at all, because
|
|
20
20
|
`ɵSIGNAL` is not there to import.
|
|
21
21
|
|
|
@@ -524,7 +524,7 @@ built over one record, so every stream, both `ParamMap`s and `snapshot` read the
|
|
|
524
524
|
|
|
525
525
|
| Call | Does |
|
|
526
526
|
| --- | --- |
|
|
527
|
-
| `provideActivatedRoute(init?)` | a `FactoryProvider` — a fresh route per injector; `init`: `params`, `queryParams`, `data`, `title`, `fragment`, `url`, `outlet`, `component`, `routeConfig`, `resolve` |
|
|
527
|
+
| `provideActivatedRoute(init?)` | a `FactoryProvider` — a fresh route per injector; `init`: `params`, `queryParams`, `data`, `title`, `fragment`, `url`, `outlet`, `component`, `routeConfig`, `resolve`, `children`, `resources` (22.2+: one record on the route and every snapshot — fill it with `mockResourceProp(resources, 'user', …)`) |
|
|
528
528
|
| `injectActivatedRoute(injector?)` | the handle: `.route`, `setParams`, `setQueryParams`, `setData`, `setFragment`, `setUrl`, `set({ … })` |
|
|
529
529
|
| `createActivatedRoute(init?)` | the same handle without a `TestBed` — for `new Page(route)`, or a guard given `route.snapshot` |
|
|
530
530
|
|
|
@@ -538,8 +538,9 @@ Four things to know:
|
|
|
538
538
|
- **List it after `provideRouter()`** — the later provider of `ActivatedRoute` wins, and
|
|
539
539
|
`injectActivatedRoute()` throws naming the one that did. With the real router present, the double
|
|
540
540
|
still works as `relativeTo` for `router.createUrlTree`.
|
|
541
|
-
- **
|
|
542
|
-
`route.parent.params` patch that member with
|
|
541
|
+
- **No parent above it.** `root` is the route itself and `parent` is `null`; below it there are only
|
|
542
|
+
the routes passed as `children`. For `route.parent.params` patch that member with
|
|
543
|
+
`mockReadonlyProp`, or use `RouterTestingHarness`.
|
|
543
544
|
- **`title` is given, not resolved; no navigation, no input binding.** `provideActivatedRoute({
|
|
544
545
|
title: 'Product 7' })` answers `route.snapshot.title` — the double puts the string under the
|
|
545
546
|
router's own `RouteTitleKey`, read off the installed router, and does not run a `title: () => …`
|
|
@@ -857,10 +858,11 @@ collaborators.get(FeatureFlagService).isOn.mockReturnValue(true);
|
|
|
857
858
|
|
|
858
859
|
TestBed.inject(CheckoutFacade).start();
|
|
859
860
|
|
|
860
|
-
expect(collaborators.names()).toEqual(['FeatureFlagService']); // analytics was never asked for
|
|
861
|
+
expect(collaborators.names({ clean: true })).toEqual(['FeatureFlagService']); // analytics was never asked for
|
|
861
862
|
```
|
|
862
863
|
|
|
863
|
-
`providers`, `injectedTokens()` (in factory-run order), `names()`, `
|
|
864
|
+
`providers`, `injectedTokens()` (in factory-run order), `names()` (raw `Function.name`, so `_Service`
|
|
865
|
+
under esbuild; `names({ clean: true })` strips the rename), `wasInjected(token)`,
|
|
864
866
|
`get<D>(token)` → `Spy<D>`, `reset()` (the record only — the doubles survive). A class token gets a
|
|
865
867
|
class spy, anything else a `createAutoMock()`; pass `{ double: () => … }` when a collaborator has to
|
|
866
868
|
be a real object. Doubles are built eagerly, so stub before the entry point runs; a factory runs once
|
|
@@ -1025,7 +1027,7 @@ expect(products.reload).toHaveBeenCalled(); // reload is spied, answers true, an
|
|
|
1025
1027
|
|
|
1026
1028
|
// signal assertions
|
|
1027
1029
|
registerSignalMatchers(); // once, in the setup file — /angular/matchers
|
|
1028
|
-
expect(component.total).toHaveSignalValue(3);
|
|
1030
|
+
expect(component.total).toHaveSignalValue(3); // toEqual semantics; { strict: true } for toStrictEqual's
|
|
1029
1031
|
|
|
1030
1032
|
// resource assertions — value AND status, which is the whole point
|
|
1031
1033
|
registerResourceMatchers(); // once, in the setup file — /angular/matchers
|
package/agent-docs/checklist.md
CHANGED
|
@@ -50,9 +50,18 @@ a unit-test target, Analog below 2.7.5, `vite` below 6.4, or Node below 22.12 in
|
|
|
50
50
|
`.node-version`, CI or a private package's `engines`. `fs-module-cache-not-persisted` warns when
|
|
51
51
|
`fsModuleCache` is on and no CI config caches its directory. Renamed config keys Vitest 5 still
|
|
52
52
|
honours (`experimental.fsModuleCache`, `browser.isolate`, `cache.dir`, …) are left to Vitest's own
|
|
53
|
-
deprecation line.
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
deprecation line. Four more cover the Angular unit-test builder and Analog:
|
|
54
|
+
`angular-build-istanbul-module-cache` notes an istanbul run under `@angular/build:unit-test` 21+ on
|
|
55
|
+
Vitest 5 without `fsModuleCache` in the target's runner config (up to −46 %, −19 % on another suite;
|
|
56
|
+
v8 was about 2x faster than istanbul with the cache there, and the cache pays off on CI only when
|
|
57
|
+
`node_modules/.vitest-cache` is kept), and
|
|
58
|
+
`analog-module-cache-inline-styles` warns that `fsModuleCache` under the Analog plugin breaks the
|
|
59
|
+
warm run of any spec reaching a component with inline `styles`. `angular-build-happy-dom` notes a
|
|
60
|
+
builder target on jsdom only because `happy-dom` is not installed.
|
|
61
|
+
`analog-fast-compile-ctor-injection` warns that Analog `fastCompile` in JIT mode leaves
|
|
62
|
+
`@Injectable` constructor parameters without a token (NG0202). `mock-reset-config-unread` notes a
|
|
63
|
+
`no-redundant-mock-reset` whose `configFile` names a config built by a factory or `mergeConfig`,
|
|
64
|
+
with no flag beside it: the rule cannot see the flags that call sets.
|
|
56
65
|
It is worth one run after any large edit to a test suite — especially after a codemod, which is where
|
|
57
66
|
the eaten glob below came from.
|
|
58
67
|
|
|
@@ -133,6 +142,11 @@ per file on 5, where it is the wait for Vite's transforms and `import`/`setup` a
|
|
|
133
142
|
totals are CPU time summed across every worker, not wall clock** — a report showing `20.29s of CPU
|
|
134
143
|
time` under `986ms wall clock` is not a bug, it is the work spread over workers.
|
|
135
144
|
|
|
145
|
+
Under `@angular/build:unit-test`, `ng test` takes no Vitest flag. Shard or run only what a change
|
|
146
|
+
reaches with `npx vitest-auto-spy ng-test --shard 1/4` / `--changed [ref]` / `--related a.ts,b.ts`
|
|
147
|
+
(`@angular/build` 21+; everything after `--` goes to `ng`); `test.repeats` in the runner config
|
|
148
|
+
reaches Vitest as it is. `vitest doctor` does not work there — use `perf --command`.
|
|
149
|
+
|
|
136
150
|
When `environment` dominates, the `perf-environment` finding names spec files that could run under
|
|
137
151
|
the `node` environment instead — but only the ones it could _prove_ reach no DOM: the spec, its
|
|
138
152
|
configured setup files, and every repository module any of them imports were all read, none
|
|
@@ -173,7 +187,9 @@ used rather than the machine's cores; under `isolate: false`, `perf-heap` lists
|
|
|
173
187
|
to its lane's heap. Environment time is counted once per lane (`concurrencyId`) and value — not per
|
|
174
188
|
`workerId`, which Vitest 5 renews for every file even in a reused worker. A finding about a switch
|
|
175
189
|
also prints `perf-vitest-doctor`: confirm it with `npx vitest doctor`, Vitest's own A/B runner, not
|
|
176
|
-
this package's `doctor`.
|
|
190
|
+
this package's `doctor`. Under `@angular/build:unit-test` it gives an A/B through `ng run <project>:<target> --runner-config=<variant>`
|
|
191
|
+
instead, because `npx vitest doctor` cannot build that suite; every settings finding there names the runner config
|
|
192
|
+
the target reads. A bare run on Vitest 5 passes `--experimental.diagnostics=false` so Vitest's
|
|
177
193
|
own hints do not repeat these. The reporter rewrites a `partial: true` report every ~2 s while the run
|
|
178
194
|
goes, so a killed or timed-out run still leaves one: `perf` prints it under a warning and the gate
|
|
179
195
|
refuses it like a red run.
|
|
@@ -15,7 +15,7 @@ createSpyFromClass(MyService, {
|
|
|
15
15
|
gettersToSpyOn: ['userName'],
|
|
16
16
|
settersToSpyOn: ['userName'],
|
|
17
17
|
autoSpyAccessors: true, // discover every accessor on the prototype chain
|
|
18
|
-
lazySpies: true, //
|
|
18
|
+
lazySpies: true, // unset: true below 8 methods, 'proxy' from 8; false = eager
|
|
19
19
|
returns: { getProducts: of([]) }, // what a spied METHOD answers
|
|
20
20
|
selfReturning: ['where'], // a method that answers the double itself, for a chained call
|
|
21
21
|
overrides: { products$: subject }, // a member that is not a method result
|
|
@@ -28,7 +28,7 @@ createSpyFromClass(MyService, {
|
|
|
28
28
|
| `onlyMethodsToSpyOn` | **Exhaustive whitelist.** Skips discovery; anything not listed is absent. |
|
|
29
29
|
| `instanceMethodsToSpyOn` | **Additive.** The name to prefer in new code (see below). |
|
|
30
30
|
| `autoSpyAccessors` | Merged with the explicit getter/setter lists. |
|
|
31
|
-
| `lazySpies` |
|
|
31
|
+
| `lazySpies` | Unset: `true` (accessor placeholders) below 8 methods, `'proxy'` (a `Proxy`, lighter once touched) from 8, counting the methods the double spies (after `onlyMethodsToSpyOn` / `methodsToSpyOn`). Explicit `true` / `false` / `'proxy'` apply at any width. Calls, `Object.keys`, spread and snapshots behave the same in every mode. |
|
|
32
32
|
| `strict` | Throw on a method nobody configured, instead of answering `undefined` (below). |
|
|
33
33
|
| `onUnstubbedCall` | The general form of `strict`; its return value becomes the call's return value. |
|
|
34
34
|
| `selfReturning` | The named methods answer the double itself — a default like `returns`; a name in both answers `returns`. |
|
|
@@ -251,6 +251,10 @@ Precedence, first one set wins: the double's `onUnstubbedCall` → the double's
|
|
|
251
251
|
double is built, or `resolveWith` / `nextWith` / `failWith` — which a `calledWith` still wins over.
|
|
252
252
|
The report is this library's own spy engine's, so Vitest and Rstest have it; Bun, `node:test` and
|
|
253
253
|
`setSpyEngine('runner')` do not. The `Once` family is never reported.
|
|
254
|
+
`vi.when(spy)` (Vitest 5) is not reported either, nor any `mockImplementation` built from
|
|
255
|
+
`spy.getMockImplementation()`: it wraps the dispatch rather than replacing it,
|
|
256
|
+
so a `calledWith` on the same method still answers every call `vi.when` has no row for. A
|
|
257
|
+
`vi.when` over a `mockReturnValue` still is.
|
|
254
258
|
- **A throw something swallowed still fails the test.** Under `setupAutoSpy({ strict: true })` every
|
|
255
259
|
strict throw is recorded, and one that a `try`/`catch` in the code under test or an RxJS error with
|
|
256
260
|
no handler (its rethrow waits on a fake clock) kept from the test fails it after the test
|
package/agent-docs/errors.md
CHANGED
|
@@ -126,6 +126,7 @@ Part of the agent reference [`AGENTS.md`](../AGENTS.md), which maps every sectio
|
|
|
126
126
|
| `flushEventLoopUntil` timing out on the **first** such test only, the rest green | a cold dynamic `import()` outran the turn budget; later tests hit the module cache | `await settleDynamicImport(() => import('…'))` — await the module, do not count turns |
|
|
127
127
|
| `Expected to be running in 'ProxyZone', but it was not found` | `zone.js/testing` patches jasmine/mocha/jest, not Vitest | `import 'vitest-auto-spy/zone'` after zone.js, with `globals: true` (§14) |
|
|
128
128
|
| `nextWith` demanding `HttpEvent<T>` on a generated API client | `Parameters`/`ReturnType` read the **last** overload | `asSpy<Client, { overload: 'first' }>(…)`, or `Overload<M, 0>` — never `@ts-expect-error`, which `no-ts-expect-error-on-double` reports (§16) |
|
|
129
|
+
| `NG0202 … dependency at index N of the parameter list is invalid` from `createWithAutoSpies` or plain `TestBed.inject`, under Analog `fastCompile: true` | the plugin drops the constructor types of `@Injectable` classes in JIT mode — not a library bug (`doctor` reports `analog-fast-compile-ctor-injection` and lists the classes) | an `inject()` field, or `@Inject(X)` on the parameter; or turn `fastCompile` off (§13) |
|
|
129
130
|
| `NG0303` / `NG0304` / nothing at all, from a directive spec | the host is `standalone: false`, or the module is in the TestBed | `createDirectiveHost({ template, scope: [Module] })`; on Angular ≥ 22.2, for a directive with no static host attribute, `TemplateRef` or sibling markup, `TestBed.createDirective(Dir, { tagName, bindings })` (§13) |
|
|
130
131
|
| `setInputs: … declares no input named …` (with a `Did you mean` for a near typo) | the key is not an input of that component — a typo, an input renamed since, or one of its own fields | take the suggestion, or set a name the message lists; a signal the component owns is moved through the signal (§13) |
|
|
131
132
|
| `setInputs: X carries no ɵcmp, so there are no inputs to set` (or `the component class is undefined`, `X is a @Pipe`) | the fixture's `componentType` is not a compiled `@Component` — a `@Pipe`, a class Angular never compiled, or `undefined` from a barrel split across chunks | pass the `@Component` class the fixture was created from; call a pipe's `transform()` directly; for `undefined` import the class from its own file (§13) |
|
package/agent-docs/factories.md
CHANGED
|
@@ -349,12 +349,13 @@ retains: an untouched 100-method double holds **215 B** where it held 25 593, an
|
|
|
349
349
|
construction on very wide classes — building a 300-method double costs about 28 % more — and
|
|
350
350
|
materialising every method of one is about 26 % cheaper in exchange.
|
|
351
351
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
352
|
+
The first materialisation is where the accessor path pays: it drops the double into a property
|
|
353
|
+
dictionary as wide as the class. So since 2026-09-27 an unset `lazySpies` depends on width: a class of
|
|
354
|
+
8 methods or more gets `'proxy'`, which defines nothing per method and never enters dictionary mode —
|
|
355
|
+
21–68 % lighter once touched, 2–6× faster to build — for ~20 ns on every read and ~100 B more on an
|
|
356
|
+
untouched double. A narrower class keeps the accessor placeholders. A spec on a wide class that needs a
|
|
357
|
+
plain object (`util.types.isProxy`, `console.log` of an unread double, `vi.spyOn` wrapping an unread
|
|
358
|
+
method) passes `lazySpies: true`.
|
|
358
359
|
|
|
359
360
|
A frozen or sealed double is fine now. `Object.freeze(spy)` — a deep-freeze fixture helper, a
|
|
360
361
|
dev-mode state guard — used to make the first read of any method throw `Cannot redefine property`
|