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 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'` (the `setupAutoSpy` default) that had
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.44.0...HEAD
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 | `@angular/common` | ✅ |
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 | `vitest` | ✅ |
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. `provideHttpTesting()` fails, by default, any test that
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 |