vitest-auto-spy 5.45.0 → 5.47.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 +1 -1
- package/CHANGELOG.md +127 -1
- package/README.md +44 -28
- package/agent-docs/adapters.md +7 -9
- package/agent-docs/angular.md +11 -1
- package/agent-docs/anti-patterns.md +3 -2
- package/agent-docs/doubles.md +4 -1
- package/agent-docs/errors.md +1 -0
- package/agent-docs/eslint.md +6 -5
- package/agent-docs/return-helpers.md +5 -1
- package/agent-docs/setup.md +11 -3
- package/dist/angular-http.d.ts +24 -38
- package/dist/angular-http.js +47 -8
- package/dist/angular-matchers.d.ts +18 -2
- package/dist/angular-matchers.js +6 -10
- package/dist/angular.d.ts +1 -1
- package/dist/angular.js +8 -1
- package/dist/bun-angular.d.ts +1 -1
- package/dist/bun-angular.js +8 -1
- package/dist/chunk-B6JUBFBA.js +25 -0
- package/dist/cli.js +10 -1
- package/dist/console.d.ts +23 -2
- package/dist/console.js +12 -2
- package/dist/dom-stubs.d.ts +13 -4
- package/dist/dom-stubs.js +9 -5
- package/dist/eslint-plugin.cjs +68 -2
- package/dist/eslint-plugin.d.ts +6 -0
- package/dist/jasmine.js +4 -3
- package/dist/setup.d.ts +21 -1
- package/dist/setup.js +47 -2
- package/dist/{track-signal-runs-CyPA-p4I.d.ts → track-signal-runs-CUdp5dJZ.d.ts} +24 -1
- package/package.json +1 -1
- package/skills/vitest-auto-spy/SKILL.md +143 -139
package/AGENTS.md
CHANGED
|
@@ -81,7 +81,7 @@ Add-ons, orthogonal to the runner:
|
|
|
81
81
|
| --- | --- | --- |
|
|
82
82
|
| Observable spies | `import 'vitest-auto-spy/rxjs'` | `nextWith` & friends. **Side-effect import, once**, in a setup file, a spec, or a `.d.ts` the `tsconfig` includes (§4) |
|
|
83
83
|
| observer-spy shim | `vitest-auto-spy/observer-spy` | `subscribeSpyTo` — the `@hirez_io/observer-spy` surface (§20). Its own entry so `/rxjs` does not carry it |
|
|
84
|
-
| Console spies | `vitest-auto-spy/console` | silent typed spies over the global `console` — `installConsoleSpies()` per test, `restoreConsole()` after; `consoleOutput()` is everything logged, keyed by channel |
|
|
84
|
+
| Console spies | `vitest-auto-spy/console` | silent typed spies over the global `console` — `useConsoleSpies()` in the `describe` (or `installConsoleSpies()` per test, `restoreConsole()` after); `consoleOutput()` is everything logged, keyed by channel |
|
|
85
85
|
| DOM stubs | `vitest-auto-spy/dom-stubs` | `stubIntersectionObserver` / `stubResizeObserver` / `stubMutationObserver` / `stubObserver`, `stubMediaElement`, `stubAbortController`, `stubWebStorage` (§12), `stubAnimationFrame`, `stubElementRect`, `intersectionEntry` / `resizeEntry` / `mutationRecord`. **Moved off the root in 4.0.0** |
|
|
86
86
|
| Run diagnostics | `vitest-auto-spy/diagnostics` | `compareTestRuns`, `summarizeTestRun`, `formatTestRunComparison`, `diffByField`. **Moved off the root in 4.0.0** |
|
|
87
87
|
| Angular HTTP | `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest` — `httpResource()` / `HttpClient` (§13). Optional `@angular/common` peer, this entry only |
|
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,130 @@ The latest released version here must always match the one published on
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [5.47.0] - 2026-09-28
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`prefer-to-have-signal-value`** (ESLint plugin, `warn`, type-aware, autofix) rewrites
|
|
18
|
+
`expect(component.total()).toBe(3)` into `expect(component.total).toHaveSignalValue(3)`:
|
|
19
|
+
the matcher keeps the signal's name in the failure output and refuses a value that is not a
|
|
20
|
+
signal. `toEqual` maps the same way, `toStrictEqual` keeps its comparison through
|
|
21
|
+
`{ strict: true }`. The signal is recognised by its type — callable and branded with Angular's
|
|
22
|
+
signal brand, spelled `__@SIGNAL@…` by the checker — so methods, plain functions and getters stay
|
|
23
|
+
unreported, and without parser services the rule says nothing. Safe to autofix since 5.46.0, which
|
|
24
|
+
made `toHaveSignalValue` compare nested `Set` and `Map` values.
|
|
25
|
+
- **`registerSignalMatchers({ strict: true })`** flips the default of one registration: every
|
|
26
|
+
`toHaveSignalValue` of the suite compares like `toStrictEqual` without repeating the option per
|
|
27
|
+
assertion, and a single assertion can still pass `{ strict: false }`.
|
|
28
|
+
- **`expectUnhandledObservableErrors([...])` from `vitest-auto-spy/setup`** asserts the rxjs errors
|
|
29
|
+
nothing handled are exactly the ones a test deliberately left — each expected entry the error
|
|
30
|
+
itself, its class, or a message (a string or a pattern), compared in order — and hands the
|
|
31
|
+
flushed entries back. Zero-argument form asserts none were left.
|
|
32
|
+
- **`prepareShallow(Component, options).create(overrides)` from `vitest-auto-spy/angular`** binds a
|
|
33
|
+
component and the options every test of a suite repeats; `create()` runs the `renderShallow`
|
|
34
|
+
sequence with the overrides applied per key — `inputs` for an `it.each`, `providers` for a
|
|
35
|
+
one-off. One instance per test: the runner resets the TestBed between tests, and two `create()`
|
|
36
|
+
calls inside a single test need a manual `TestBed.resetTestingModule()`.
|
|
37
|
+
- **`useConsoleSpies()` from `vitest-auto-spy/console`** registers `beforeEach(installConsoleSpies)`
|
|
38
|
+
and `afterEach(restoreConsole)` in the enclosing `describe` and returns the spies, replacing the
|
|
39
|
+
hook pair every console-asserting spec wrote by hand. It registers Vitest hooks (Bun resolves them
|
|
40
|
+
to its own); on `node:test` and Rstest the pair stays manual. `no-import-time-console-spies`
|
|
41
|
+
accepts it as the file's install, and the stray-console report and docs recommend it.
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- **An app error in `expectRequest` / `expectNoRequest`'s tick under `provideDocumentDouble()` is no
|
|
46
|
+
longer blamed on the double.** 5.46.0 rewrapped every tick failure whenever `DOCUMENT` was not the
|
|
47
|
+
real document, so an effect that threw under the library's own double read as "a partial double
|
|
48
|
+
does not have" the members Angular reads, and pointed at `{ tick: false }`, which hides the error.
|
|
49
|
+
The message is now kept for a `DOCUMENT` that is not a document, or whose `body` has no
|
|
50
|
+
`querySelector`; any other failure is rethrown as it is.
|
|
51
|
+
|
|
52
|
+
### Documentation
|
|
53
|
+
|
|
54
|
+
- **`provideDocumentDouble()` needs no `{ tick: false }`**, and a JSON error body is
|
|
55
|
+
`flush(body, { status })` — `.error(status, { error })` takes a `ProgressEvent`, as Angular's
|
|
56
|
+
`TestRequest.error()` does. The HTTP page, `agent-docs/angular.md` and `agent-docs/errors.md` say so.
|
|
57
|
+
- **The README's `SubjectLike` member list gains `unsubscribe`**, as `agent-docs` did in 5.46.0.
|
|
58
|
+
|
|
59
|
+
## [5.46.0] - 2026-09-27
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
|
|
63
|
+
- **`stubAnimationFrame(...).lastHandle`: the handle the latest `requestAnimationFrame` returned.**
|
|
64
|
+
5.40.0 moved the stub's handles above 2^30 so a native frame's cancel could be told apart, and a
|
|
65
|
+
spec that asserted `expect(frames.cancelAnimationFrame).toHaveBeenLastCalledWith(1)` broke on the
|
|
66
|
+
upgrade with nothing in that entry saying so. `lastHandle` is `undefined` before the first request,
|
|
67
|
+
so the assertion reads `toHaveBeenLastCalledWith(frames.lastHandle)` and survives any numbering.
|
|
68
|
+
- **`injectHttpTesting()` from `/angular-http`: the `HttpTestingController` `provideHttpTesting()`
|
|
69
|
+
installed.** A spec of a service that returns an Observable has its request out before the method
|
|
70
|
+
returns, so it calls `expectOne` / `match` / `expectNone` synchronously, and `expectRequest` (which
|
|
71
|
+
ticks and flushes asynchronously) is not a drop-in there; such specs kept a
|
|
72
|
+
`TestBed.inject(HttpTestingController)` beside `...provideHttpTesting()`. Without the providers it
|
|
73
|
+
throws the message `expectRequest` gives, naming `provideHttpTesting()`.
|
|
74
|
+
- **`expectRequest(...).error(status, { error })`: the payload the `HttpErrorResponse` carries.** The
|
|
75
|
+
helper always failed the request with `new ProgressEvent('error')`, so a spec that pinned
|
|
76
|
+
`HttpErrorResponse.error` stayed on `HttpTestingController`. The default is unchanged. `error(0)` is
|
|
77
|
+
documented as the network failure it is.
|
|
78
|
+
- **`{ tick: false }` on `expectRequest` and `expectNoRequest`.** Both tick the app first, and a module
|
|
79
|
+
that provides a partial `DOCUMENT` double fails that tick inside Angular
|
|
80
|
+
(`inject(...).body?.querySelector is not a function`). The option skips the tick before the lookup and
|
|
81
|
+
after the answer, for an `HttpClient` call whose request is already out.
|
|
82
|
+
|
|
83
|
+
### Fixed
|
|
84
|
+
|
|
85
|
+
- **`toHaveSignalValue` and `toHaveResourceValue` compare the contents of a `Set` and a `Map`.** They
|
|
86
|
+
called the matcher context's `equals` without `iterableEquality`, so a collection had no own keys to
|
|
87
|
+
compare and any two Sets, or any two Maps, were equal:
|
|
88
|
+
`expect(signal(new Set(['a']))).toHaveSignalValue(new Set(['b']), { strict: true })` passed, nested
|
|
89
|
+
in an object as well. Both now compare with the testers `toEqual` uses, and `{ strict: true }` with
|
|
90
|
+
those of `toStrictEqual` (array holes inside a collection and `ArrayBuffer` bytes included). The
|
|
91
|
+
non-strict branch also ignored testers registered through `expect.addEqualityTesters`; both modes
|
|
92
|
+
apply them now. An assertion on a Set- or Map-valued signal that passed only because of this can
|
|
93
|
+
newly fail.
|
|
94
|
+
- **A tick that fails on a doubled `DOCUMENT` says so.** `expectRequest` / `expectNoRequest` failed with
|
|
95
|
+
Angular's bare `TypeError`; when the module's `DOCUMENT` is not the real document, the error now names
|
|
96
|
+
it and `{ tick: false }`, with Angular's error as `cause`. A tick failure under the real document is
|
|
97
|
+
rethrown as it was.
|
|
98
|
+
- **`jasmine.mapContaining` / `setContaining` / `arrayWithExactContents` compare a nested `Set` or
|
|
99
|
+
`Map` by its contents**, for the same reason: `new Map([['a', new Set([1])]])` contained
|
|
100
|
+
`new Map([['a', new Set([2])]])`.
|
|
101
|
+
|
|
102
|
+
### Changed
|
|
103
|
+
|
|
104
|
+
- **`stubAnimationFrame`'s spies are typed `Mock<RequestAnimationFrameFn>` /
|
|
105
|
+
`Mock<CancelAnimationFrameFn>`** instead of `(...args: any[]) => any`, so
|
|
106
|
+
`frames.requestAnimationFrame.mock.calls` compiles and is typed. Both function types are exported
|
|
107
|
+
from `/dom-stubs` and spelled without the DOM lib. A spec that called the spy with arguments
|
|
108
|
+
`requestAnimationFrame` does not take now fails to compile.
|
|
109
|
+
|
|
110
|
+
### Size
|
|
111
|
+
|
|
112
|
+
`/angular/matchers` **2799 → 2936 B** min+gzip (+137 B, +4.9 %) and +1.0 kB to import (22906 →
|
|
113
|
+
23887 B, one module more): the shared equality module the signal and resource matchers now use.
|
|
114
|
+
`/jasmine` 19161 → 19203 B (+42 B) and +1.3 kB to import (148816 → 150096 B, one module more): the
|
|
115
|
+
same module, for the collection matchers. `/angular-http` 3770 → 3997 B (+227 B, +6.0 %) and +0.9 kB
|
|
116
|
+
to import (31370 → 32313 B): `injectHttpTesting`, the `{ tick }` option, the doubled-`DOCUMENT`
|
|
117
|
+
message and the `error` payload. `/dom-stubs` +14 B.
|
|
118
|
+
|
|
119
|
+
### Documentation
|
|
120
|
+
|
|
121
|
+
- **The 5.40.0 handle range is a change a spec may have to act on.** A literal handle (`1`, `2`) in an
|
|
122
|
+
assertion against `frames.cancelAnimationFrame` fails from 5.40.0; assert against `frames.lastHandle`
|
|
123
|
+
or the value `requestAnimationFrame` returned. The frame page, `agent-docs/doubles.md`, the README
|
|
124
|
+
and the skill say so.
|
|
125
|
+
- **`SubjectOf` from `/angular` is still rxjs's `Subject` only with `vitest-auto-spy/rxjs` in the
|
|
126
|
+
program.** `agent-docs/return-helpers.md` and the API page recommended the `/angular` export for a
|
|
127
|
+
variable holding `returnSubject()` without repeating that; without the import it is `SubjectLike`,
|
|
128
|
+
so `next()` compiles and `pipe()` does not. `SubjectLike`'s member list in `agent-docs` gains
|
|
129
|
+
`unsubscribe`.
|
|
130
|
+
- **Asserting the errors `flushUnhandledObservableErrors()` returned, without `.map(({ error }) => error)`:**
|
|
131
|
+
`expect(flushUnhandledObservableErrors()).toMatchObject([{ error: new Error('502') }])` checks the
|
|
132
|
+
count and ignores `test`. The setup page and `agent-docs/setup.md` show it.
|
|
133
|
+
- **"Only these console channels" is `expect(Object.keys(consoleOutput())).toStrictEqual(['info'])`**,
|
|
134
|
+
keys in alphabetical order; `{ info: expect.any(Array) }` trips `no-unsafe-assignment`.
|
|
135
|
+
`agent-docs/setup.md` and the `consoleOutput` docs show it.
|
|
136
|
+
|
|
13
137
|
## [5.45.0] - 2026-09-27
|
|
14
138
|
|
|
15
139
|
### Added
|
|
@@ -5922,7 +6046,9 @@ moved under 1 %: `/angular` +192 B (+1.0 %) for the override queue and the HTTP
|
|
|
5922
6046
|
This copy ships with the package and carries 5.x only. Releases before 5.0.0 are in the
|
|
5923
6047
|
[full changelog](https://github.com/ASDAlexey/vitest-auto-spy/blob/master/CHANGELOG.md).
|
|
5924
6048
|
|
|
5925
|
-
[Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.
|
|
6049
|
+
[Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.47.0...HEAD
|
|
6050
|
+
[5.47.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.46.0...v5.47.0
|
|
6051
|
+
[5.46.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.45.0...v5.46.0
|
|
5926
6052
|
[5.45.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.44.0...v5.45.0
|
|
5927
6053
|
[5.44.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.43.0...v5.44.0
|
|
5928
6054
|
[5.43.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.42.0...v5.43.0
|
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ faster at suite scale ([benchmarks](#benchmarks)) — and for
|
|
|
81
81
|
- 🧩 Module mocks that prove they applied — `assertMocked`, `moduleNamespace`, for a `vi.mock()` a bundler quietly ignored; `moduleNamespace(await importOriginal(), { passthrough: true })` is Vitest's `{ spy: true }` with `calledWith` on top, on any runner
|
|
82
82
|
- 🧾 Fixtures without casts — deep-partial `createMock`, `createFixture` / `createFixtureFactory`, `narrow()`, `withOverrides()`, `asInstances()`, `captureArg()`, and `outOfType()` for the fixture that is outside its type on purpose
|
|
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
|
-
- 📏 Lint rules and one-line test-run hygiene —
|
|
84
|
+
- 📏 Lint rules and one-line test-run hygiene — fifty rules in `vitest-auto-spy/eslint-plugin` (eight `--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
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
|
|
@@ -876,24 +876,21 @@ since those statuses carry no body.
|
|
|
876
876
|
A test that expects output absorbs it, and asserts on it:
|
|
877
877
|
|
|
878
878
|
```ts
|
|
879
|
-
import {
|
|
879
|
+
import { useConsoleSpies } from 'vitest-auto-spy/console';
|
|
880
880
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
beforeEach(() => {
|
|
884
|
-
consoleSpies = installConsoleSpies();
|
|
885
|
-
});
|
|
886
|
-
|
|
887
|
-
afterEach(() => restoreConsole());
|
|
881
|
+
const { consoleErrorSpy } = useConsoleSpies();
|
|
888
882
|
|
|
889
883
|
it('reports a failed load', () => {
|
|
890
884
|
service.load();
|
|
891
885
|
|
|
892
|
-
expect(
|
|
886
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('load failed', expect.any(Error));
|
|
893
887
|
});
|
|
894
888
|
```
|
|
895
889
|
|
|
896
|
-
|
|
890
|
+
`useConsoleSpies()` registers `beforeEach(installConsoleSpies)` and `afterEach(restoreConsole)` in
|
|
891
|
+
the enclosing `describe` (or the file) and returns the spies, which are the same objects in every
|
|
892
|
+
test. It registers Vitest hooks, which Bun resolves to its own; on `node:test` and Rstest write that
|
|
893
|
+
pair in the runner's hooks yourself. Do not
|
|
897
894
|
rely on the **import** to install them: it does so once per worker, so under `isolate: false` the
|
|
898
895
|
spies go on in whichever file imported them first and silence every later file of the worker — on a
|
|
899
896
|
1759-file Angular consumer, 32 of the 39 files importing the entry relied on that, and three files
|
|
@@ -1581,7 +1578,7 @@ Node / Bun / React / Vue project pulls **neither rxjs nor Angular into its runti
|
|
|
1581
1578
|
| `vitest-auto-spy/angular/diagnostics` | `enableAngularDiagnostics` and the whole TestBed timing family — a companion to `/angular`, no core re-export; moved off `/angular` in 5.21.0 | `@angular/core` | ✅ |
|
|
1582
1579
|
| `vitest-auto-spy/angular/doubles` | the `window` / `document` and Material-dialog doubles; registers the Vitest adapter, so its doubles spy out of the box; moved off `/angular` in 5.21.0 | `@angular/core` | ✅ |
|
|
1583
1580
|
| `vitest-auto-spy/angular/matchers` | `registerDirectiveMatchers`, `registerResourceMatchers`, `registerSignalMatchers` — a companion to `/angular`, no core re-export; moved off `/angular` in 5.21.0 | `@angular/core`, `@angular/platform-browser` | ✅ |
|
|
1584
|
-
| `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest`, `expectNoRequest`, `verifyNoPendingRequests` — the `httpResource()` / `HttpClient` recipe in two lines, settling included
|
|
1581
|
+
| `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest`, `expectNoRequest`, `verifyNoPendingRequests`, `injectHttpTesting` — the `httpResource()` / `HttpClient` recipe in two lines, settling included | `@angular/common` | ✅ |
|
|
1585
1582
|
| `vitest-auto-spy/angular-router` | `provideActivatedRoute`, `injectActivatedRoute`, `createActivatedRoute` — Angular's own `ActivatedRoute` over one record, streams and snapshot moved together; `provideRouterDouble` / `injectRouterDouble` for the `Router` beside it, with `collectRouterEvents()` asserting the events that follow; `provideLocationDouble` wrapping the `SpyLocation` Angular ships | `@angular/router`, `@angular/common` | ✅ |
|
|
1586
1583
|
| `vitest-auto-spy/signal-forms` | `createForm` — Angular's own `form()` built where it can inject — and `registerFormMatchers()` for `toHaveFieldErrors` | `@angular/forms` (>=22) | ✅ |
|
|
1587
1584
|
| `vitest-auto-spy/bun` | the same core, driven by Bun's `bun:test` mocks | `bun:test` | ✅ |
|
|
@@ -2531,7 +2528,7 @@ subject.next([{ name: 'manual' }]);
|
|
|
2531
2528
|
|
|
2532
2529
|
`returnSubject()` and `nextWithPerCall()` are typed `SubjectOf<T>`: rxjs's own `Subject<T>` wherever
|
|
2533
2530
|
`import 'vitest-auto-spy/rxjs'` is in the TypeScript program, and the structural `SubjectLike<T>`
|
|
2534
|
-
(`next` / `error` / `complete` / `asObservable` / `closed`) where it is not. That is how the
|
|
2531
|
+
(`next` / `error` / `complete` / `unsubscribe` / `asObservable` / `closed`) where it is not. That is how the
|
|
2535
2532
|
published declarations name no rxjs type while a suite that has rxjs keeps the exact type it had.
|
|
2536
2533
|
The seam is one augmentable interface, `AutoSpyRxjsTypes<T>`, which the `/rxjs` entry fills in; what
|
|
2537
2534
|
decides that a member _is_ an observable is the other new type, `ObservableLike<T>` — `subscribe`
|
|
@@ -2945,6 +2942,19 @@ function. The matcher reads it, deep-compares (as `toEqual` does; `{ strict: tru
|
|
|
2945
2942
|
`toStrictEqual` does), and rejects anything that is not a zero-argument getter, so the
|
|
2946
2943
|
missing-parentheses mistake fails instead of quietly passing.
|
|
2947
2944
|
|
|
2945
|
+
A suite that wants the strict comparison everywhere passes it once instead of per assertion;
|
|
2946
|
+
a single assertion can still opt back out:
|
|
2947
|
+
|
|
2948
|
+
```ts
|
|
2949
|
+
registerSignalMatchers({ strict: true });
|
|
2950
|
+
|
|
2951
|
+
expect(component.items).toHaveSignalValue(items); // compares like toStrictEqual
|
|
2952
|
+
expect(component.draft).toHaveSignalValue({}, { strict: false });
|
|
2953
|
+
```
|
|
2954
|
+
|
|
2955
|
+
The `prefer-to-have-signal-value` lint rule rewrites the inline spellings — `expect(component.total()).toBe(3)`
|
|
2956
|
+
— into the matcher with a `--fix`.
|
|
2957
|
+
|
|
2948
2958
|
#### Where a spec spends its time
|
|
2949
2959
|
|
|
2950
2960
|
```ts
|
|
@@ -3139,7 +3149,9 @@ it('loads the products', async () => {
|
|
|
3139
3149
|
`expectRequest` ticks before it looks — an `httpResource()` has issued nothing until something does —
|
|
3140
3150
|
and `flush()` / `error()` settle before they resolve, which is why the value reads on the next line.
|
|
3141
3151
|
Match by URL (either `url` or `urlWithParams`), by `RegExp`, or by a predicate over the request, with
|
|
3142
|
-
`{ method }` to tell a read from a write. `
|
|
3152
|
+
`{ method }` to tell a read from a write. `error(0)` is a network failure, and
|
|
3153
|
+
`error(status, { error })` sets the payload the `HttpErrorResponse` carries. `{ tick: false }` skips
|
|
3154
|
+
the tick, for a module whose partial `DOCUMENT` double the tick cannot run against (`provideDocumentDouble()` is not partial). `provideHttpTesting()` fails, by default, any test that
|
|
3143
3155
|
ends holding a request nothing answered; `provideHttpTesting({ verifyOnTeardown: false })` turns that
|
|
3144
3156
|
off, and `verifyNoPendingRequests()` runs the same check by hand. An interceptor under test goes in
|
|
3145
3157
|
the same spread — `provideHttpTesting({ interceptors: [authInterceptor] })`, or
|
|
@@ -3289,7 +3301,7 @@ single-purpose utility you can pick up independently — they all ride on the sa
|
|
|
3289
3301
|
| `stubAbortController()` | `/dom-stubs` | A realm-consistent `AbortController`, so `addEventListener(…, { signal })` works under jsdom + zone.js |
|
|
3290
3302
|
| `stubWebStorage(key?, opts?)` | `/dom-stubs` | An in-memory `localStorage` / `sessionStorage` for this test, with `snapshot()` to assert on ([details](#how-to-mock-localstorage-and-sessionstorage)) |
|
|
3291
3303
|
| `stubWorker(opts?)` | `/dom-stubs` | A `Worker` whose script is the spec — listeners stack, replies arrive on a microtask, `emit()` / `fail()` from the worker's side ([details](#how-to-mock-a-web-worker)) |
|
|
3292
|
-
| `stubAnimationFrame(opts?)` | `/dom-stubs` | `requestAnimationFrame` / `cancelAnimationFrame` run on the spot or on `flush(timestamp?)`; a frame requested inside a frame waits for the next flush; `onError` intercepts a throwing callback
|
|
3304
|
+
| `stubAnimationFrame(opts?)` | `/dom-stubs` | `requestAnimationFrame` / `cancelAnimationFrame` run on the spot or on `flush(timestamp?)`; a frame requested inside a frame waits for the next flush; `onError` intercepts a throwing callback; `lastHandle` is the latest handle issued (they start above 2^30) |
|
|
3293
3305
|
| `stubElementRect(el, rect?)` | `/dom-stubs` | `getBoundingClientRect()` answering a real `DOMRect` built from a partial `DOMRectInit`; returns the undo, plus the installed spy as `.getBoundingClientRect` |
|
|
3294
3306
|
| `flushEventLoop(turns?)` / `settleDynamicImport(load, turns?)` | core | Real event-loop turns while the timers are faked — for a dynamic `import()` or native `async` in a dependency |
|
|
3295
3307
|
| `flushEventLoopUntil(isDone, opts?)` | core | Real turns until a condition holds — a `resource()` leaving `loading` — with a budget instead of a hang |
|
|
@@ -3340,6 +3352,7 @@ single-purpose utility you can pick up independently — they all ride on the sa
|
|
|
3340
3352
|
| `withoutStrayTimerTracking(work)` | `/setup` | Run setup work whose timers the stray-timer tracker neither counts nor cancels — jsdom schedules one per Web Storage write |
|
|
3341
3353
|
| `describeStrayTimers()` | `/setup` | Every timer still pending, with its kind and delay, the spec file that scheduled it and the scheduling frames — the list `onStrayTimers` gets. Blind to anything scheduled under fake timers — `vi.useFakeTimers()` assigns over the wrapper |
|
|
3342
3354
|
| `flushUnhandledObservableErrors()` | `/setup` | Run rxjs's pending rethrows of Observable errors nothing handled, now, and return `{ error, test }` (or `outsideTest`) for each — the check `strayTimers` runs after every test, which fails the test with `Unhandled Observable error` |
|
|
3355
|
+
| `expectUnhandledObservableErrors(expected?)` | `/setup` | The same flush as an assertion: the errors nothing handled must be exactly the ones listed — each entry the error itself, its class, or a message (string or pattern) — or the test fails with the diff; no argument asserts none were left |
|
|
3343
3356
|
| `guardPrototypePollution(reaction)` | `/setup` | Name the test that left a key on `Object.prototype` — it stops later files collecting ([details](#test-run-hygiene)) |
|
|
3344
3357
|
| `guardDocumentPollution(option)` | `/setup` | Name the test that left an attribute on `<html>` / `<body>`, and put it back ([details](#test-run-hygiene)) |
|
|
3345
3358
|
| `installPerTest(install)` | `/setup` | Re-install a stub before every test of the block — a `describe`-level stub is restored away after the first |
|
|
@@ -3375,15 +3388,14 @@ onSave.calledWith(1).resolveWith();
|
|
|
3375
3388
|
`installConsoleSpies()` replaces `console.debug` / `error` / `info` / `log` / `time` / `timeEnd` /
|
|
3376
3389
|
`trace` / `warn` with **silent, fully-typed spies**, and the entry exports each one ready to assert —
|
|
3377
3390
|
no `vi.spyOn(console, 'info')` boilerplate in every suite, no log output polluting the test run.
|
|
3378
|
-
|
|
3379
|
-
[How to mock: the console](#how-to-mock-the-console)); importing the entry installs them too, but
|
|
3380
|
-
once per worker, which under `isolate: false` silences every later file:
|
|
3391
|
+
`useConsoleSpies()` installs them before each test of the block and restores the console after
|
|
3392
|
+
(see [How to mock: the console](#how-to-mock-the-console)); importing the entry installs them too, but
|
|
3393
|
+
only once per worker, which under `isolate: false` silences every later file:
|
|
3381
3394
|
|
|
3382
3395
|
```ts
|
|
3383
|
-
import { consoleInfoSpy, consoleWarnSpy,
|
|
3396
|
+
import { consoleInfoSpy, consoleWarnSpy, useConsoleSpies } from 'vitest-auto-spy/console';
|
|
3384
3397
|
|
|
3385
|
-
|
|
3386
|
-
afterEach(() => restoreConsole());
|
|
3398
|
+
useConsoleSpies();
|
|
3387
3399
|
|
|
3388
3400
|
service.doWork();
|
|
3389
3401
|
|
|
@@ -3403,8 +3415,8 @@ the worker and every log from then on went nowhere.
|
|
|
3403
3415
|
|
|
3404
3416
|
Under `setupAutoSpy({ strayConsole })` importing the entry **installs nothing**: under
|
|
3405
3417
|
`isolate: false` the import ran once per worker and left the spies silencing every later file, which
|
|
3406
|
-
is exactly the output the guard exists to see. Call `
|
|
3407
|
-
each test
|
|
3418
|
+
is exactly the output the guard exists to see. Call `useConsoleSpies()` (or `installConsoleSpies()` in
|
|
3419
|
+
a `beforeEach`) for each test, or `installConsoleSpies()` at the top of the spec file for the file; the guard takes them off again when that
|
|
3408
3420
|
scope ends. See [How to mock: the console](#how-to-mock-the-console).
|
|
3409
3421
|
|
|
3410
3422
|
> The spies use the registered `MockAdapter` — import your runtime entry
|
|
@@ -4005,6 +4017,7 @@ export can never be.
|
|
|
4005
4017
|
| `no-import-time-spread` | `error` | suggest | `export const x = [...Imported]` at module scope → a `TypeError`, or a silently empty object, while the bundle loads |
|
|
4006
4018
|
| `no-unregistered-inject-spy` | `error` | — | `injectSpy(X)` for a token this file never registered → the real instance, whose spy helpers exist only for the compiler |
|
|
4007
4019
|
| `prefer-render-shallow` | `warn` | suggest | `TestBed.createComponent` in a file that never reads the template → `renderShallow(X)`; 0.24× the per-test cycle at 100 children |
|
|
4020
|
+
| `prefer-to-have-signal-value` | `warn` | `--fix` | `expect(signal()).toBe(…)` → `expect(signal).toHaveSignalValue(…)`; the matcher names the signal in the failure and refuses a non-signal. `toStrictEqual` keeps `{ strict: true }`. **Type-aware** — the callee must be callable and carry Angular's signal brand — and silent without `parserOptions.project`; safe to autofix since 5.46.0 fixed the `Set`/`Map` comparison |
|
|
4008
4021
|
| `prefer-set-inputs` | `warn` | suggest | a run of `fixture.componentRef.setInput('title', v)` on one fixture → `await setInputs(fixture, { title: v })`, which resolves every name against the compiled definition before it writes one — an undeclared name is an `NG0303` on the console and no change at all — and types the value. The suggestion collapses the run, drops a `detectChanges()` under it and makes the callback `async`; it is offered rather than applied, because `stable()` ticks and a zone.js suite can answer that with `NG0101` |
|
|
4009
4022
|
| `prefer-spy-on-own-method` | `warn` | `--fix` / suggest | `createSpyFromInstance(x, { onlyMethodsToSpyOn: ['m'], passthrough: true })` read for `m` alone — `.m` on the call, `const { m } = …`, a bare statement, or a name read only as `v.m` → `spyOnOwnMethod(x, 'm')`; the same whitelist with `returns: { m: undefined }` → `spyOnVoidMethod(x, 'm')`. Both fix, import and all; a `Spy<X>` annotation becomes `Spy<X>['m']` in a suggestion. A bare `returns: { m: undefined }` seed is a suggestion, offered only on a real event or element — `new MouseEvent(…)`, `document.createElement(…)`, `fixture.nativeElement` — never on a double |
|
|
4010
4023
|
| `prefer-observer-stub` | `error` | — | a hand-rolled `IntersectionObserver` / `ResizeObserver` / `MutationObserver` written into a global → `stubIntersectionObserver()` and friends, whose undo `restoreMockedProps()` already runs |
|
|
@@ -4013,7 +4026,7 @@ export can never be.
|
|
|
4013
4026
|
| `prefer-provide-activated-route` | `error` | — | a hand-built `ActivatedRoute` — any `useValue` / `useClass` / `useFactory`, and `provideAutoSpy(ActivatedRoute)` too → `provideActivatedRoute({ … })`; the double knows either the streams or the snapshot, never both, and `injectActivatedRoute().setParams(…)` moves them together mid-test |
|
|
4014
4027
|
| `no-passthrough-console-spy` | `error` | suggest | `vi.spyOn(console, m)` nothing gives an implementation — it calls through and still prints → `installConsoleSpies()` from `vitest-auto-spy/console`, or `.mockImplementation(() => undefined)` |
|
|
4015
4028
|
| `no-console-in-spec` | `error` | — | a spec that calls `console.x(…)` itself, or replaces a method with `console.x = …`, which nothing puts back — under `isolate: false` every later file inherits it |
|
|
4016
|
-
| `no-import-time-console-spies` | `error` | — | an import of `vitest-auto-spy/console` in a file that never calls `installConsoleSpies()` — the import installs once per worker and, under `isolate: false`, silences every later file
|
|
4029
|
+
| `no-import-time-console-spies` | `error` | — | an import of `vitest-auto-spy/console` in a file that never calls `installConsoleSpies()` or `useConsoleSpies()` — the import installs once per worker and, under `isolate: false`, silences every later file |
|
|
4017
4030
|
| `no-mistyped-use-value` | `error` | — | `{ provide: TOKEN, useValue }` whose value is not assignable to the primitive `TOKEN` declares — `useValue` is `any`, so `{}` for an `InjectionToken<boolean>` compiles and is truthy; **type-aware**, and silent without `parserOptions.project` |
|
|
4018
4031
|
| `no-unknown-use-value-key` | `error` | — | a key of an object `useValue` literal the provided type does not have — `T` of an `InjectionToken<T>`, or a class's instance; keys only, never the values; silent on `any` / `unknown`, an index signature, a spread and `multi: true`; **type-aware** |
|
|
4019
4032
|
| `no-instance-lifecycle-spy` | `warn` | — | `vi.spyOn(component, 'ngOnInit')` and the other hooks a view reads off the prototype — Angular never calls the instance spy → spy on `Cls.prototype` before the component is created, or assert the hook's effect |
|
|
@@ -4038,7 +4051,7 @@ Every message ends with a link to the matching [recipe](#how-to-mock): a rule th
|
|
|
4038
4051
|
"don't" moves the problem rather than solving it. Rules travel with the API they recommend, so they
|
|
4039
4052
|
are versioned together and stop being re-written in every project that installs the package.
|
|
4040
4053
|
|
|
4041
|
-
**
|
|
4054
|
+
**Eight of the fifty fix on their own, nineteen offer suggestions**, and the split is not about how hard
|
|
4042
4055
|
the rewrite is. `no-mocked-for-spy` touches a _declaration_: get it wrong and the file stops
|
|
4043
4056
|
compiling, which is the loudest, cheapest failure there is — so `--fix` rewrites the type, adds
|
|
4044
4057
|
`import type { Spy } from 'vitest-auto-spy'` and drops the `Mocked` import once nothing else uses
|
|
@@ -4209,7 +4222,7 @@ package's own — it needs its ESLint integration switched on.
|
|
|
4209
4222
|
### WebStorm and the other JetBrains IDEs
|
|
4210
4223
|
|
|
4211
4224
|
No plugin to install: WebStorm, IntelliJ IDEA Ultimate, PhpStorm, PyCharm Professional and RubyMine
|
|
4212
|
-
all run ESLint natively, so the
|
|
4225
|
+
all run ESLint natively, so the fifty rules appear inline, in the **Problems** tool window, and
|
|
4213
4226
|
under **Code → Inspect Code** for the whole project.
|
|
4214
4227
|
|
|
4215
4228
|
```js
|
|
@@ -4226,7 +4239,7 @@ has supported flat config since 2023.3); scope the block to spec files yourself;
|
|
|
4226
4239
|
the fixes and suggestions live.
|
|
4227
4240
|
|
|
4228
4241
|
A native JetBrains plugin is **not** planned — it would duplicate an integration the IDE already has
|
|
4229
|
-
and then keep a second copy of
|
|
4242
|
+
and then keep a second copy of fifty rules, in Kotlin, in step with the TypeScript ones.
|
|
4230
4243
|
|
|
4231
4244
|
### VS Code, Cursor, Windsurf, VSCodium
|
|
4232
4245
|
|
|
@@ -4317,7 +4330,7 @@ another, and the next one is in that same file.
|
|
|
4317
4330
|
| `stubAbortController()` | A realm-consistent `AbortController` / `AbortSignal` for jsdom + zone.js, statics (`abort`, `timeout`, `any`) included |
|
|
4318
4331
|
| `stubWebStorage(key?, opts?)` _(`/dom-stubs`)_ | An in-memory `localStorage` / `sessionStorage` a spec installs for itself, seeded or empty; `snapshot()` reads it back as a plain record |
|
|
4319
4332
|
| `stubWorker(opts?)` _(`/dom-stubs`)_ | A `Worker` a spec drives from the worker's side: `respond` per message, `messages` / `terminated` to assert, `emit()` / `fail()`; returns `WorkerStub<TIn, TOut>` |
|
|
4320
|
-
| `stubAnimationFrame(opts?)` _(`/dom-stubs`)_ | Frames on the spec's schedule: `'immediate'` or `'queued'` + `flush()`, `pending`, both globals as spies; returns `AnimationFrameStub`
|
|
4333
|
+
| `stubAnimationFrame(opts?)` _(`/dom-stubs`)_ | Frames on the spec's schedule: `'immediate'` or `'queued'` + `flush()`, `pending`, `lastHandle`, both globals as spies; returns `AnimationFrameStub` |
|
|
4321
4334
|
| `stubElementRect(el, rect?)` _(`/dom-stubs`)_ | A box for `getBoundingClientRect()` — missing fields `0`, edges derived; returns `ElementRectRestore` (callable undo plus `.getBoundingClientRect`) |
|
|
4322
4335
|
| `createComponentStub(Class, overrides?, opts?)` _(`/angular`)_ | A standalone stand-in for a child component, directive or pipe, built from its compiled definition so the selector cannot drift |
|
|
4323
4336
|
| `flushEventLoop(turns?)` / `settleDynamicImport(load, turns?)` | Real event-loop turns under fake timers; wait for a dynamic `import()` |
|
|
@@ -4332,6 +4345,7 @@ another, and the next one is in that same file.
|
|
|
4332
4345
|
| `setupFakeTimers(config?)` / `advanceTimers(ms?)` _(`/setup`)_ | Paired fake-timer install/restore, and an advance that also settles queued microtasks |
|
|
4333
4346
|
| `describeDuplicateCopies()` / `getPackageCopies()` | The duplicate-install report, and the copies behind it |
|
|
4334
4347
|
| `renderShallow(Component, opts?)` _(Angular)_ | `TestBed` component, minus its children and (by default) its template |
|
|
4348
|
+
| `prepareShallow(Component, opts?)` _(Angular)_ | `renderShallow` bound to shared options — `.create(overrides?)` per test, one instance each |
|
|
4335
4349
|
| `setInputs(fixture, inputs, opts?)` _(Angular)_ | Change an input after the first render — one `setInput` per name, checked against the compiled definition, then one wait |
|
|
4336
4350
|
| `createWithAutoSpies(Class, opts?)` _(Angular)_ | Build a class through Angular DI with every unprovided token auto-spied |
|
|
4337
4351
|
| `createNestUnit(Class, opts?)` _(NestJS)_ | Build a provider from its DI metadata with every unprovided token auto-spied; `expose` builds collaborators for real, `providers` wins over both |
|
|
@@ -4339,6 +4353,7 @@ another, and the next one is in that same file.
|
|
|
4339
4353
|
| `settleResource(resource, opts?)` _(Angular)_ | Tick until an `httpResource()` / `resource()` / `rxResource()` leaves `loading` |
|
|
4340
4354
|
| `provideHttpTesting(opts?)` / `expectRequest(matcher, opts?)` _(`/angular-http`)_ | `provideHttpClient(...features)` + `provideHttpClientTesting()` in one spread — `{ interceptors, features }` for the interceptors under test; find the one matching request and `flush` / `error` it with the settling included |
|
|
4341
4355
|
| `expectNoRequest(matcher?, opts?)` / `verifyNoPendingRequests(opts?)` _(`/angular-http`)_ | Assert that nothing was requested / that the test left nothing unanswered; `{ ignoreCancelled: true }` — also accepted by `provideHttpTesting({ verifyOnTeardown })` — forgives a request the code under test unsubscribed from |
|
|
4356
|
+
| `injectHttpTesting()` _(`/angular-http`)_ | The `HttpTestingController` `provideHttpTesting()` installed, for a synchronous `expectOne` / `match` / `expectNone` on an Observable service; names the missing providers when there are none |
|
|
4342
4357
|
| `provideActivatedRoute(init?)` / `injectActivatedRoute(injector?)` _(`/angular-router`)_ | An `ActivatedRoute` provider whose streams, `paramMap`s and snapshot share one record, and the handle whose setters move them together |
|
|
4343
4358
|
| `createActivatedRoute(init?)` _(`/angular-router`)_ | The same double without a `TestBed` — `.route` plus the setters |
|
|
4344
4359
|
| `provideRouterDouble(init?)` / `injectRouterDouble(injector?)` / `createRouterDouble(init?)` _(`/angular-router`)_ | A `Router` derived from one URL — `navigate` spies, `setUrl`, `emitNavigation`, `setCurrentNavigation`, and the router's own URL serialization |
|
|
@@ -4363,6 +4378,7 @@ another, and the next one is in that same file.
|
|
|
4363
4378
|
| `guardDocumentPollution(option)` _(`/setup`)_ | Name the test that left an attribute on `<html>` / `<body>`, before a later file takes another branch on it |
|
|
4364
4379
|
| `consoleDebugSpy` … `consoleWarnSpy` _(`/console`)_ | Silent typed spies replacing the global `console` methods on import |
|
|
4365
4380
|
| `installConsoleSpies()` / `resetConsoleSpies()` / `restoreConsole()` | Install / clear / undo the console spies |
|
|
4381
|
+
| `useConsoleSpies()` _(`/console`)_ | Install the console spies before each test of the block, restore after; returns the spies |
|
|
4366
4382
|
| `consoleOutput()` _(`/console`)_ | Everything the console spies recorded, keyed by channel, only the channels written to — `toStrictEqual({})` is silence |
|
|
4367
4383
|
| `explainSpy(spy, method?)` _(`/diagnostics`)_ | Every configured argument list next to every recorded call, attributed to the config it hit |
|
|
4368
4384
|
| `errorHandler` | The `mustBeCalledWith` argument-mismatch error helper |
|
package/agent-docs/adapters.md
CHANGED
|
@@ -28,21 +28,19 @@ import { createSpyFromClass } from 'vitest-auto-spy/react';
|
|
|
28
28
|
|
|
29
29
|
Console spies — `installConsoleSpies()` replaces `console.debug` / `error` / `info` / `log` / `time` /
|
|
30
30
|
`timeEnd` / `trace` / `warn` with silent typed spies, named `console<Method>Spy`. Install them per test
|
|
31
|
-
and take them off after it
|
|
31
|
+
and take them off after it — `useConsoleSpies()` registers exactly that pair in the enclosing `describe`:
|
|
32
32
|
|
|
33
33
|
```ts
|
|
34
|
-
import {
|
|
34
|
+
import { useConsoleSpies } from 'vitest-auto-spy/console';
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
const { consoleInfoSpy } = useConsoleSpies();
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
consoleSpies = installConsoleSpies();
|
|
40
|
-
});
|
|
41
|
-
afterEach(() => restoreConsole());
|
|
42
|
-
|
|
43
|
-
expect(consoleSpies.consoleInfoSpy).toHaveBeenCalledWith('done'); // the output is silenced, not printed
|
|
38
|
+
expect(consoleInfoSpy).toHaveBeenCalledWith('done'); // the output is silenced, not printed
|
|
44
39
|
```
|
|
45
40
|
|
|
41
|
+
It registers Vitest hooks (Bun resolves them to its own); on `node:test` and Rstest write
|
|
42
|
+
`installConsoleSpies()` in a `beforeEach` and `restoreConsole()` in an `afterEach` yourself.
|
|
43
|
+
|
|
46
44
|
The exported `consoleInfoSpy` & co. are the same objects; `restoreConsole()` keeps them and clears
|
|
47
45
|
their calls. **Do not rely on the import to install them**: it does so once per worker, so under
|
|
48
46
|
`isolate: false` the spies go on in whichever file imported them first and silence every later file
|
package/agent-docs/angular.md
CHANGED
|
@@ -203,7 +203,10 @@ replaced where nothing would notice it — the refusal now names `computed()` sp
|
|
|
203
203
|
that driving the signal the computation reads is the way through. An `input()` is refused outright:
|
|
204
204
|
Angular sets an input through the input node rather than the property, so a replaced one breaks the
|
|
205
205
|
host's next write. Drive an input with `await setInputs(fixture, { … })`, or with
|
|
206
|
-
`renderShallow(Component, { inputs: { … } })` for the value it starts at.
|
|
206
|
+
`renderShallow(Component, { inputs: { … } })` for the value it starts at. When every test of a
|
|
207
|
+
describe renders the same component with the same providers, `prepareShallow(Component, opts)` binds
|
|
208
|
+
them once and each test calls `.create({ inputs: { … } })` — the overrides replace a key outright,
|
|
209
|
+
one instance per test.
|
|
207
210
|
|
|
208
211
|
### Observers the component constructs itself
|
|
209
212
|
|
|
@@ -478,6 +481,7 @@ resource's **default** value and passes.
|
|
|
478
481
|
| `provideHttpTesting(opts?)` | `provideHttpClient(...features)` + `provideHttpClientTesting()`; `{ verifyOnTeardown }` defaults to `true`, and takes `{ ignoreCancelled }` instead of `false`; `{ interceptors: [fn] }` for functional interceptors under test, `{ features: [...] }` for any other `provideHttpClient()` feature |
|
|
479
482
|
| `expectRequest(matcher, opts?)` | tick, then the one match — `.request`, `.flush(body, opts?)`, `.error(status, opts?)` |
|
|
480
483
|
| `expectNoRequest(matcher?, opts?)` | tick, then assert nothing matched; no argument means "nothing at all was requested" |
|
|
484
|
+
| `injectHttpTesting()` | the `HttpTestingController` itself, for a synchronous `expectOne` / `match` / `expectNone` on an Observable service — no tick needed; throws the same missing-providers message |
|
|
481
485
|
| `verifyNoPendingRequests(opts?)` | the teardown check by hand, for mid-test use or a suite with `verifyOnTeardown: false`; `{ ignoreCancelled: true }` forgives a request the code under test unsubscribed from |
|
|
482
486
|
|
|
483
487
|
An interceptor under test goes into the same call, and the teardown check stays armed — do not
|
|
@@ -506,6 +510,12 @@ provided after it, so it wins over `withXhr()`.
|
|
|
506
510
|
|
|
507
511
|
`matcher` is a URL (matched against either `url` or `urlWithParams`), a `RegExp` over
|
|
508
512
|
`urlWithParams`, or a predicate `(request) => boolean`. `{ method }` is case-insensitive.
|
|
513
|
+
`.error(0)` is a network failure; `.error(status, { error })` sets the payload `HttpErrorResponse.error`
|
|
514
|
+
carries (default `new ProgressEvent('error')`). A module that provides a partial `DOCUMENT` double
|
|
515
|
+
fails Angular's tick (`inject(...).body?.querySelector is not a function`, reported as a doubled
|
|
516
|
+
`DOCUMENT`): pass `{ tick: false }` to `expectRequest` / `expectNoRequest` when the request is already out.
|
|
517
|
+
`provideDocumentDouble()` is not partial and needs no option. A JSON error body is a response:
|
|
518
|
+
`flush(body, { status: 409 })`; `{ error }` on `.error()` is a `ProgressEvent`.
|
|
509
519
|
`flush()` and `error()` are `async` because settling requires letting a microtask run — `await` them,
|
|
510
520
|
and the next line reads the settled value.
|
|
511
521
|
|
|
@@ -61,8 +61,9 @@ Part of the agent reference [`AGENTS.md`](../AGENTS.md), which maps every sectio
|
|
|
61
61
|
| an assertion containing a date, with no clock set | `mockSystemTime(iso)` first |
|
|
62
62
|
| `configureTestingModule` inside every `it()` | one per `describe` |
|
|
63
63
|
| `vi.mock('@angular/core')` to neutralise `effect()` | set the signals, `await stable(fixture)`, assert the result |
|
|
64
|
-
| a second `vi.spyOn(console, 'error')` | `consoleErrorSpy` from `vitest-auto-spy/console`, installed with `
|
|
65
|
-
| `import { consoleErrorSpy } from 'vitest-auto-spy/console'` as the install | `
|
|
64
|
+
| a second `vi.spyOn(console, 'error')` | `consoleErrorSpy` from `vitest-auto-spy/console`, installed with `useConsoleSpies()` |
|
|
65
|
+
| `import { consoleErrorSpy } from 'vitest-auto-spy/console'` as the install | `useConsoleSpies()` in the `describe` (the `installConsoleSpies()` / `restoreConsole()` hook pair) — the import installs once per worker |
|
|
66
|
+
| `beforeEach(installConsoleSpies)` with `afterEach(restoreConsole)` repeated per spec | `useConsoleSpies()` — registers both hooks and returns the spies |
|
|
66
67
|
| a bare `vi.spyOn(console, 'error')` to keep a spec quiet | `.mockImplementation(() => undefined)` — without it the line still prints |
|
|
67
68
|
| `mockReadonlyProp(c, 'items', vi.fn(() => []))` | `mockReadonlyProp(c, 'items', signal([]))` — a real signal |
|
|
68
69
|
| `spy.m.mockReturnValue(subject$)` for a `vi.fn(() => subject$)` | `spy.m.mockImplementation(() => subject$)` — the variable is re-read |
|
package/agent-docs/doubles.md
CHANGED
|
@@ -156,7 +156,10 @@ running frame is queued in either mode, so a loop that requests its own next fra
|
|
|
156
156
|
per `flush()`; `frames.flushAll()` runs such a chain until nothing is pending (and throws after 1000
|
|
157
157
|
rounds, on a loop that never stops). A `cancelAnimationFrame` for a handle the stub did not issue — a
|
|
158
158
|
native frame requested before it was installed, such as zoneless Angular's scheduler frame — is
|
|
159
|
-
passed on to the real `cancelAnimationFrame`, so installing the stub after the render leaks no timer.
|
|
159
|
+
passed on to the real `cancelAnimationFrame`, so installing the stub after the render leaks no timer.
|
|
160
|
+
The stub's own handles start above 2^30 (since 5.40.0), so assert a cancel against the handle it
|
|
161
|
+
issued — `expect(frames.cancelAnimationFrame).toHaveBeenLastCalledWith(frames.lastHandle)`, or the
|
|
162
|
+
value `requestAnimationFrame` returned — never against a literal such as `1`. A callback that throws stops `flush()` (or propagates out of `requestAnimationFrame`
|
|
160
163
|
in `'immediate'` mode); pass `onError: (error) => { … }` to intercept it instead — rethrow from
|
|
161
164
|
inside to keep the default for an error you did not mean to swallow. `stubElementRect` returns a real
|
|
162
165
|
`DOMRect` per call, edges derived, plus the installed spy as `.getBoundingClientRect` for
|
package/agent-docs/errors.md
CHANGED
|
@@ -168,6 +168,7 @@ Part of the agent reference [`AGENTS.md`](../AGENTS.md), which maps every sectio
|
|
|
168
168
|
| `expectRequest: no GET …?page=2 — but GET …?page=3 was made; the query differs` | the same path, another query string | name the query the code sends, or match the path alone: `expectRequest('/api/products')` (§13) |
|
|
169
169
|
| `expectRequest: no request matched … — nothing was requested at all` | an `httpResource()` whose `request()` returned no URL yet, or an `HttpClient` call nothing subscribed to | set the signal the resource reads, or subscribe to the call (§13) |
|
|
170
170
|
| `this TestBed has no HttpTestingController` | `expectRequest` / `expectNoRequest` on a module that never got the testing backend | `TestBed.configureTestingModule({ providers: [...provideHttpTesting()] })` from `vitest-auto-spy/angular-http` (§13) |
|
|
171
|
+
| `TestBed.tick() failed, and this TestBed provides its own DOCUMENT` (cause: `inject(...).body?.querySelector is not a function`) | `expectRequest` / `expectNoRequest` ticked the app, and Angular's tick reads members a partial `DOCUMENT` double lacks | `expectRequest(url, { tick: false })` when the request is already out (an `HttpClient` call), or provide a real document — `provideDocumentDouble()` is one: it keeps every member, and an app error under it is rethrown as it is |
|
|
171
172
|
| `X was never answered (end of "…")` / `N requests were never answered` | `provideHttpTesting({ verifyOnTeardown })` or `enableAngularDiagnostics({ pendingRequests })` — the test left a request open; the message names its URL and the test | answer it with the line the message prints (`await expectRequest(url).flush(body)`, `controller.expectOne(url).flush(body)` under diagnostics in a module built on `provideHttpClientTesting()`), or assert its absence (`expectNoRequest(url)`); a cancelled one is forgiven with the `ignoreCancelled` opt-in the message names (§13) |
|
|
172
173
|
| `injectActivatedRoute(): nothing provides ActivatedRoute here` | no `ActivatedRoute` provider in the injector it read | `provideActivatedRoute({ … })` from `vitest-auto-spy/angular-router` in `providers`; `injectActivatedRoute(fixture.debugElement.injector)` for a component-level one (§13) |
|
|
173
174
|
| `the ActivatedRoute here is … not one provideActivatedRoute() built` | a later provider of `ActivatedRoute` won — `provideRouter()`, `RouterModule`, a `useValue`, `provideAutoSpy(ActivatedRoute)` | list `provideActivatedRoute()` last, or drop the other provider (§13) |
|
package/agent-docs/eslint.md
CHANGED
|
@@ -56,6 +56,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
|
|
|
56
56
|
| `no-unregistered-inject-spy` | `error` | — | `injectSpy(X)` for a token this file never registered → the real instance, whose spy helpers exist only for the compiler |
|
|
57
57
|
| `prefer-render-shallow` | `warn` | suggest | `TestBed.createComponent` in a file that never reads the template → `renderShallow(X)`; 0.24× the per-test cycle at 100 children |
|
|
58
58
|
| `prefer-set-inputs` | `warn` | suggest | a run of `fixture.componentRef.setInput('title', v)` on one fixture → `await setInputs(fixture, { title: v })` — the name is resolved against the compiled definition before the first write (an undeclared one is an `NG0303` and no change) and the value is typed. The run collapses into one call and a `detectChanges()` under it goes; offered, not applied, because `stable()` ticks and a zone.js suite answers that with `NG0101` |
|
|
59
|
+
| `prefer-to-have-signal-value` | `warn` | `--fix` | `expect(component.total()).toBe(3)` / `toEqual(…)` → `expect(component.total).toHaveSignalValue(3)`, `toStrictEqual` → `{ strict: true }`; the matcher names the signal in the failure and refuses a value that is not one. **Type-aware**: the signal is recognised by Angular's brand on its type, so methods, functions and getters are never reported, and without `parserOptions.project` it reports nothing |
|
|
59
60
|
| `prefer-spy-on-own-method` | `warn` | `--fix` / suggest | `createSpyFromInstance(x, { onlyMethodsToSpyOn: ['m'], passthrough: true })` read for `m` alone — `.m` on the call, `const { m } = …`, a bare statement, or a name read only as `v.m` → `spyOnOwnMethod(x, 'm')`; the same whitelist with `returns: { m: undefined }` → `spyOnVoidMethod(x, 'm')`. Both fix, import and all; a `Spy<X>` annotation becomes `Spy<X>['m']` in a suggestion. A bare `returns: { m: undefined }` seed is a suggestion, offered only on a real event or element — `new MouseEvent(…)`, `document.createElement(…)`, `fixture.nativeElement` — never on a double |
|
|
60
61
|
| `prefer-observer-stub` | `error` | — | a hand-rolled observer global → `stubIntersectionObserver()` / `stubResizeObserver()` / `stubMutationObserver()`; the manual save-and-restore goes too, `restoreMockedProps()` runs the undo |
|
|
61
62
|
| `no-hand-assigned-global` | `error` | `--fix` | a double assigned to a global (`global.fetch = vi.fn()`, `window.matchMedia = vi.fn()`, `window.localStorage = { getItem: vi.fn() }`) with no restore in `afterEach` / `afterAll` / `onTestFinished` → `mockValueProp(globalThis, name, value)` or `vi.stubGlobal` + `unstubGlobals`; `blockNetwork()` for network globals, `stubWebStorage()` for the storages, `stubWorker({ respond })` for `Worker`; the three observers stay with `prefer-observer-stub`; any value written into an imported object (`environment.production = true`) → `mockValueProp(environment, 'production', true)`, fixed in a test or `beforeEach` |
|
|
@@ -63,7 +64,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
|
|
|
63
64
|
| `prefer-provide-activated-route` | `error` | — | a hand-built `ActivatedRoute` — any `useValue` / `useClass` / `useFactory` / `useExisting`, and `provideAutoSpy(ActivatedRoute)` too → `provideActivatedRoute({ … })`; the double knows either the streams or the snapshot, never both, and `injectActivatedRoute().setParams(…)` moves them together mid-test |
|
|
64
65
|
| `no-passthrough-console-spy` | `error` | suggest | `vi.spyOn(console, m)` nothing gives an implementation — it calls through and prints → `installConsoleSpies()` + `consoleXSpy`, or `.mockImplementation(() => undefined)` |
|
|
65
66
|
| `no-console-in-spec` | `error` | — | a spec calling `console.x(…)` itself, or `console.x = …`, which nothing restores → absorb the code's output through `vitest-auto-spy/console` |
|
|
66
|
-
| `no-import-time-console-spies` | `error` | — | an import of `vitest-auto-spy/console` in a file that never calls `installConsoleSpies()` — the import installs once per worker and silences every later file → `installConsoleSpies()` in `beforeEach`, `restoreConsole()` in `afterEach` |
|
|
67
|
+
| `no-import-time-console-spies` | `error` | — | an import of `vitest-auto-spy/console` in a file that never calls `installConsoleSpies()` or `useConsoleSpies()` — the import installs once per worker and silences every later file → `installConsoleSpies()` in `beforeEach`, `restoreConsole()` in `afterEach` |
|
|
67
68
|
| `no-mistyped-use-value` | `error` | — | `{ provide: TOKEN, useValue }` whose value is not assignable to the primitive `T` of `InjectionToken<T>` (string, number, boolean, bigint, enum, their literals, `null`, `undefined`) — `useValue` is `any`, so `{}` for a `boolean` token compiles and is truthy. **Type-aware**: silent without `parserOptions.project`; object-typed tokens are `no-unknown-use-value-key`'s |
|
|
68
69
|
| `no-unknown-use-value-key` | `error` | — | a key of an object `useValue` literal the provided type does not have — `T` of `InjectionToken<T>`, or the instance type of a `provide:` class; `{ provide: ActivatedRoute, useValue: { queryParams$: … } }` compiles, since `useValue` is `any`. Keys only, never assignability (a partial fixture is fine). **Type-aware**; silent on `any` / `unknown` / `object`, an index signature, a spread's keys, `multi: true` |
|
|
69
70
|
| `no-instance-lifecycle-spy` | `warn` | — | `vi.spyOn(instance, 'ngOnInit')` (and `ngOnDestroy`, `ngDoCheck`, `ngAfterContent*`, `ngAfterView*`) — a view calls the hook read off the prototype, so the instance spy is never called and its stub never runs → `vi.spyOn(Cls.prototype, …)` before `createComponent`, or assert the effect. `ngOnChanges` is exempt: Angular calls it through the instance |
|
|
@@ -84,14 +85,14 @@ blanket downgrade so those keep their severity; do not copy the two names into a
|
|
|
84
85
|
| `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()` — a no-op here, so the spec silently asserts on post-mutation state |
|
|
85
86
|
| `prefer-native-spy-api` | `error` | `--fix` / suggest | `.and` / `.calls` where the spy's own API says the same thing — turn it on for the last mile off the jasmine shim |
|
|
86
87
|
|
|
87
|
-
|
|
88
|
+
Fifty rules, **every one an `error` since 4.0.0 except `prefer-render-shallow`,
|
|
88
89
|
`no-stub-class-double`, `no-structural-double`, `prefer-create-mock`, `no-instance-lifecycle-spy`,
|
|
89
|
-
`prefer-set-inputs`, `no-unasserted-argument
|
|
90
|
-
`no-unknown-use-value-key` read types, and all
|
|
90
|
+
`prefer-set-inputs`, `no-unasserted-argument`, `prefer-spy-on-own-method` and `prefer-to-have-signal-value`**; eight fix on their own, nineteen offer suggestions. Forty-six are syntactic; `no-private-member-access`, `no-mistyped-use-value`,
|
|
91
|
+
`no-unknown-use-value-key` and `prefer-to-have-signal-value` read types, and all four report nothing at all without `parserOptions.project` / `projectService`
|
|
91
92
|
rather than guessing. `no-compile-components` waits the same way for a fact no file holds — which
|
|
92
93
|
builder the project has — and reports nothing until `{ builder: 'inline-resources' }` states it. The config used to be a graded mix of `error` / `warn` / `off`, which decided for the
|
|
93
94
|
consumer how much each finding mattered — a `warn` nothing reads is `off` with extra output. The
|
|
94
|
-
|
|
95
|
+
nine `warn`s left are not judgements about how much a finding matters. `prefer-render-shallow` is
|
|
95
96
|
about the _kind_ of finding: every other rule names something wrong or dead, while this one names a
|
|
96
97
|
file that could render more cheaply, and `renderShallow` is a migration a suite either takes or does
|
|
97
98
|
not. At `error` the plugin would gate that migration — 491 findings across 398 of one consumer's
|