vitest-auto-spy 5.44.0 → 5.46.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 +140 -2
- package/README.md +63 -59
- package/agent-docs/angular.md +11 -2
- package/agent-docs/doubles.md +4 -1
- package/agent-docs/errors.md +3 -2
- package/agent-docs/eslint.md +1 -1
- package/agent-docs/return-helpers.md +5 -1
- package/agent-docs/setup.md +11 -3
- package/dist/angular-diagnostics.js +14 -3
- package/dist/angular-http.d.ts +23 -37
- package/dist/angular-http.js +44 -11
- package/dist/angular-matchers.js +3 -8
- package/dist/{chunk-JFOJFCVQ.js → chunk-3JL2YWIG.js} +2 -1
- package/dist/chunk-B6JUBFBA.js +25 -0
- package/dist/cli.js +5 -2
- package/dist/console.d.ts +2 -1
- package/dist/dom-stubs.d.ts +13 -4
- package/dist/dom-stubs.js +9 -5
- package/dist/eslint-plugin.cjs +33 -6
- package/dist/jasmine.js +4 -3
- package/package.json +2 -1
- package/skills/vitest-auto-spy/SKILL.md +7 -5
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,142 @@ The latest released version here must always match the one published on
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [5.46.0] - 2026-09-27
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`stubAnimationFrame(...).lastHandle`: the handle the latest `requestAnimationFrame` returned.**
|
|
18
|
+
5.40.0 moved the stub's handles above 2^30 so a native frame's cancel could be told apart, and a
|
|
19
|
+
spec that asserted `expect(frames.cancelAnimationFrame).toHaveBeenLastCalledWith(1)` broke on the
|
|
20
|
+
upgrade with nothing in that entry saying so. `lastHandle` is `undefined` before the first request,
|
|
21
|
+
so the assertion reads `toHaveBeenLastCalledWith(frames.lastHandle)` and survives any numbering.
|
|
22
|
+
- **`injectHttpTesting()` from `/angular-http`: the `HttpTestingController` `provideHttpTesting()`
|
|
23
|
+
installed.** A spec of a service that returns an Observable has its request out before the method
|
|
24
|
+
returns, so it calls `expectOne` / `match` / `expectNone` synchronously, and `expectRequest` (which
|
|
25
|
+
ticks and flushes asynchronously) is not a drop-in there; such specs kept a
|
|
26
|
+
`TestBed.inject(HttpTestingController)` beside `...provideHttpTesting()`. Without the providers it
|
|
27
|
+
throws the message `expectRequest` gives, naming `provideHttpTesting()`.
|
|
28
|
+
- **`expectRequest(...).error(status, { error })`: the payload the `HttpErrorResponse` carries.** The
|
|
29
|
+
helper always failed the request with `new ProgressEvent('error')`, so a spec that pinned
|
|
30
|
+
`HttpErrorResponse.error` stayed on `HttpTestingController`. The default is unchanged. `error(0)` is
|
|
31
|
+
documented as the network failure it is.
|
|
32
|
+
- **`{ tick: false }` on `expectRequest` and `expectNoRequest`.** Both tick the app first, and a module
|
|
33
|
+
that provides a partial `DOCUMENT` double fails that tick inside Angular
|
|
34
|
+
(`inject(...).body?.querySelector is not a function`). The option skips the tick before the lookup and
|
|
35
|
+
after the answer, for an `HttpClient` call whose request is already out.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **`toHaveSignalValue` and `toHaveResourceValue` compare the contents of a `Set` and a `Map`.** They
|
|
40
|
+
called the matcher context's `equals` without `iterableEquality`, so a collection had no own keys to
|
|
41
|
+
compare and any two Sets, or any two Maps, were equal:
|
|
42
|
+
`expect(signal(new Set(['a']))).toHaveSignalValue(new Set(['b']), { strict: true })` passed, nested
|
|
43
|
+
in an object as well. Both now compare with the testers `toEqual` uses, and `{ strict: true }` with
|
|
44
|
+
those of `toStrictEqual` (array holes inside a collection and `ArrayBuffer` bytes included). The
|
|
45
|
+
non-strict branch also ignored testers registered through `expect.addEqualityTesters`; both modes
|
|
46
|
+
apply them now. An assertion on a Set- or Map-valued signal that passed only because of this can
|
|
47
|
+
newly fail.
|
|
48
|
+
- **A tick that fails on a doubled `DOCUMENT` says so.** `expectRequest` / `expectNoRequest` failed with
|
|
49
|
+
Angular's bare `TypeError`; when the module's `DOCUMENT` is not the real document, the error now names
|
|
50
|
+
it and `{ tick: false }`, with Angular's error as `cause`. A tick failure under the real document is
|
|
51
|
+
rethrown as it was.
|
|
52
|
+
- **`jasmine.mapContaining` / `setContaining` / `arrayWithExactContents` compare a nested `Set` or
|
|
53
|
+
`Map` by its contents**, for the same reason: `new Map([['a', new Set([1])]])` contained
|
|
54
|
+
`new Map([['a', new Set([2])]])`.
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
|
|
58
|
+
- **`stubAnimationFrame`'s spies are typed `Mock<RequestAnimationFrameFn>` /
|
|
59
|
+
`Mock<CancelAnimationFrameFn>`** instead of `(...args: any[]) => any`, so
|
|
60
|
+
`frames.requestAnimationFrame.mock.calls` compiles and is typed. Both function types are exported
|
|
61
|
+
from `/dom-stubs` and spelled without the DOM lib. A spec that called the spy with arguments
|
|
62
|
+
`requestAnimationFrame` does not take now fails to compile.
|
|
63
|
+
|
|
64
|
+
### Size
|
|
65
|
+
|
|
66
|
+
`/angular/matchers` **2799 → 2936 B** min+gzip (+137 B, +4.9 %) and +1.0 kB to import (22906 →
|
|
67
|
+
23887 B, one module more): the shared equality module the signal and resource matchers now use.
|
|
68
|
+
`/jasmine` 19161 → 19203 B (+42 B) and +1.3 kB to import (148816 → 150096 B, one module more): the
|
|
69
|
+
same module, for the collection matchers. `/angular-http` 3770 → 3997 B (+227 B, +6.0 %) and +0.9 kB
|
|
70
|
+
to import (31370 → 32313 B): `injectHttpTesting`, the `{ tick }` option, the doubled-`DOCUMENT`
|
|
71
|
+
message and the `error` payload. `/dom-stubs` +14 B.
|
|
72
|
+
|
|
73
|
+
### Documentation
|
|
74
|
+
|
|
75
|
+
- **The 5.40.0 handle range is a change a spec may have to act on.** A literal handle (`1`, `2`) in an
|
|
76
|
+
assertion against `frames.cancelAnimationFrame` fails from 5.40.0; assert against `frames.lastHandle`
|
|
77
|
+
or the value `requestAnimationFrame` returned. The frame page, `agent-docs/doubles.md`, the README
|
|
78
|
+
and the skill say so.
|
|
79
|
+
- **`SubjectOf` from `/angular` is still rxjs's `Subject` only with `vitest-auto-spy/rxjs` in the
|
|
80
|
+
program.** `agent-docs/return-helpers.md` and the API page recommended the `/angular` export for a
|
|
81
|
+
variable holding `returnSubject()` without repeating that; without the import it is `SubjectLike`,
|
|
82
|
+
so `next()` compiles and `pipe()` does not. `SubjectLike`'s member list in `agent-docs` gains
|
|
83
|
+
`unsubscribe`.
|
|
84
|
+
- **Asserting the errors `flushUnhandledObservableErrors()` returned, without `.map(({ error }) => error)`:**
|
|
85
|
+
`expect(flushUnhandledObservableErrors()).toMatchObject([{ error: new Error('502') }])` checks the
|
|
86
|
+
count and ignores `test`. The setup page and `agent-docs/setup.md` show it.
|
|
87
|
+
- **"Only these console channels" is `expect(Object.keys(consoleOutput())).toStrictEqual(['info'])`**,
|
|
88
|
+
keys in alphabetical order; `{ info: expect.any(Array) }` trips `no-unsafe-assignment`.
|
|
89
|
+
`agent-docs/setup.md` and the `consoleOutput` docs show it.
|
|
90
|
+
|
|
91
|
+
## [5.45.0] - 2026-09-27
|
|
92
|
+
|
|
93
|
+
### Added
|
|
94
|
+
|
|
95
|
+
- **`no-redundant-mock-reset` takes `configFlags`: the flags a factory-built `configFile` sets
|
|
96
|
+
beyond its text.** The rule reads `configFile` as text, so a config such as
|
|
97
|
+
`export default createProjectConfig(…)` that sets `clearMocks: true` inside the factory showed no
|
|
98
|
+
flag. The only way to state it was the inline `clearMocks: true`, which the Angular unit-test
|
|
99
|
+
builder check does not narrow. On Vitest 4, with an `@angular/build:unit-test` /
|
|
100
|
+
`@nx/angular:unit-test` target that names no `runnerConfig`, that reported a clear the builder
|
|
101
|
+
run still needs. `{ configFile, configFlags: { clearMocks?, mockReset?, restoreMocks? } }` reads
|
|
102
|
+
those flags as if the file wrote them: they override its text and apply only to the runs that
|
|
103
|
+
load that file (plain `vitest run`, and a target whose `runnerConfig` resolves to it). A target
|
|
104
|
+
without `runnerConfig` still runs on Vitest's defaults. `configFlags` without `configFile` is a
|
|
105
|
+
configuration error. Inline flags still win and are still not narrowed. The fix line of `doctor`'s
|
|
106
|
+
`mock-reset-config-unread` now suggests `{ configFile, configFlags: { clearMocks: true } }`.
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
|
|
110
|
+
- **`pendingRequests` answers with `expectRequest` in a suite built on `provideHttpTesting()`.**
|
|
111
|
+
An unanswered request in such a suite is usually reported by `enableAngularDiagnostics({
|
|
112
|
+
pendingRequests })` / `assertNoPendingRequests()`, whose fix line read "Answer it in the spec:
|
|
113
|
+
`controller.expectOne('/api/unflushed').flush(body)`" — a controller these specs never inject. The
|
|
114
|
+
line is now `await expectRequest('/api/unflushed').flush(body)` (with `, { method: 'POST' }` when
|
|
115
|
+
two open requests share a URL), the same one `provideHttpTesting()`'s own teardown check gives.
|
|
116
|
+
`provideHttpTesting()` registers a marker provider the diagnostics entry reads from the
|
|
117
|
+
configuration, so `vitest-auto-spy/angular/diagnostics` still does not import `@angular/common`;
|
|
118
|
+
a module configured with `provideHttpClientTesting()` or `HttpClientTestingModule` keeps the
|
|
119
|
+
`controller.expectOne(...)` line.
|
|
120
|
+
|
|
121
|
+
### Documentation
|
|
122
|
+
|
|
123
|
+
- **The `provideHttpTesting({ interceptors })` example in `agent-docs/angular.md` no longer fails
|
|
124
|
+
under `strayTimers`.** It answered `error(401)` to a bare `.subscribe()`, which rxjs rethrows as
|
|
125
|
+
an unhandled Observable error, and 5.44.0 fails the test on that. The example now subscribes with
|
|
126
|
+
an error callback and asserts what it received.
|
|
127
|
+
- **`flushUnhandledObservableErrors()` returns `{ error, outsideTest }` for a rethrow scheduled
|
|
128
|
+
outside a test**, beside `{ error, test }`; the README, the setup page and `agent-docs/setup.md`
|
|
129
|
+
named only the second. `agent-docs/errors.md` also lists the file-level forms of the message
|
|
130
|
+
(`Unhandled Observable errors`, `outside any test`, `while the file was imported`).
|
|
131
|
+
- **The 5.44.0 entry on `strayConsole` routing called `'throw'` the `setupAutoSpy` default.** The
|
|
132
|
+
default is `'off'`; `preset: 'strict'` sets `'throw'`. `agent-docs/setup.md` now also says both
|
|
133
|
+
environment lines get a `Likely cause:`, how `allow` lets one through, and that routing is off
|
|
134
|
+
under `disableConsoleIntercept`.
|
|
135
|
+
- `StrayTimersOptions` is named in `agent-docs/setup.md`; `consoleOutput()` is listed with the
|
|
136
|
+
`/console` entry in the README; `runner-dom-differs-from-builder` names `vite.config.*` too.
|
|
137
|
+
- The skill gains a row for an interceptor under test (`provideHttpTesting({ interceptors, features })`
|
|
138
|
+
instead of `provideHttpClient(withInterceptors([...]))` + `provideHttpClientTesting()`) and names
|
|
139
|
+
the two runner-parity checks in its `doctor` paragraph.
|
|
140
|
+
|
|
141
|
+
### Internal
|
|
142
|
+
|
|
143
|
+
- **`undici` is a devDependency.** The `strayTimers` spec that fetches through the `undici` package
|
|
144
|
+
loaded jsdom's copy with `createRequire(import.meta.resolve('jsdom'))`, so a jsdom release that
|
|
145
|
+
dropped or moved the dependency would have broken the suite with nothing in `package.json` saying
|
|
146
|
+
why. It is now declared at the version the lockfile already held (8.10.2), and imported after the
|
|
147
|
+
`Symbol.dispose` shim it needs on Node 22.
|
|
148
|
+
|
|
13
149
|
## [5.44.0] - 2026-09-27
|
|
14
150
|
|
|
15
151
|
### Added
|
|
@@ -78,7 +214,7 @@ The latest released version here must always match the one published on
|
|
|
78
214
|
reached stderr of a green run, past the guard and past every console spy. The guard now routes that
|
|
79
215
|
console into the one it watches: the line fails the test that caused it at the spec's frame, a
|
|
80
216
|
`/console` spy or `vi.spyOn(…).mockImplementation` absorbs it (and can assert it), and both lines
|
|
81
|
-
get a `Likely cause:`. A suite under `strayConsole: 'throw'` (
|
|
217
|
+
get a `Likely cause:`. A suite under `strayConsole: 'throw'` (set by `preset: 'strict'`) that had
|
|
82
218
|
such lines on stderr now fails on them — that output is what the guard promises to catch. Fix the
|
|
83
219
|
cause (stub the API, mock the navigation, absorb it with a console spy), or let a line through with
|
|
84
220
|
`strayConsole: { allow: ['Not implemented: navigation'] }`. Routing is off when Vitest does not
|
|
@@ -5864,7 +6000,9 @@ moved under 1 %: `/angular` +192 B (+1.0 %) for the override queue and the HTTP
|
|
|
5864
6000
|
This copy ships with the package and carries 5.x only. Releases before 5.0.0 are in the
|
|
5865
6001
|
[full changelog](https://github.com/ASDAlexey/vitest-auto-spy/blob/master/CHANGELOG.md).
|
|
5866
6002
|
|
|
5867
|
-
[Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.
|
|
6003
|
+
[Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.46.0...HEAD
|
|
6004
|
+
[5.46.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.45.0...v5.46.0
|
|
6005
|
+
[5.45.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.44.0...v5.45.0
|
|
5868
6006
|
[5.44.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.43.0...v5.44.0
|
|
5869
6007
|
[5.43.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.42.0...v5.43.0
|
|
5870
6008
|
[5.42.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.41.0...v5.42.0
|
package/README.md
CHANGED
|
@@ -1063,7 +1063,7 @@ error tsconfig-glob-matches-nothing libs/users/tsconfig.spec.json
|
|
|
1063
1063
|
| `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` |
|
|
1064
1064
|
| `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 |
|
|
1065
1065
|
| `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 |
|
|
1066
|
-
| `runner-dom-differs-from-builder` | A `vitest.config.*` on `environment: 'jsdom'` beside a builder target that runs happy-dom — two DOMs for one suite; happy-dom was −20 % Duration
|
|
1066
|
+
| `runner-dom-differs-from-builder` | A `vitest.config.*` / `vite.config.*` on `environment: 'jsdom'` beside a builder target that runs happy-dom — two DOMs for one suite; happy-dom was −20 % Duration |
|
|
1067
1067
|
| `analog-testbed-laxer-than-builder` | Analog `setupTestBed()` without `errorOnUnknownElements` / `errorOnUnknownProperties`, which the builder's TestBed turns on |
|
|
1068
1068
|
| `coverage-all-removed` | `coverage.all` in a config, on a Vitest that stopped reading the key |
|
|
1069
1069
|
| `coverage-include-misses-bundle` | A `coverage.include` of sources only, in a runner config over a bundle |
|
|
@@ -1581,7 +1581,7 @@ Node / Bun / React / Vue project pulls **neither rxjs nor Angular into its runti
|
|
|
1581
1581
|
| `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
1582
|
| `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
1583
|
| `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
|
|
1584
|
+
| `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest`, `expectNoRequest`, `verifyNoPendingRequests`, `injectHttpTesting` — the `httpResource()` / `HttpClient` recipe in two lines, settling included | `@angular/common` | ✅ |
|
|
1585
1585
|
| `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
1586
|
| `vitest-auto-spy/signal-forms` | `createForm` — Angular's own `form()` built where it can inject — and `registerFormMatchers()` for `toHaveFieldErrors` | `@angular/forms` (>=22) | ✅ |
|
|
1587
1587
|
| `vitest-auto-spy/bun` | the same core, driven by Bun's `bun:test` mocks | `bun:test` | ✅ |
|
|
@@ -1592,7 +1592,7 @@ Node / Bun / React / Vue project pulls **neither rxjs nor Angular into its runti
|
|
|
1592
1592
|
| `vitest-auto-spy/react` | the core, with a natural import for React Testing Library suites | — (your `react`) | ✅ |
|
|
1593
1593
|
| `vitest-auto-spy/vue` | `provideAutoSpy` for `global.provide` + Pinia store spying | — (your `vue`/`pinia`) | ✅ |
|
|
1594
1594
|
| `vitest-auto-spy/svelte` | the core, with a natural import for Svelte suites | — (your `svelte`) | ✅ |
|
|
1595
|
-
| `vitest-auto-spy/console` | `consoleInfoSpy` & friends — silent typed spies over the global `console`, installed on import
|
|
1595
|
+
| `vitest-auto-spy/console` | `consoleInfoSpy` & friends — silent typed spies over the global `console`, installed on import; `consoleOutput()` for all of it at once | `vitest` | ✅ |
|
|
1596
1596
|
| `vitest-auto-spy/jasmine` | the drop-in surface for a `jasmine-auto-spies` suite — `.and` / `.calls` / `.withArgs` on every spy, `createSpyObj`, the `jasmine` namespace, `registerJasmineMatchers` | `vitest` | ✅ |
|
|
1597
1597
|
| `vitest-auto-spy/jasmine-compat` | `enableJasmineCompat()` alone — the same `.and` / `.calls` layer, registering no adapter, for `bun test` and `node --test` | — (your runner) | ✅ |
|
|
1598
1598
|
| `vitest-auto-spy/observer-spy` | `subscribeSpyTo` / `ObserverSpy` / `SubscriberSpy` — the `@hirez_io/observer-spy` surface, so `/rxjs` does not carry it | `rxjs` | ✅ |
|
|
@@ -3139,7 +3139,9 @@ it('loads the products', async () => {
|
|
|
3139
3139
|
`expectRequest` ticks before it looks — an `httpResource()` has issued nothing until something does —
|
|
3140
3140
|
and `flush()` / `error()` settle before they resolve, which is why the value reads on the next line.
|
|
3141
3141
|
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. `
|
|
3142
|
+
`{ method }` to tell a read from a write. `error(0)` is a network failure, and
|
|
3143
|
+
`error(status, { error })` sets the payload the `HttpErrorResponse` carries. `{ tick: false }` skips
|
|
3144
|
+
the tick, for a module whose partial `DOCUMENT` double the tick cannot run against. `provideHttpTesting()` fails, by default, any test that
|
|
3143
3145
|
ends holding a request nothing answered; `provideHttpTesting({ verifyOnTeardown: false })` turns that
|
|
3144
3146
|
off, and `verifyNoPendingRequests()` runs the same check by hand. An interceptor under test goes in
|
|
3145
3147
|
the same spread — `provideHttpTesting({ interceptors: [authInterceptor] })`, or
|
|
@@ -3289,7 +3291,7 @@ single-purpose utility you can pick up independently — they all ride on the sa
|
|
|
3289
3291
|
| `stubAbortController()` | `/dom-stubs` | A realm-consistent `AbortController`, so `addEventListener(…, { signal })` works under jsdom + zone.js |
|
|
3290
3292
|
| `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
3293
|
| `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
|
|
3294
|
+
| `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
3295
|
| `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
3296
|
| `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
3297
|
| `flushEventLoopUntil(isDone, opts?)` | core | Real turns until a condition holds — a `resource()` leaving `loading` — with a budget instead of a hang |
|
|
@@ -3339,7 +3341,7 @@ single-purpose utility you can pick up independently — they all ride on the sa
|
|
|
3339
3341
|
| `guardStrayConsole(reaction)` | `/setup` | Fail a test that wrote to the console without absorbing it, and a file that wrote outside any test ([details](#test-run-hygiene)) |
|
|
3340
3342
|
| `withoutStrayTimerTracking(work)` | `/setup` | Run setup work whose timers the stray-timer tracker neither counts nor cancels — jsdom schedules one per Web Storage write |
|
|
3341
3343
|
| `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
|
-
| `flushUnhandledObservableErrors()` | `/setup` | Run rxjs's pending rethrows of Observable errors nothing handled, now, and return `{ error, test }` for each — the check `strayTimers` runs after every test, which fails the test with `Unhandled Observable error`
|
|
3344
|
+
| `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` |
|
|
3343
3345
|
| `guardPrototypePollution(reaction)` | `/setup` | Name the test that left a key on `Object.prototype` — it stops later files collecting ([details](#test-run-hygiene)) |
|
|
3344
3346
|
| `guardDocumentPollution(option)` | `/setup` | Name the test that left an attribute on `<html>` / `<body>`, and put it back ([details](#test-run-hygiene)) |
|
|
3345
3347
|
| `installPerTest(install)` | `/setup` | Re-install a stub before every test of the block — a `describe`-level stub is restored away after the first |
|
|
@@ -3394,7 +3396,8 @@ expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
|
3394
3396
|
Housekeeping: `resetConsoleSpies()` clears the recorded calls between tests (Vitest's
|
|
3395
3397
|
`clearMocks: true` already does that automatically), `restoreConsole()` puts the original
|
|
3396
3398
|
methods back and keeps the spies, so the exports stay live, and `installConsoleSpies()` puts the same
|
|
3397
|
-
spies back on after a restore.
|
|
3399
|
+
spies back on after a restore. `consoleOutput()` is everything the spies recorded as one value,
|
|
3400
|
+
`{ info: [['done']] }`, only the channels written to — for an exact `toStrictEqual`.
|
|
3398
3401
|
|
|
3399
3402
|
They survive `vi.resetModules()`. A fresh copy of the module used to record the previous copy's spy
|
|
3400
3403
|
as "the real `console.warn`", after which `restoreConsole()` installed that dead spy for the rest of
|
|
@@ -3981,57 +3984,57 @@ object of `vi.fn()`s is perfectly reasonable in application code. Flat config on
|
|
|
3981
3984
|
`.eslintrc` `plugins: ['…']` form resolves names to `eslint-plugin-*` packages, which a subpath
|
|
3982
3985
|
export can never be.
|
|
3983
3986
|
|
|
3984
|
-
| Rule | Recommended | Fix | Flags
|
|
3985
|
-
| --------------------------------- | :---------: | ----------------- |
|
|
3986
|
-
| `prefer-provide-auto-spy` | `error` | fix | a hand-rolled `useValue`, `useFactory` **or** `useClass` → `provideAutoSpy(Class)` / `provideAutoSpyForToken(TOKEN)`; and `{ provide: X, useValue: createSpyFromClass(X) }` — the factory written out — which it rewrites. On the `ActivatedRoute` token the advice is `provideActivatedRoute()` from `/angular-router` instead, with a message of its own
|
|
3987
|
-
| `prefer-create-spy-from-class` | `error` | — | an object literal of two or more `vi.fn()`s → `createSpyFromClass` / `createAutoMock`, unless it is a factory's own seed
|
|
3988
|
-
| `no-stub-class-double` | `warn` | — | a class whose fields are `vi.fn()`s → `createSpyFromClass` / `provideAutoSpy`, the stub class deleted
|
|
3989
|
-
| `no-structural-double` | `warn` | — | an object of `vi.fn()`s bound to a name declared `{ load: Mock }` → `createAutoMock<T>()`
|
|
3990
|
-
| `prefer-inject-spy` | `error` | suggest | `vi.spyOn(TestBed.inject(X), 'm')`, in one step or two → `injectSpy(X).m`; `ApplicationRef`, `DestroyRef`, `EnvironmentInjector`, `HttpClient` and `Injector` keep their real instance — option: `{ ignoreTokens }`
|
|
3991
|
-
| `no-object-define-property` | `error` | suggest | `Object.defineProperty` in a spec → `mockReadonlyProp` / `mockValueProp`
|
|
3992
|
-
| `no-expect-in-subscribe` | `error` | suggest | `expect()` inside a `subscribe()` callback → `expectEmission` / `firstValueFrom`
|
|
3993
|
-
| `no-shared-module-level-mock` | `error` | — | an **exported** value holding `vi.fn()`s → export a factory that returns it
|
|
3994
|
-
| `no-mocked-for-spy` | `error` | `--fix` / suggest | `Mocked<T>` in any type position → `Spy<T>`, import and all — a suggestion where the value assigned is not one of this library's factories
|
|
3995
|
-
| `prefer-as-spy` | `error` | `--fix` | `TestBed.inject(X) as Spy<X>` → `asSpy<X>(TestBed.inject(X))`, import and all
|
|
3996
|
-
| `no-done-callback` | `error` | — | a first parameter of a test or hook that is **called**, **passed on** as an argument, or never used → `async` + an awaited assertion, and `done.fail(…)` at the call site. A parameter read only as `ctx.skip()` / `ctx.task` is Vitest's `TestContext` and is left alone, destructured or not
|
|
3997
|
-
| `no-floating-assertion` | `error` | — | `expect()` in a `.then()` nobody awaits → `expect(await promise)`
|
|
3998
|
-
| `no-bare-called-with` | `error` | — | `spy.m.calledWith(1);` as a statement of its own — a stub nobody continued, asserting nothing
|
|
3999
|
-
| `no-overridden-provider` | `error` | suggest | two providers for one token in one array → the earlier one never runs; the exact duplicate can be deleted
|
|
4000
|
-
| `no-inject-before-override` | `error` | — | `TestBed.inject()` / `injectSpy()` / `renderShallow()` in a hook, in a suite that still calls `override*`
|
|
4001
|
-
| `no-private-member-access` | `error` | — | `instance['privateMember']`, `(instance as any).privateMember` and `vi.spyOn(Object.getPrototypeOf(x), 'm')` — the three spellings that get past `private`; **type-aware**, and silent without `parserOptions.project`
|
|
4002
|
-
| `no-reflect-member-access` | `error` | on a double | `Reflect.get(component, 'privateField')` / `Reflect.set(service, 'x', v)` on a subject the test holds — the second door out of `no-private-member-access`, and the one no compiler stands in: the key is an ordinary string argument, so a rename leaves the spec reading a property that is no longer there and `Reflect.set` writing a dead one nothing reads. `window` / `globalThis` and a computed key are never reported; a write onto a double this library built is reported apart and suggests `mockValueProp`
|
|
4003
|
-
| `no-dead-schemas` | `error` | — | `schemas` on a testing module that declares nothing — a charm protecting nobody, and one that starts working the day `declarations` arrive
|
|
4004
|
-
| `no-import-time-spread` | `error` | suggest | `export const x = [...Imported]` at module scope → a `TypeError`, or a silently empty object, while the bundle loads
|
|
4005
|
-
| `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
|
|
4006
|
-
| `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
|
|
4007
|
-
| `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`
|
|
4008
|
-
| `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
|
|
4009
|
-
| `prefer-observer-stub` | `error` | — | a hand-rolled `IntersectionObserver` / `ResizeObserver` / `MutationObserver` written into a global → `stubIntersectionObserver()` and friends, whose undo `restoreMockedProps()` already runs
|
|
4010
|
-
| `no-hand-assigned-global` | `error` | `--fix` | `global.fetch = vi.fn(…)` and any other double assigned to a global with no restore in a teardown hook → `mockValueProp(globalThis, …)` / `vi.stubGlobal` + `unstubGlobals`, `blockNetwork()` for a spec that only stays offline, `stubWebStorage()` for the storages; any value written into an imported object (`environment.production = true`) → `mockValueProp(environment, 'production', true)`, fixed in a test or `beforeEach`
|
|
4011
|
-
| `prefer-stub-response` | `error` | — | an object literal cast to `Response` (`as Response`, `as unknown as Response`, `<Response>{ … }`) or a `createMock<Response>(…)` / `createAutoMock<Response>(…)` call → `stubResponse({ body })` from `/setup`, which builds the platform's own and answers every member; `Response` has to resolve to the global, so an Express handler's or a generated client's envelope of the same name is left alone
|
|
4012
|
-
| `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
|
|
4013
|
-
| `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)`
|
|
4014
|
-
| `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
|
|
4015
|
-
| `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
|
|
4016
|
-
| `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`
|
|
4017
|
-
| `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**
|
|
4018
|
-
| `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
|
|
4019
|
-
| `no-ts-expect-error-on-double` | `error` | — | `@ts-expect-error` / `@ts-ignore` above `nextWith`, `resolveWith`, `mockReturnValue`, `calledWith(…)` and the other helpers that check a stub against the method's signature — an overloaded method takes `Spy<X, { overload: { m: 'first' } }>`, anything else is a fixture of the wrong shape
|
|
4020
|
-
| `no-constant-expect` | `error` | — | `expect(true).toBe(true)`, `expect({ … }).toBeDefined()` — a value the spec spelled out, under a matcher whose answer that value already fixes → assert on what the code produced, or `expect.fail(…)` for a branch the test must not reach
|
|
4021
|
-
| `no-redundant-smoke-test` | `error` | suggest | `it('should create', () => expect(pipe).toBeTruthy())` — a test whose whole body asks whether the subject is there, in a block whose other tests run the same `beforeEach` and would fail first, naming what they were doing → delete it; the suggestion does. A block whose only running test is that one is left alone
|
|
4022
|
-
| `no-self-called-spy` | `error` | — | `vi.spyOn(obj, 'm')`, then `obj.m(…)` written by the test itself, then a positive `toHaveBeenCalled*` — the assertion is satisfied by the test's own line, so the test survives the deletion of the behaviour its title names. Order decides it: a call written **before** the spy is arrangement, and a `mockClear` between the call and the assertion, a negated matcher or arguments the call did not pass all silence it
|
|
4023
|
-
| `no-vacuous-absence-assertion` | `error` | — | a test whose **every** assertion holds on "nothing happened" — a value only a `subscribe` callback writes, or a `vi.fn()` handed to `subscribe`, under a matcher that repeats the declaration (`let x = []` … `expect(x).toEqual([])`) or asserts absence (`toBeUndefined`, `toBeNull`, `toBeFalsy`, `not.toHaveBeenCalled`, `toHaveBeenCalledTimes(0)`) → `await expectNoEmission(source$)` when silence is the claim, `expect(await expectEmission(source$)).toEqual([])` when the empty result is. Silent as soon as one assertion in the test could fail on silence, which is what leaves the "not yet, trigger, now yes" shape alone
|
|
4024
|
-
| `prefer-settle-dynamic-import` | `error` | suggest | `await import('./thing')` — or `import('./thing').then(…)` — written in a test body or a hook to settle a module the code under test lazy-loads → `await settleDynamicImport(() => import('./thing'))`. Awaiting the specifier waits for the **module**; the handler's own continuation, the lines after _its_ `await`, is still queued behind it, so the assertion reads the state one turn early. The suggestion wraps the `import()` and imports the helper. Reported only where the innermost enclosing function is the runner's own callback, which is what exempts a `vi.mock` factory, a lazy route's `loadComponent` and a callback the spec hands to production code
|
|
4025
|
-
| `prefer-create-mock` | `warn` | suggest | an object literal under `as SomeType` — `{ id: '1', isOffline: false } as Device`, and the `<Device>{ … }` spelling → `createMock<Device>({ … })`, which takes a `DeepPartial<Device>` and answers a `Device`. A cast asks whether the two types overlap, so it passes a key the type does not declare **and** a required field the fixture never sets; both type gates stay silent and the fixture is then spread into an expected payload. Silent on `as const`, on `as unknown` / `as any` and the double cast built from them, on a cast of anything that is not a literal, and on a literal inside one of this library's own factories. `warn`, not `error`: the repair makes a drifted fixture red on the next type-check, which is the finding and also a migration taken file by file
|
|
4026
|
-
| `no-mock-cast` | `error` | suggest | `TestBed.inject(S).m as Mock` — a cast to Vitest's `Mock` / `MockInstance` over a **member access** → `injectSpy(S).m`, which the suggestion writes when the token is in view. `Mock` with no parameters is `Mock<any>`, so `mockReturnValue` takes anything and `toHaveBeenCalledWith` stops comparing arguments; the worse form, `(spy.m.mockReturnValue as Mock)(…)`, gets a message of its own because the cast sits on the member that installs the answer. The name has to resolve to a runner's import, so a domain `Mock` is never reported
|
|
4027
|
-
| `no-redundant-mock-reset` | `error` | `--fix` / suggest | a mock reset **the runner already performs** — `vi.clearAllMocks()` / `resetAllMocks()` / `restoreAllMocks()`, `x.mockClear()` / `mockReset()` / `mockRestore()` — first in a `beforeEach` no other `beforeEach` precedes, or a clear closing an `afterEach` → delete it. **Silent until** the runner's flags are known: `['error', { clearMocks: true }]` and siblings, `{ configFile }`, or a `vitest.config.*` / `vite.config.*` above the file, read as text; a factory or `mergeConfig` in another module is not read through, and `setupAutoSpy({ restoreMocks })` is not the runner's flag. The flag has to match the call: `restoreMocks` never reaches a plain `vi.fn()`. `--fix` only where it is the file's only before-hook, a suggestion elsewhere, **never** in a test body
|
|
4028
|
-
| `no-unasserted-argument` | `warn` | — | a bare `expect(spy).toHaveBeenCalled()` where the file itself says the arguments are the point: the same subject is pinned with `toHaveBeenCalledWith(…)` in another test of the file, or the test's title says `with` and its body asserts nothing else → name the arguments. Narrower than `vitest/prefer-called-with` on purpose — 175 findings against its 1941 on the same suite
|
|
4029
|
-
| `no-compile-components` | `error` | suggest | `compileComponents()` under a builder that inlines `templateUrl` / `styleUrls` — a promise already settled; **silent** until `{ builder: 'inline-resources' }` says so, because a JIT setup that loads resources at run time needs the call. One exception the rule cannot see: a component whose template holds a `@defer` block ships async class metadata, so the call stays load-bearing there
|
|
4030
|
-
| `no-sync-testbed-await` | `error` | suggest | `await` on a TestBed call that answers the TestBed or a fixture — `configureTestingModule`, every `override*`, `resetTestingModule`, `createComponent`, `getLastFixture`; none of them is a promise, so the `await` waits for nothing and the `async` it forced on the hook awaits nothing either. The suggestion drops both. Reads no types, so it reports without `parserOptions.project`
|
|
4031
|
-
| `jasmine-namespace-without-entry` | `error` | — | `.and` / `.calls` / `.withArgs` on a library spy in a file that installs the compatibility layer nowhere
|
|
4032
|
-
| `no-jasmine-globals` | `error` | — | `jasmine.*`, `spyOn(` / `spyOnProperty(` / `spyOnAllFunctions(` / `fail(` / `pending(`, `.withContext(` — none of them exist under Vitest
|
|
4033
|
-
| `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()`, which is a no-op here → take the copy at call time
|
|
4034
|
-
| `prefer-native-spy-api` | `error` | `--fix` / suggest | `.and` / `.calls` where the spy's own API says the same thing — the last mile off the jasmine shim
|
|
3987
|
+
| Rule | Recommended | Fix | Flags |
|
|
3988
|
+
| --------------------------------- | :---------: | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
3989
|
+
| `prefer-provide-auto-spy` | `error` | fix | a hand-rolled `useValue`, `useFactory` **or** `useClass` → `provideAutoSpy(Class)` / `provideAutoSpyForToken(TOKEN)`; and `{ provide: X, useValue: createSpyFromClass(X) }` — the factory written out — which it rewrites. On the `ActivatedRoute` token the advice is `provideActivatedRoute()` from `/angular-router` instead, with a message of its own |
|
|
3990
|
+
| `prefer-create-spy-from-class` | `error` | — | an object literal of two or more `vi.fn()`s → `createSpyFromClass` / `createAutoMock`, unless it is a factory's own seed |
|
|
3991
|
+
| `no-stub-class-double` | `warn` | — | a class whose fields are `vi.fn()`s → `createSpyFromClass` / `provideAutoSpy`, the stub class deleted |
|
|
3992
|
+
| `no-structural-double` | `warn` | — | an object of `vi.fn()`s bound to a name declared `{ load: Mock }` → `createAutoMock<T>()` |
|
|
3993
|
+
| `prefer-inject-spy` | `error` | suggest | `vi.spyOn(TestBed.inject(X), 'm')`, in one step or two → `injectSpy(X).m`; `ApplicationRef`, `DestroyRef`, `EnvironmentInjector`, `HttpClient` and `Injector` keep their real instance — option: `{ ignoreTokens }` |
|
|
3994
|
+
| `no-object-define-property` | `error` | suggest | `Object.defineProperty` in a spec → `mockReadonlyProp` / `mockValueProp` |
|
|
3995
|
+
| `no-expect-in-subscribe` | `error` | suggest | `expect()` inside a `subscribe()` callback → `expectEmission` / `firstValueFrom` |
|
|
3996
|
+
| `no-shared-module-level-mock` | `error` | — | an **exported** value holding `vi.fn()`s → export a factory that returns it |
|
|
3997
|
+
| `no-mocked-for-spy` | `error` | `--fix` / suggest | `Mocked<T>` in any type position → `Spy<T>`, import and all — a suggestion where the value assigned is not one of this library's factories |
|
|
3998
|
+
| `prefer-as-spy` | `error` | `--fix` | `TestBed.inject(X) as Spy<X>` → `asSpy<X>(TestBed.inject(X))`, import and all |
|
|
3999
|
+
| `no-done-callback` | `error` | — | a first parameter of a test or hook that is **called**, **passed on** as an argument, or never used → `async` + an awaited assertion, and `done.fail(…)` at the call site. A parameter read only as `ctx.skip()` / `ctx.task` is Vitest's `TestContext` and is left alone, destructured or not |
|
|
4000
|
+
| `no-floating-assertion` | `error` | — | `expect()` in a `.then()` nobody awaits → `expect(await promise)` |
|
|
4001
|
+
| `no-bare-called-with` | `error` | — | `spy.m.calledWith(1);` as a statement of its own — a stub nobody continued, asserting nothing |
|
|
4002
|
+
| `no-overridden-provider` | `error` | suggest | two providers for one token in one array → the earlier one never runs; the exact duplicate can be deleted |
|
|
4003
|
+
| `no-inject-before-override` | `error` | — | `TestBed.inject()` / `injectSpy()` / `renderShallow()` in a hook, in a suite that still calls `override*` |
|
|
4004
|
+
| `no-private-member-access` | `error` | — | `instance['privateMember']`, `(instance as any).privateMember` and `vi.spyOn(Object.getPrototypeOf(x), 'm')` — the three spellings that get past `private`; **type-aware**, and silent without `parserOptions.project` |
|
|
4005
|
+
| `no-reflect-member-access` | `error` | on a double | `Reflect.get(component, 'privateField')` / `Reflect.set(service, 'x', v)` on a subject the test holds — the second door out of `no-private-member-access`, and the one no compiler stands in: the key is an ordinary string argument, so a rename leaves the spec reading a property that is no longer there and `Reflect.set` writing a dead one nothing reads. `window` / `globalThis` and a computed key are never reported; a write onto a double this library built is reported apart and suggests `mockValueProp` |
|
|
4006
|
+
| `no-dead-schemas` | `error` | — | `schemas` on a testing module that declares nothing — a charm protecting nobody, and one that starts working the day `declarations` arrive |
|
|
4007
|
+
| `no-import-time-spread` | `error` | suggest | `export const x = [...Imported]` at module scope → a `TypeError`, or a silently empty object, while the bundle loads |
|
|
4008
|
+
| `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 |
|
|
4009
|
+
| `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 |
|
|
4010
|
+
| `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` |
|
|
4011
|
+
| `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 |
|
|
4012
|
+
| `prefer-observer-stub` | `error` | — | a hand-rolled `IntersectionObserver` / `ResizeObserver` / `MutationObserver` written into a global → `stubIntersectionObserver()` and friends, whose undo `restoreMockedProps()` already runs |
|
|
4013
|
+
| `no-hand-assigned-global` | `error` | `--fix` | `global.fetch = vi.fn(…)` and any other double assigned to a global with no restore in a teardown hook → `mockValueProp(globalThis, …)` / `vi.stubGlobal` + `unstubGlobals`, `blockNetwork()` for a spec that only stays offline, `stubWebStorage()` for the storages; any value written into an imported object (`environment.production = true`) → `mockValueProp(environment, 'production', true)`, fixed in a test or `beforeEach` |
|
|
4014
|
+
| `prefer-stub-response` | `error` | — | an object literal cast to `Response` (`as Response`, `as unknown as Response`, `<Response>{ … }`) or a `createMock<Response>(…)` / `createAutoMock<Response>(…)` call → `stubResponse({ body })` from `/setup`, which builds the platform's own and answers every member; `Response` has to resolve to the global, so an Express handler's or a generated client's envelope of the same name is left alone |
|
|
4015
|
+
| `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 |
|
|
4016
|
+
| `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)` |
|
|
4017
|
+
| `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 |
|
|
4018
|
+
| `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 |
|
|
4019
|
+
| `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` |
|
|
4020
|
+
| `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** |
|
|
4021
|
+
| `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 |
|
|
4022
|
+
| `no-ts-expect-error-on-double` | `error` | — | `@ts-expect-error` / `@ts-ignore` above `nextWith`, `resolveWith`, `mockReturnValue`, `calledWith(…)` and the other helpers that check a stub against the method's signature — an overloaded method takes `Spy<X, { overload: { m: 'first' } }>`, anything else is a fixture of the wrong shape |
|
|
4023
|
+
| `no-constant-expect` | `error` | — | `expect(true).toBe(true)`, `expect({ … }).toBeDefined()` — a value the spec spelled out, under a matcher whose answer that value already fixes → assert on what the code produced, or `expect.fail(…)` for a branch the test must not reach |
|
|
4024
|
+
| `no-redundant-smoke-test` | `error` | suggest | `it('should create', () => expect(pipe).toBeTruthy())` — a test whose whole body asks whether the subject is there, in a block whose other tests run the same `beforeEach` and would fail first, naming what they were doing → delete it; the suggestion does. A block whose only running test is that one is left alone |
|
|
4025
|
+
| `no-self-called-spy` | `error` | — | `vi.spyOn(obj, 'm')`, then `obj.m(…)` written by the test itself, then a positive `toHaveBeenCalled*` — the assertion is satisfied by the test's own line, so the test survives the deletion of the behaviour its title names. Order decides it: a call written **before** the spy is arrangement, and a `mockClear` between the call and the assertion, a negated matcher or arguments the call did not pass all silence it |
|
|
4026
|
+
| `no-vacuous-absence-assertion` | `error` | — | a test whose **every** assertion holds on "nothing happened" — a value only a `subscribe` callback writes, or a `vi.fn()` handed to `subscribe`, under a matcher that repeats the declaration (`let x = []` … `expect(x).toEqual([])`) or asserts absence (`toBeUndefined`, `toBeNull`, `toBeFalsy`, `not.toHaveBeenCalled`, `toHaveBeenCalledTimes(0)`) → `await expectNoEmission(source$)` when silence is the claim, `expect(await expectEmission(source$)).toEqual([])` when the empty result is. Silent as soon as one assertion in the test could fail on silence, which is what leaves the "not yet, trigger, now yes" shape alone |
|
|
4027
|
+
| `prefer-settle-dynamic-import` | `error` | suggest | `await import('./thing')` — or `import('./thing').then(…)` — written in a test body or a hook to settle a module the code under test lazy-loads → `await settleDynamicImport(() => import('./thing'))`. Awaiting the specifier waits for the **module**; the handler's own continuation, the lines after _its_ `await`, is still queued behind it, so the assertion reads the state one turn early. The suggestion wraps the `import()` and imports the helper. Reported only where the innermost enclosing function is the runner's own callback, which is what exempts a `vi.mock` factory, a lazy route's `loadComponent` and a callback the spec hands to production code |
|
|
4028
|
+
| `prefer-create-mock` | `warn` | suggest | an object literal under `as SomeType` — `{ id: '1', isOffline: false } as Device`, and the `<Device>{ … }` spelling → `createMock<Device>({ … })`, which takes a `DeepPartial<Device>` and answers a `Device`. A cast asks whether the two types overlap, so it passes a key the type does not declare **and** a required field the fixture never sets; both type gates stay silent and the fixture is then spread into an expected payload. Silent on `as const`, on `as unknown` / `as any` and the double cast built from them, on a cast of anything that is not a literal, and on a literal inside one of this library's own factories. `warn`, not `error`: the repair makes a drifted fixture red on the next type-check, which is the finding and also a migration taken file by file |
|
|
4029
|
+
| `no-mock-cast` | `error` | suggest | `TestBed.inject(S).m as Mock` — a cast to Vitest's `Mock` / `MockInstance` over a **member access** → `injectSpy(S).m`, which the suggestion writes when the token is in view. `Mock` with no parameters is `Mock<any>`, so `mockReturnValue` takes anything and `toHaveBeenCalledWith` stops comparing arguments; the worse form, `(spy.m.mockReturnValue as Mock)(…)`, gets a message of its own because the cast sits on the member that installs the answer. The name has to resolve to a runner's import, so a domain `Mock` is never reported |
|
|
4030
|
+
| `no-redundant-mock-reset` | `error` | `--fix` / suggest | a mock reset **the runner already performs** — `vi.clearAllMocks()` / `resetAllMocks()` / `restoreAllMocks()`, `x.mockClear()` / `mockReset()` / `mockRestore()` — first in a `beforeEach` no other `beforeEach` precedes, or a clear closing an `afterEach` → delete it. **Silent until** the runner's flags are known: `['error', { clearMocks: true }]` and siblings, `{ configFile }`, or a `vitest.config.*` / `vite.config.*` above the file, read as text; a factory or `mergeConfig` in another module is not read through (name its flags as `{ configFile, configFlags: { clearMocks: true } }`), and `setupAutoSpy({ restoreMocks })` is not the runner's flag. The flag has to match the call: `restoreMocks` never reaches a plain `vi.fn()`. `--fix` only where it is the file's only before-hook, a suggestion elsewhere, **never** in a test body |
|
|
4031
|
+
| `no-unasserted-argument` | `warn` | — | a bare `expect(spy).toHaveBeenCalled()` where the file itself says the arguments are the point: the same subject is pinned with `toHaveBeenCalledWith(…)` in another test of the file, or the test's title says `with` and its body asserts nothing else → name the arguments. Narrower than `vitest/prefer-called-with` on purpose — 175 findings against its 1941 on the same suite |
|
|
4032
|
+
| `no-compile-components` | `error` | suggest | `compileComponents()` under a builder that inlines `templateUrl` / `styleUrls` — a promise already settled; **silent** until `{ builder: 'inline-resources' }` says so, because a JIT setup that loads resources at run time needs the call. One exception the rule cannot see: a component whose template holds a `@defer` block ships async class metadata, so the call stays load-bearing there |
|
|
4033
|
+
| `no-sync-testbed-await` | `error` | suggest | `await` on a TestBed call that answers the TestBed or a fixture — `configureTestingModule`, every `override*`, `resetTestingModule`, `createComponent`, `getLastFixture`; none of them is a promise, so the `await` waits for nothing and the `async` it forced on the hook awaits nothing either. The suggestion drops both. Reads no types, so it reports without `parserOptions.project` |
|
|
4034
|
+
| `jasmine-namespace-without-entry` | `error` | — | `.and` / `.calls` / `.withArgs` on a library spy in a file that installs the compatibility layer nowhere |
|
|
4035
|
+
| `no-jasmine-globals` | `error` | — | `jasmine.*`, `spyOn(` / `spyOnProperty(` / `spyOnAllFunctions(` / `fail(` / `pending(`, `.withContext(` — none of them exist under Vitest |
|
|
4036
|
+
| `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()`, which is a no-op here → take the copy at call time |
|
|
4037
|
+
| `prefer-native-spy-api` | `error` | `--fix` / suggest | `.and` / `.calls` where the spy's own API says the same thing — the last mile off the jasmine shim |
|
|
4035
4038
|
|
|
4036
4039
|
Every message ends with a link to the matching [recipe](#how-to-mock): a rule that only says
|
|
4037
4040
|
"don't" moves the problem rather than solving it. Rules travel with the API they recommend, so they
|
|
@@ -4316,7 +4319,7 @@ another, and the next one is in that same file.
|
|
|
4316
4319
|
| `stubAbortController()` | A realm-consistent `AbortController` / `AbortSignal` for jsdom + zone.js, statics (`abort`, `timeout`, `any`) included |
|
|
4317
4320
|
| `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 |
|
|
4318
4321
|
| `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>` |
|
|
4319
|
-
| `stubAnimationFrame(opts?)` _(`/dom-stubs`)_ | Frames on the spec's schedule: `'immediate'` or `'queued'` + `flush()`, `pending`, both globals as spies; returns `AnimationFrameStub`
|
|
4322
|
+
| `stubAnimationFrame(opts?)` _(`/dom-stubs`)_ | Frames on the spec's schedule: `'immediate'` or `'queued'` + `flush()`, `pending`, `lastHandle`, both globals as spies; returns `AnimationFrameStub` |
|
|
4320
4323
|
| `stubElementRect(el, rect?)` _(`/dom-stubs`)_ | A box for `getBoundingClientRect()` — missing fields `0`, edges derived; returns `ElementRectRestore` (callable undo plus `.getBoundingClientRect`) |
|
|
4321
4324
|
| `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 |
|
|
4322
4325
|
| `flushEventLoop(turns?)` / `settleDynamicImport(load, turns?)` | Real event-loop turns under fake timers; wait for a dynamic `import()` |
|
|
@@ -4338,6 +4341,7 @@ another, and the next one is in that same file.
|
|
|
4338
4341
|
| `settleResource(resource, opts?)` _(Angular)_ | Tick until an `httpResource()` / `resource()` / `rxResource()` leaves `loading` |
|
|
4339
4342
|
| `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 |
|
|
4340
4343
|
| `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 |
|
|
4344
|
+
| `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 |
|
|
4341
4345
|
| `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 |
|
|
4342
4346
|
| `createActivatedRoute(init?)` _(`/angular-router`)_ | The same double without a `TestBed` — `.route` plus the setters |
|
|
4343
4347
|
| `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 |
|