vitest-auto-spy 5.21.1 → 5.23.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.
Files changed (55) hide show
  1. package/AGENTS.md +144 -63
  2. package/README.md +141 -78
  3. package/dist/angular-diagnostics.js +8 -8
  4. package/dist/angular-doubles.js +7 -7
  5. package/dist/angular-http.d.ts +14 -2
  6. package/dist/angular-http.js +17 -9
  7. package/dist/angular-router.d.ts +93 -6
  8. package/dist/angular-router.js +112 -15
  9. package/dist/bun-angular.d.ts +1 -1
  10. package/dist/bun-angular.js +16 -16
  11. package/dist/bun.d.ts +103 -1
  12. package/dist/bun.js +9 -9
  13. package/dist/{chunk-NEFATXWT.js → chunk-45W5LBPY.js} +5 -5
  14. package/dist/{chunk-R3DF5ZEO.js → chunk-BBPYNKGB.js} +1 -1
  15. package/dist/{chunk-ITOFGQTX.js → chunk-CPLUHRHD.js} +1 -1
  16. package/dist/{chunk-I6JZIWIZ.js → chunk-DQ3SPSCH.js} +3 -3
  17. package/dist/{chunk-OEE74SMY.js → chunk-E5M7DBWB.js} +31 -7
  18. package/dist/{chunk-RPCKHTDN.js → chunk-JBV35SU3.js} +1 -1
  19. package/dist/{chunk-4SJY7ZHM.js → chunk-LZVIH54N.js} +2 -2
  20. package/dist/{chunk-S5ARWUD4.js → chunk-MBSPAUUD.js} +3 -3
  21. package/dist/{chunk-SA2QIER3.js → chunk-MG7LKE4R.js} +1 -1
  22. package/dist/{chunk-2KBFEQOV.js → chunk-MUZZBRUP.js} +2 -2
  23. package/dist/{chunk-JJVTDL5R.js → chunk-QO4BPR3V.js} +3 -0
  24. package/dist/{chunk-DPTWTLIG.js → chunk-U5NJJ4ZN.js} +1 -1
  25. package/dist/{chunk-BN2QV45R.js → chunk-V6TIK7XU.js} +2 -2
  26. package/dist/{chunk-NS3Y6AQB.js → chunk-WYYKUPXW.js} +1 -1
  27. package/dist/{chunk-QT5JIDOK.js → chunk-YEQQIKQR.js} +1 -1
  28. package/dist/{chunk-TQAYWU5J.js → chunk-ZCWAFGHX.js} +2 -2
  29. package/dist/cli.js +10 -1
  30. package/dist/console.js +7 -7
  31. package/dist/eslint-plugin.cjs +1240 -239
  32. package/dist/eslint-plugin.d.ts +2 -0
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.js +25 -1
  35. package/dist/jasmine-compat.js +2 -2
  36. package/dist/jasmine.js +10 -10
  37. package/dist/nestjs.js +10 -10
  38. package/dist/node.cjs +25 -0
  39. package/dist/node.d.cts +1 -0
  40. package/dist/node.d.ts +1 -1
  41. package/dist/node.js +25 -1
  42. package/dist/react.d.ts +1 -1
  43. package/dist/react.js +25 -1
  44. package/dist/rstest.d.ts +1 -1
  45. package/dist/rstest.js +9 -9
  46. package/dist/rxjs.js +2 -2
  47. package/dist/setup.d.ts +12 -3
  48. package/dist/setup.js +8 -2
  49. package/dist/signal-forms.js +1 -1
  50. package/dist/svelte.d.ts +1 -1
  51. package/dist/svelte.js +25 -1
  52. package/dist/vue.d.ts +1 -1
  53. package/dist/vue.js +25 -1
  54. package/package.json +1 -1
  55. package/skills/vitest-auto-spy/SKILL.md +22 -2
package/AGENTS.md CHANGED
@@ -1300,6 +1300,29 @@ line that triggers the source, because the `await` is the same statement as the
1300
1300
  the test deadlocks against a source that only emits once something pokes it. Hold the promise
1301
1301
  first, poke, then await.
1302
1302
 
1303
+ ### `createLog()` — the order between the spies
1304
+
1305
+ ```ts
1306
+ const log = createLog<'drop-cache' | 'flush-telemetry' | 'stop-engine'>();
1307
+
1308
+ engine.onShutdown(log.fn('drop-cache'));
1309
+ engine.onShutdown(log.fn('stop-engine'));
1310
+ engine.onShutdown(log.fn('flush-telemetry'));
1311
+
1312
+ engine.shutdown();
1313
+
1314
+ expect(log.result()).toBe('drop-cache; flush-telemetry; stop-engine'); // fails with the real order
1315
+ ```
1316
+
1317
+ Emission helpers answer a sequence _within one source_. The order of calls **across** collaborators
1318
+ is the gap `toHaveBeenCalled` papers over — three green checks that would accept the sequence
1319
+ backwards — and `toHaveBeenCalledBefore` covers pairwise. One journal the code under test writes into
1320
+ (`add`, `fn(value)` for a labelled callback, `clear`, `items`, `result()`) makes the sequence a
1321
+ single comparable value; `T` is a string union so a step nobody declared is a compile error. In
1322
+ Angular, provide it and let the component report its own lifecycle:
1323
+ `providers: [{ provide: PANEL_LOG, useValue: log }]`. Ported from Angular's own `Log`, which the
1324
+ framework keeps three copies of.
1325
+
1303
1326
  ---
1304
1327
 
1305
1328
  ## 9. Patching properties (and putting them back)
@@ -1659,6 +1682,14 @@ a behaviour change —`stubConstructor(globalThis, 'WebSocket', …)` is the too
1659
1682
  Plain data in `body` goes out as JSON with `application/json`; an `ok` that disagrees with
1660
1683
  `status` throws. A body can be read once: for a stub answering several calls use
1661
1684
  `mockImplementation(async () => stubResponse(…))`, not `mockResolvedValue`.
1685
+ - **`body: null` is the JSON literal, and only `undefined` or an omitted `body` send no body.** It
1686
+ reverses what the field used to mean: `null` sent no body, so `.json()` threw
1687
+ `Unexpected end of JSON input` on it while `0`, `false`, `[]` and `{}` round-tripped — the one
1688
+ literal a reader had no reason to expect to be different, and the shape a backend answering
1689
+ "nothing here" (an empty login, an absent profile) really sends. A spec that meant "no body"
1690
+ drops the field. `{ body: null, status: 204 }` throws by name rather than letting the constructor
1691
+ raise "Response with null body status cannot have body" about a field whose old meaning was the
1692
+ opposite; 205 and 304 are the same.
1662
1693
  - `blockNetwork` leaves `fetch` alone while `Symbol.for('fetch-interceptor')` is on `globalThis`
1663
1694
  (MSW `setupServer`, nock 14), so MSW handlers keep answering; XHR stays blocked for what MSW does
1664
1695
  not handle. MSW's `onUnhandledRequest: 'error'` exempts asset-looking URLs (`.svg`, `.json`,
@@ -2703,12 +2734,12 @@ response reaches the resource, tick again so the view reading it is current. Mis
2703
2734
  `expectOne` reports a request that was never sent; miss the fifth and the assertion reads the
2704
2735
  resource's **default** value and passes.
2705
2736
 
2706
- | Call | Does |
2707
- | ---------------------------------- | ----------------------------------------------------------------------------------------------- |
2708
- | `provideHttpTesting(opts?)` | `provideHttpClient()` + `provideHttpClientTesting()`; `{ verifyOnTeardown }` defaults to `true` |
2709
- | `expectRequest(matcher, opts?)` | tick, then the one match — `.request`, `.flush(body, opts?)`, `.error(status, opts?)` |
2710
- | `expectNoRequest(matcher?, opts?)` | tick, then assert nothing matched; no argument means "nothing at all was requested" |
2711
- | `verifyNoPendingRequests()` | the teardown check by hand, for mid-test use or a suite with `verifyOnTeardown: false` |
2737
+ | Call | Does |
2738
+ | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2739
+ | `provideHttpTesting(opts?)` | `provideHttpClient()` + `provideHttpClientTesting()`; `{ verifyOnTeardown }` defaults to `true`, and takes `{ ignoreCancelled }` instead of `false` |
2740
+ | `expectRequest(matcher, opts?)` | tick, then the one match — `.request`, `.flush(body, opts?)`, `.error(status, opts?)` |
2741
+ | `expectNoRequest(matcher?, opts?)` | tick, then assert nothing matched; no argument means "nothing at all was requested" |
2742
+ | `verifyNoPendingRequests(opts?)` | the teardown check by hand, for mid-test use or a suite with `verifyOnTeardown: false`; `{ ignoreCancelled: true }` forgives a request the code under test unsubscribed from |
2712
2743
 
2713
2744
  `matcher` is a URL (matched against either `url` or `urlWithParams`), a `RegExp` over
2714
2745
  `urlWithParams`, or a predicate `(request) => boolean`. `{ method }` is case-insensitive.
@@ -2755,11 +2786,11 @@ is `undefined` on the `{ params: of(…) }` one, and a spec that updates one hal
2755
2786
  no navigation produces. `provideActivatedRoute(init)` provides **Angular's own `ActivatedRoute`**
2756
2787
  built over one record, so every stream, both `ParamMap`s and `snapshot` read the same values.
2757
2788
 
2758
- | Call | Does |
2759
- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
2760
- | `provideActivatedRoute(init?)` | a `FactoryProvider` — a fresh route per injector; `init`: `params`, `queryParams`, `data`, `fragment`, `url`, `outlet`, `component`, `routeConfig` |
2761
- | `injectActivatedRoute(injector?)` | the handle: `.route`, `setParams`, `setQueryParams`, `setData`, `setFragment`, `setUrl`, `set({ … })` |
2762
- | `createActivatedRoute(init?)` | the same handle without a `TestBed` — for `new Page(route)`, or a guard given `route.snapshot` |
2789
+ | Call | Does |
2790
+ | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2791
+ | `provideActivatedRoute(init?)` | a `FactoryProvider` — a fresh route per injector; `init`: `params`, `queryParams`, `data`, `title`, `fragment`, `url`, `outlet`, `component`, `routeConfig`, `resolve` |
2792
+ | `injectActivatedRoute(injector?)` | the handle: `.route`, `setParams`, `setQueryParams`, `setData`, `setFragment`, `setUrl`, `set({ … })` |
2793
+ | `createActivatedRoute(init?)` | the same handle without a `TestBed` — for `new Page(route)`, or a guard given `route.snapshot` |
2763
2794
 
2764
2795
  A setter behaves like the router after a navigation: it **replaces** (not merges) that part, builds
2765
2796
  a new snapshot first, then emits only the streams whose value changed (router equality: same keys,
@@ -2773,11 +2804,14 @@ Four things to know:
2773
2804
  still works as `relativeTo` for `router.createUrlTree`.
2774
2805
  - **One node, no tree.** `root` is the route itself, `parent` / `firstChild` are `null`. For
2775
2806
  `route.parent.params` patch that member with `mockReadonlyProp`, or use `RouterTestingHarness`.
2776
- - **No `title`, no navigation, no input binding** — `title` emits `undefined`, `Router.navigate`
2777
- does not move it (the setters do), and `withComponentInputBinding()` is the outlet's job
2778
- (`fixture.componentRef.setInput`).
2807
+ - **`title` is given, not resolved; no navigation, no input binding.** `provideActivatedRoute({
2808
+ title: 'Product 7' })` answers `route.snapshot.title` — the double puts the string under the
2809
+ router's own `RouteTitleKey`, read off the installed router, and does not run a `title: () => …`
2810
+ resolver. `Router.navigate` does not move it (the setters do), and `withComponentInputBinding()`
2811
+ is the outlet's job (`fixture.componentRef.setInput`).
2779
2812
  - **Its own entry, like `/angular-http`.** It does not re-export the core, imports no runner, and is
2780
- the only file that reaches `@angular/router` — an optional peer.
2813
+ the only file that reaches `@angular/router` — an optional peer. The `Location` double below wraps
2814
+ `@angular/common/testing`, which `@angular/router` itself depends on, so it adds no peer.
2781
2815
 
2782
2816
  ### `Router` from one URL — `vitest-auto-spy/angular-router`
2783
2817
 
@@ -2803,6 +2837,7 @@ redirect a guard builds. `provideRouterDouble(init)` keeps one URL and derives t
2803
2837
  | `provideRouterDouble(init?)` | a `FactoryProvider` for the `Router` token — a fresh router per injector; `init` is `{ url }`, default `'/'` |
2804
2838
  | `injectRouterDouble(injector?)` | the handle: `.router`, `.navigate`, `.navigateByUrl`, `setUrl(url)`, `emitNavigation(event?)`, `setCurrentNavigation(navigation?)` |
2805
2839
  | `createRouterDouble(init?)` | the same handle without a `TestBed` — for `new AuthGuard(router)` |
2840
+ | `collectRouterEvents(events)` | the recording of what `router.events` emits, `expect([[Class, url], …])` in one line — Angular's own integration-spec idiom |
2806
2841
 
2807
2842
  Unlike the route, this is **not** an instance of Angular's class: a real `Router` drags the whole
2808
2843
  routing stack in. It is a structural double that answers `url` (serialized as the real router does),
@@ -2821,7 +2856,9 @@ Five things to know:
2821
2856
  `url`. `setUrl()` and `emitNavigation()` move it; `RouterTestingHarness` over a real
2822
2857
  `provideRouter()` is what tests a navigation.
2823
2858
  - **`emitNavigation()` takes what you have** — nothing, a URL string, or an event you built. A
2824
- `NavigationEnd` moves the URL with it; any other event does not.
2859
+ `NavigationEnd` moves the URL with it; any other event does not. It resolves once the event is
2860
+ delivered and a navigation it ended is back to `null` — the moment `navigate()` resolves in an
2861
+ application; the work is synchronous, so ignoring the promise changes nothing.
2825
2862
  - **The navigation in flight follows the events, and a terminal event ends it _after_ delivering
2826
2863
  it.** `setCurrentNavigation({ extras: { state } })` puts one up and `setCurrentNavigation(null)`
2827
2864
  ends it; a `NavigationStart` pushed through `emitNavigation()` starts one with that event's id,
@@ -2839,6 +2876,28 @@ Five things to know:
2839
2876
  - **It builds spies**, so a runtime entry has to be imported in the suite (`vitest-auto-spy/angular`
2840
2877
  in the same file, or the setup file); the route double needs none.
2841
2878
 
2879
+ ### `Location` from Angular's own testing classes — `vitest-auto-spy/angular-router`
2880
+
2881
+ ```ts
2882
+ import { injectLocationDouble, provideLocationDouble } from 'vitest-auto-spy/angular-router';
2883
+
2884
+ TestBed.configureTestingModule({ providers: [provideLocationDouble()] });
2885
+
2886
+ const location = injectLocationDouble();
2887
+
2888
+ location.go('/reports/7');
2889
+ location.simulateUrlPop('/'); // the popstate no method call can cause
2890
+ ```
2891
+
2892
+ **A wrap, not a rival.** Angular ships the double — `SpyLocation` with a real history array, a
2893
+ `urlChanges` journal for assertions, and `simulateUrlPop()` / `simulateHashChange()` for the
2894
+ browser's half of the contract — and this entry puts it in the family shape of the route and the
2895
+ router: one provider call, an `injectLocationDouble()` whose errors name the instance that won
2896
+ (`Location` is `providedIn: 'root'`, so the quiet failure is the real `Location` nobody configured).
2897
+ `createLocationDouble()` is the same without a `TestBed`. One asymmetry worth knowing: `back()` and
2898
+ `forward()` wake the popstate subscribers but do not write `urlChanges` — the journal holds what the
2899
+ app asked for, the subscribers carry what the browser did.
2900
+
2842
2901
  ### Signal forms — `vitest-auto-spy/signal-forms`
2843
2902
 
2844
2903
  ```ts
@@ -3615,55 +3674,65 @@ export default [
3615
3674
  errors — `prefer-as-spy` (`TS2352`) and `no-mocked-for-spy` (`TS2322`). Spread its `rules` after a
3616
3675
  blanket downgrade so those keep their severity; do not copy the two names into a consumer's config.
3617
3676
 
3618
- | Rule | Level | Fix | Flags |
3619
- | --------------------------------- | ------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
3620
- | `no-expect-in-subscribe` | `error` | suggest | `expect()` inside `subscribe()` → `expectEmission` / `firstValueFrom` |
3621
- | `no-object-define-property` | `error` | suggest | `Object.defineProperty` in a spec → `mockReadonlyProp` / `mockValueProp` |
3622
- | `prefer-provide-auto-spy` | `error` | fix | a hand-rolled `useValue`, `useFactory`, `useClass` or `useExisting` (also `useValue: new StubMock()`), in a provider **or** a `TestBed.overrideProvider(X, { … })` → `provideAutoSpy(Class)` / `provideAutoSpyForToken(TOKEN)`. A name in the slot is followed one step, through an initialiser or a single `beforeEach` assignment. `{ provide: X, useValue: createSpyFromClass(X, config) }` — the factory's own body, written out — is reported and **rewritten** to `provideAutoSpy(X, config)`; a double read from a _different_ class (`{ provide: LocalStorage, useValue: createSpyFromClass(BaseLocalStorage) }`) is not, since the abstract token has no prototype for `provideAutoSpy` to read. On the `ActivatedRoute` token — in a provider or in an `overrideProvider` — the advice is `provideActivatedRoute()` from `/angular-router` instead, with a message of its own, so the two route rules agree |
3623
- | `prefer-create-spy-from-class` | `error` | — | an object literal of 2+ `vi.fn()`s → `createSpyFromClass` (a factory's own seed is exempt) |
3624
- | `no-stub-class-double` | `warn` | — | a class whose fields are `vi.fn()`s → `createSpyFromClass` / `provideAutoSpy` and delete the stub; a decorated, `implements`-ing, `extends`-ing or unnamed class is exempt — option: `{ minRunnerFns }` |
3625
- | `no-structural-double` | `warn` | — | an object of `vi.fn()`s bound to a name declared `{ load: Mock }` → `createAutoMock<T>()`; a bare `let fn: Mock` is a callback and never reported — option: `{ minRunnerFns }` |
3626
- | `prefer-inject-spy` | `error` | suggest | `vi.spyOn(TestBed.inject(X), 'm')`, inline or via a `const` → `injectSpy(X).m`; `ApplicationRef`, `DestroyRef`, `EnvironmentInjector`, `HttpClient` and `Injector` keep their real instance — option: `{ ignoreTokens }` |
3627
- | `no-shared-module-level-mock` | `error` | — | an **exported** value holding `vi.fn()`s → export a factory instead |
3628
- | `no-mocked-for-spy` | `error` | `--fix` / suggest | `Mocked<T>` in any type position → `Spy<T>`, import and all — a suggestion where the assigned value is not from a factory of this library |
3629
- | `prefer-as-spy` | `error` | `--fix` | `TestBed.inject(X) as Spy<X>` → `asSpy<X>(TestBed.inject(X))`, import and all |
3630
- | `no-done-callback` | `error` | — | a first parameter of a test or hook that is **called**, **passed on** as an argument, or never used at all → `async` + an awaited assertion, and `done.fail(…)` at the call site. A parameter read only as `ctx.skip()` / `ctx.task` / `ctx.expect` is Vitest's `TestContext` and is left alone, destructured or not |
3631
- | `no-floating-assertion` | `error` | — | `expect()` in a `.then()` nobody awaits → `expect(await promise)` |
3632
- | `no-bare-called-with` | `error` | — | `spy.m.calledWith(1);` as a statement — a stub nobody continued, asserting nothing; chai's `expect(fn).to.have.been.calledWith()` exempt |
3633
- | `no-overridden-provider` | `error` | suggest | two providers for one token in one array, or one a `TestBed.overrideProvider` in the same hook replaces → the earlier one never runs; the exact duplicate can be deleted |
3634
- | `no-inject-before-override` | `error` | — | `TestBed.inject()` / `injectSpy()` / `renderShallow()` in a hook, in a suite that still calls `override*` |
3635
- | `no-private-member-access` | `error` | — | `instance['privateMember']`, `(instance as any).privateMember` (and `as unknown as`, and a decoy type), `vi.spyOn(Object.getPrototypeOf(x), 'm')` → drive the member through the public API. **Type-aware**: silent without `parserOptions.project`, and silent on an index signature |
3636
- | `no-dead-schemas` | `error` | — | `schemas` on a testing module with no `declarations` — the schema applies to nothing; the file decides, so a `declarations` in another `configureTestingModule` call silences it |
3637
- | `no-import-time-spread` | `error` | suggest | `export const x = [...Imported]` at module scope → a `TypeError`, or a silently empty object, while the bundle loads |
3638
- | `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 |
3639
- | `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 |
3640
- | `prefer-set-inputs` | `warn` | suggest | a run of `fixture.componentRef.setInput('title', v)` on one fixture → `await setInputs(fixture, { title: v })` — the name is resolved against the compiled definition before the first write (an undeclared one is an `NG0303` and no change) and the value is typed. The run collapses into one call and a `detectChanges()` under it goes; offered, not applied, because `stable()` ticks and a zone.js suite answers that with `NG0101` |
3641
- | `prefer-observer-stub` | `error` | — | a hand-rolled observer global → `stubIntersectionObserver()` / `stubResizeObserver()` / `stubMutationObserver()`; the manual save-and-restore goes too, `restoreMockedProps()` runs the undo |
3642
- | `no-hand-assigned-global` | `error` | — | a double assigned to a global (`global.fetch = vi.fn()`, `window.matchMedia = vi.fn()`, `window.localStorage = { getItem: vi.fn() }`) with no restore in `afterEach` / `afterAll` / `onTestFinished` → `mockValueProp(globalThis, name, value)` or `vi.stubGlobal` + `unstubGlobals`; `blockNetwork()` for network globals, `stubWebStorage()` for the storages; the three observers stay with `prefer-observer-stub` |
3643
- | `prefer-provide-activated-route` | `error` | — | a hand-built `ActivatedRoute` — any `useValue` / `useClass` / `useFactory` / `useExisting`, and `provideAutoSpy(ActivatedRoute)` too → `provideActivatedRoute({ … })`; the double knows either the streams or the snapshot, never both, and `injectActivatedRoute().setParams(…)` moves them together mid-test |
3644
- | `no-passthrough-console-spy` | `error` | suggest | `vi.spyOn(console, m)` nothing gives an implementation — it calls through and prints → `installConsoleSpies()` + `consoleXSpy`, or `.mockImplementation(() => undefined)` |
3645
- | `no-console-in-spec` | `error` | — | a spec calling `console.x(…)` itself, or `console.x = …`, which nothing restores → absorb the code's output through `vitest-auto-spy/console` |
3646
- | `no-import-time-console-spies` | `error` | — | an import of `vitest-auto-spy/console` in a file that never calls `installConsoleSpies()` — the import installs once per worker and silences every later file → `installConsoleSpies()` in `beforeEach`, `restoreConsole()` in `afterEach` |
3647
- | `no-mistyped-use-value` | `error` | — | `{ provide: TOKEN, useValue }` whose value is not assignable to the primitive `T` of `InjectionToken<T>` (string, number, boolean, bigint, enum, their literals, `null`, `undefined`) — `useValue` is `any`, so `{}` for a `boolean` token compiles and is truthy. **Type-aware**: silent without `parserOptions.project`; object-typed tokens are `no-unknown-use-value-key`'s |
3648
- | `no-unknown-use-value-key` | `error` | — | a key of an object `useValue` literal the provided type does not have — `T` of `InjectionToken<T>`, or the instance type of a `provide:` class; `{ provide: ActivatedRoute, useValue: { queryParams$: … } }` compiles, since `useValue` is `any`. Keys only, never assignability (a partial fixture is fine). **Type-aware**; silent on `any` / `unknown` / `object`, an index signature, a spread's keys, `multi: true` |
3649
- | `no-instance-lifecycle-spy` | `warn` | — | `vi.spyOn(instance, 'ngOnInit')` (and `ngOnDestroy`, `ngDoCheck`, `ngAfterContent*`, `ngAfterView*`) — a view calls the hook read off the prototype, so the instance spy is never called and its stub never runs → `vi.spyOn(Cls.prototype, …)` before `createComponent`, or assert the effect. `ngOnChanges` is exempt: Angular calls it through the instance |
3650
- | `no-ts-expect-error-on-double` | `error` | — | `@ts-expect-error` / `@ts-ignore` above `nextWith`, `resolveWith`, `mockReturnValue`, `returnValue`, `calledWith(…)` and the other helpers that check a stub against the method's signature → an overloaded method takes `Spy<X, { overload: { m: 'first' } }>`; otherwise the fixture is the wrong shape, checked against `ReturnType<X['m']>`. A reason after the directive does not silence it; a deliberate out-of-type value keeps it under `eslint-disable-next-line … -- <why>` |
3651
- | `no-constant-expect` | `error` | — | `expect(true).toBe(true)`, `expect({ … }).toBeDefined()` — a value the spec spelled out, under a matcher whose answer it already fixes (`toBe` / `toEqual` / `toStrictEqual` against a literal; `toBeTruthy`, `toBeDefined`, `toBeNull`… for any literal) → assert on what the code produced, or `expect.fail(…)` for an unreachable branch |
3652
- | `no-redundant-smoke-test` | `error` | suggest | `it('should create', () => expect(pipe).toBeTruthy())` — every statement of the body an `expect(x)` under `toBeTruthy` / `toBeDefined` / `toBeInstanceOf` (or their negated twins), weighed against the tests that run the same setup: the rest of the block, and everything the blocks nested in it declare → delete it; the suggestion removes the test and the blank line above it. Silent where that test is the block's only running one, and a skipped sibling does not count as proof |
3653
- | `no-compile-components` | `error` | suggest | `compileComponents()` under a builder that inlines `templateUrl` / `styleUrls` — a promise already settled → delete it, and the `async` of a hook that awaits nothing else. **Silent until** `['error', { builder: 'inline-resources' }]`: under a JIT setup that loads resources at run time the call is load-bearing. **Keep the call for a component whose template holds a `@defer` block** — that ships async class metadata, which `TestBed` resolves in this very call whatever the builder did, and dropping it fails the test with `has unresolved metadata`; the rule cannot see another file's template, so such a call keeps its own `// eslint-disable-next-line vitest-auto-spy/no-compile-components -- @defer: async class metadata` |
3654
- | `no-sync-testbed-await` | `error` | suggest | `await` on a TestBed call that answers the TestBed or a fixture — `configureTestingModule`, `override*`, `resetTestingModule`, `createComponent`, `getLastFixture`, through `TestBed`, `getTestBed()`, a chain of those, or a name holding one → drop the `await`, and the `async` of a hook that then awaits nothing else; the suggestion does both. Reads **no types**, so it reports without `parserOptions.project`, where `@typescript-eslint/await-thenable` cannot. `inject` and `runInInjectionContext` are never reported: each answers whatever the token or the callback holds, which can be a promise |
3655
- | `jasmine-namespace-without-entry` | `error` | — | `.and` / `.calls` / `.withArgs` on a library spy in a file that installs the compat layer nowhere — option: `{ setupModules: […] }` |
3656
- | `no-jasmine-globals` | `error` | — | `jasmine.*`, bare `spyOn(` / `spyOnProperty(` / `spyOnAllFunctions(` / `fail(` / `pending(`, `.withContext(` |
3657
- | `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()` — a no-op here, so the spec silently asserts on post-mutation state |
3658
- | `prefer-native-spy-api` | `error` | `--fix` / suggest | `.and` / `.calls` where the spy's own API says the same thing — turn it on for the last mile off the jasmine shim |
3659
-
3660
- Thirty-nine rules, **every one an `error` since 4.0.0 except `prefer-render-shallow`,
3661
- `no-stub-class-double`, `no-structural-double`, `no-instance-lifecycle-spy` and `prefer-set-inputs`**; four fix on their own, thirteen offer suggestions. Thirty-six are syntactic; `no-private-member-access`, `no-mistyped-use-value` and
3677
+ | Rule | Level | Fix | Flags |
3678
+ | --------------------------------- | ------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
3679
+ | `no-expect-in-subscribe` | `error` | suggest | `expect()` inside `subscribe()` → `expectEmission` / `firstValueFrom` |
3680
+ | `no-object-define-property` | `error` | suggest | `Object.defineProperty` in a spec → `mockReadonlyProp` / `mockValueProp` |
3681
+ | `prefer-provide-auto-spy` | `error` | fix | a hand-rolled `useValue`, `useFactory`, `useClass` or `useExisting` (also `useValue: new StubMock()`), in a provider **or** a `TestBed.overrideProvider(X, { … })` → `provideAutoSpy(Class)` / `provideAutoSpyForToken(TOKEN)`. A name in the slot is followed one step, through an initialiser or a single `beforeEach` assignment. `{ provide: X, useValue: createSpyFromClass(X, config) }` — the factory's own body, written out — is reported and **rewritten** to `provideAutoSpy(X, config)`; a double read from a _different_ class (`{ provide: LocalStorage, useValue: createSpyFromClass(BaseLocalStorage) }`) is not, since the abstract token has no prototype for `provideAutoSpy` to read. On the `ActivatedRoute` token — in a provider or in an `overrideProvider` — the advice is `provideActivatedRoute()` from `/angular-router` instead, with a message of its own, so the two route rules agree |
3682
+ | `prefer-create-spy-from-class` | `error` | — | an object literal of 2+ `vi.fn()`s → `createSpyFromClass` (a factory's own seed is exempt) |
3683
+ | `no-stub-class-double` | `warn` | — | a class whose fields are `vi.fn()`s → `createSpyFromClass` / `provideAutoSpy` and delete the stub; a decorated, `implements`-ing, `extends`-ing or unnamed class is exempt — option: `{ minRunnerFns }` |
3684
+ | `no-structural-double` | `warn` | — | an object of `vi.fn()`s bound to a name declared `{ load: Mock }` → `createAutoMock<T>()`; a bare `let fn: Mock` is a callback and never reported — option: `{ minRunnerFns }` |
3685
+ | `prefer-inject-spy` | `error` | suggest | `vi.spyOn(TestBed.inject(X), 'm')`, inline or via a `const` → `injectSpy(X).m`; `ApplicationRef`, `DestroyRef`, `EnvironmentInjector`, `HttpClient` and `Injector` keep their real instance — option: `{ ignoreTokens }` |
3686
+ | `no-shared-module-level-mock` | `error` | — | an **exported** value holding `vi.fn()`s → export a factory instead |
3687
+ | `no-mocked-for-spy` | `error` | `--fix` / suggest | `Mocked<T>` in any type position → `Spy<T>`, import and all — a suggestion where the assigned value is not from a factory of this library |
3688
+ | `prefer-as-spy` | `error` | `--fix` | `TestBed.inject(X) as Spy<X>` → `asSpy<X>(TestBed.inject(X))`, import and all |
3689
+ | `no-done-callback` | `error` | — | a first parameter of a test or hook that is **called**, **passed on** as an argument, or never used at all → `async` + an awaited assertion, and `done.fail(…)` at the call site. A parameter read only as `ctx.skip()` / `ctx.task` / `ctx.expect` is Vitest's `TestContext` and is left alone, destructured or not |
3690
+ | `no-floating-assertion` | `error` | — | `expect()` in a `.then()` nobody awaits → `expect(await promise)` |
3691
+ | `no-bare-called-with` | `error` | — | `spy.m.calledWith(1);` as a statement — a stub nobody continued, asserting nothing; chai's `expect(fn).to.have.been.calledWith()` exempt |
3692
+ | `no-overridden-provider` | `error` | suggest | two providers for one token in one array, or one a `TestBed.overrideProvider` in the same hook replaces → the earlier one never runs; the exact duplicate can be deleted |
3693
+ | `no-inject-before-override` | `error` | — | `TestBed.inject()` / `injectSpy()` / `renderShallow()` in a hook, in a suite that still calls `override*` |
3694
+ | `no-private-member-access` | `error` | — | `instance['privateMember']`, `(instance as any).privateMember` (and `as unknown as`, and a decoy type), `vi.spyOn(Object.getPrototypeOf(x), 'm')` → drive the member through the public API. **Type-aware**: silent without `parserOptions.project`, and silent on an index signature |
3695
+ | `no-reflect-member-access` | `error` | suggest | `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` |
3696
+ | `no-dead-schemas` | `error` | — | `schemas` on a testing module with no `declarations` — the schema applies to nothing; the file decides, so a `declarations` in another `configureTestingModule` call silences it |
3697
+ | `no-import-time-spread` | `error` | suggest | `export const x = [...Imported]` at module scope → a `TypeError`, or a silently empty object, while the bundle loads |
3698
+ | `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 |
3699
+ | `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 |
3700
+ | `prefer-set-inputs` | `warn` | suggest | a run of `fixture.componentRef.setInput('title', v)` on one fixture → `await setInputs(fixture, { title: v })` — the name is resolved against the compiled definition before the first write (an undeclared one is an `NG0303` and no change) and the value is typed. The run collapses into one call and a `detectChanges()` under it goes; offered, not applied, because `stable()` ticks and a zone.js suite answers that with `NG0101` |
3701
+ | `prefer-observer-stub` | `error` | — | a hand-rolled observer global → `stubIntersectionObserver()` / `stubResizeObserver()` / `stubMutationObserver()`; the manual save-and-restore goes too, `restoreMockedProps()` runs the undo |
3702
+ | `no-hand-assigned-global` | `error` | — | a double assigned to a global (`global.fetch = vi.fn()`, `window.matchMedia = vi.fn()`, `window.localStorage = { getItem: vi.fn() }`) with no restore in `afterEach` / `afterAll` / `onTestFinished` → `mockValueProp(globalThis, name, value)` or `vi.stubGlobal` + `unstubGlobals`; `blockNetwork()` for network globals, `stubWebStorage()` for the storages; the three observers stay with `prefer-observer-stub` |
3703
+ | `prefer-stub-response` | `error` | — | an object literal cast to `Response` (`as Response`, `as unknown as Response`, `<Response>{ … }`) or `createMock<Response>(…)` / `createAutoMock<Response>(…)` → `stubResponse({ body })` from `/setup`; the literal answers `undefined` for every member it does not list (`status`, `headers`, `text()`) and the cast is what makes that compile. `Response` must resolve to the **global**, so an Express handler's or a generated client's `Response` is never reported |
3704
+ | `prefer-provide-activated-route` | `error` | — | a hand-built `ActivatedRoute` — any `useValue` / `useClass` / `useFactory` / `useExisting`, and `provideAutoSpy(ActivatedRoute)` too → `provideActivatedRoute({ … })`; the double knows either the streams or the snapshot, never both, and `injectActivatedRoute().setParams(…)` moves them together mid-test |
3705
+ | `no-passthrough-console-spy` | `error` | suggest | `vi.spyOn(console, m)` nothing gives an implementation — it calls through and prints → `installConsoleSpies()` + `consoleXSpy`, or `.mockImplementation(() => undefined)` |
3706
+ | `no-console-in-spec` | `error` | — | a spec calling `console.x(…)` itself, or `console.x = …`, which nothing restores → absorb the code's output through `vitest-auto-spy/console` |
3707
+ | `no-import-time-console-spies` | `error` | — | an import of `vitest-auto-spy/console` in a file that never calls `installConsoleSpies()` — the import installs once per worker and silences every later file → `installConsoleSpies()` in `beforeEach`, `restoreConsole()` in `afterEach` |
3708
+ | `no-mistyped-use-value` | `error` | — | `{ provide: TOKEN, useValue }` whose value is not assignable to the primitive `T` of `InjectionToken<T>` (string, number, boolean, bigint, enum, their literals, `null`, `undefined`) — `useValue` is `any`, so `{}` for a `boolean` token compiles and is truthy. **Type-aware**: silent without `parserOptions.project`; object-typed tokens are `no-unknown-use-value-key`'s |
3709
+ | `no-unknown-use-value-key` | `error` | — | a key of an object `useValue` literal the provided type does not have — `T` of `InjectionToken<T>`, or the instance type of a `provide:` class; `{ provide: ActivatedRoute, useValue: { queryParams$: … } }` compiles, since `useValue` is `any`. Keys only, never assignability (a partial fixture is fine). **Type-aware**; silent on `any` / `unknown` / `object`, an index signature, a spread's keys, `multi: true` |
3710
+ | `no-instance-lifecycle-spy` | `warn` | — | `vi.spyOn(instance, 'ngOnInit')` (and `ngOnDestroy`, `ngDoCheck`, `ngAfterContent*`, `ngAfterView*`) — a view calls the hook read off the prototype, so the instance spy is never called and its stub never runs → `vi.spyOn(Cls.prototype, …)` before `createComponent`, or assert the effect. `ngOnChanges` is exempt: Angular calls it through the instance |
3711
+ | `no-ts-expect-error-on-double` | `error` | — | `@ts-expect-error` / `@ts-ignore` above `nextWith`, `resolveWith`, `mockReturnValue`, `returnValue`, `calledWith(…)` and the other helpers that check a stub against the method's signature → an overloaded method takes `Spy<X, { overload: { m: 'first' } }>`; otherwise the fixture is the wrong shape, checked against `ReturnType<X['m']>`. A reason after the directive does not silence it; a deliberate out-of-type value keeps it under `eslint-disable-next-line … -- <why>` |
3712
+ | `no-constant-expect` | `error` | — | `expect(true).toBe(true)`, `expect({ … }).toBeDefined()` — a value the spec spelled out, under a matcher whose answer it already fixes (`toBe` / `toEqual` / `toStrictEqual` against a literal; `toBeTruthy`, `toBeDefined`, `toBeNull`… for any literal) → assert on what the code produced, or `expect.fail(…)` for an unreachable branch |
3713
+ | `no-redundant-smoke-test` | `error` | suggest | `it('should create', () => expect(pipe).toBeTruthy())` — every statement of the body an `expect(x)` under `toBeTruthy` / `toBeDefined` / `toBeInstanceOf` (or their negated twins), weighed against the tests that run the same setup: the rest of the block, and everything the blocks nested in it declare → delete it; the suggestion removes the test and the blank line above it. Silent where that test is the block's only running one, and a skipped sibling does not count as proof |
3714
+ | `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 |
3715
+ | `no-vacuous-absence-assertion` | `error` | — | a test **all** of whose assertions are satisfied by the stream never emitting. The carrier is a `const` / `let` declared in the test whose every write outside the declaration sits in a `subscribe` callback of that test (the assignment and the `push` forms both count), or a `vi.fn()` the test hands to `subscribe` and never calls itself. Silence satisfies an equality matcher repeating the initialiser's source text, `toBeUndefined` / `not.toBeDefined` on a `let` holding `undefined`, `toBeNull` on one holding `null`, `toBeFalsy` / `not.toBeTruthy` on any falsy literal, `toHaveLength(0)` on `[]` or `''`, and `not.toHaveBeenCalled` / `not.toHaveBeenCalledWith` / `toHaveBeenCalledTimes(0)` on any subject — read literally, so `toBeNull()` on a `let` with no initialiser is **not** reported. One `expect()` a silent source could fail silences the rule, which is what exempts the "nothing yet, trigger, now the value" shape; a test asserting through a local helper is skipped entirely → `await expectNoEmission(source$)`, or `expect(await expectEmission(source$))` |
3716
+ | `prefer-settle-dynamic-import` | `error` | suggest | `await import('./thing')` and `import('./thing').then(…)` inside an `it` / `test` body or a `beforeEach` / `beforeAll` / `afterEach` / `afterAll` hook (the `.only`, `.skip` and `.each` spellings included) → `await settleDynamicImport(() => import('./thing'))`, which is the same load plus `flushEventLoop(1)` and returns the namespace, so the destructured form keeps reading the same way. The bare `await` waits for the **module** and not for the continuation of the code that was loading it, so the assertion runs a turn early and the test is green only while that continuation is short. Syntax only. Silent wherever a function of its own sits between the runner's callback and the import — a `vi.mock` factory, a lazy route's `loadComponent` / `loadChildren`, a callback handed to production code, `settleDynamicImport`'s own `() => import(…)` — and, by the same reading, silent on a spec-local `const load = async () => { await import('…') }`, which is written identically whether the spec calls it or the code under test does |
3717
+ | `prefer-create-mock` | `warn` | suggest | an object literal under `as SomeType` — `{ id: '1', isOffline: false } as Device`, `<Device>{ … }` too → `createMock<Device>({ … })`. A cast asks whether the two types overlap rather than whether the value is one of them, so it passes a key the type does not declare (the excess-property check is skipped) **and** a required field the fixture never sets; both type gates stay silent, and the same object is then spread into the expected payload of a call assertion. Where the literal already sits in a typed slot the first repair is to delete the cast. Silent on `as const`, `as unknown` / `as any` and the double cast built from them, on a cast of anything but a literal, and on a literal inside one of this library's own factories. `warn`, because accepting the suggestion makes a drifted fixture red on the next type-check — the finding, and a migration taken file by file |
3718
+ | `no-mock-cast` | `error` | suggest | a cast to Vitest's `Mock` / `MockInstance` over a **member access** — `TestBed.inject(S).m as Mock` → `injectSpy(S).m`, which the suggestion writes whenever the token is in view (directly, or through a `const` the file settled with a `TestBed.inject`). `Mock` with no parameters is `Mock<any>`: `mockReturnValue` takes anything and `toHaveBeenCalledWith` stops comparing arguments. `(spy.m.mockReturnValue as Mock)(…)` gets its own message — the cast is on the member that installs the answer, so neither the value nor the method's return type is checked. The name must resolve to an import from `vitest` / `@rstest/core` / `bun:test` / `jest`, or to nothing at all, so a domain `Mock` is left alone |
3719
+ | `no-redundant-mock-reset` | `error` | `--fix` / suggest | a mock reset in a `beforeEach` / `afterEach` / `beforeAll` / `afterAll` — `vi.clearAllMocks()` / `resetAllMocks()` / `restoreAllMocks()`, `x.mockClear()` / `mockReset()` / `mockRestore()` — the runner already performs between tests → delete it, and the hook with it where that is all it held. **Silent** until the flags are known: options `{ clearMocks, restoreMocks, mockReset }` first, then a `vitest.config.*` / `vite.config.*` searched upwards from the file and read as text. Flag matches call, never family — `restoreMocks` walks only the spies `vi.spyOn` installed. `--fix` only as the first statement of the file's only `beforeEach`; a suggestion otherwise; never inside a test body |
3720
+ | `no-unasserted-argument` | `warn` | — | a bare `expect(spy).toHaveBeenCalled()` on evidence from the file: the same subject asserted with `toHaveBeenCalledWith(…)` in another test, or a title containing `with` over a body whose every assertion is a bare call → `toHaveBeenCalledWith(…)` / `mustBeCalledWith(…)`. `not.toHaveBeenCalled()` and the counting matchers are never reported |
3721
+ | `no-compile-components` | `error` | suggest | `compileComponents()` under a builder that inlines `templateUrl` / `styleUrls` — a promise already settled → delete it, and the `async` of a hook that awaits nothing else. **Silent until** `['error', { builder: 'inline-resources' }]`: under a JIT setup that loads resources at run time the call is load-bearing. **Keep the call for a component whose template holds a `@defer` block** — that ships async class metadata, which `TestBed` resolves in this very call whatever the builder did, and dropping it fails the test with `has unresolved metadata`; the rule cannot see another file's template, so such a call keeps its own `// eslint-disable-next-line vitest-auto-spy/no-compile-components -- @defer: async class metadata` |
3722
+ | `no-sync-testbed-await` | `error` | suggest | `await` on a TestBed call that answers the TestBed or a fixture — `configureTestingModule`, `override*`, `resetTestingModule`, `createComponent`, `getLastFixture`, through `TestBed`, `getTestBed()`, a chain of those, or a name holding one → drop the `await`, and the `async` of a hook that then awaits nothing else; the suggestion does both. Reads **no types**, so it reports without `parserOptions.project`, where `@typescript-eslint/await-thenable` cannot. `inject` and `runInInjectionContext` are never reported: each answers whatever the token or the callback holds, which can be a promise |
3723
+ | `jasmine-namespace-without-entry` | `error` | — | `.and` / `.calls` / `.withArgs` on a library spy in a file that installs the compat layer nowhere — option: `{ setupModules: […] }` |
3724
+ | `no-jasmine-globals` | `error` | — | `jasmine.*`, bare `spyOn(` / `spyOnProperty(` / `spyOnAllFunctions(` / `fail(` / `pending(`, `.withContext(` |
3725
+ | `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()` — a no-op here, so the spec silently asserts on post-mutation state |
3726
+ | `prefer-native-spy-api` | `error` | `--fix` / suggest | `.and` / `.calls` where the spy's own API says the same thing — turn it on for the last mile off the jasmine shim |
3727
+
3728
+ Forty-eight rules, **every one an `error` since 4.0.0 except `prefer-render-shallow`,
3729
+ `no-stub-class-double`, `no-structural-double`, `prefer-create-mock`, `no-instance-lifecycle-spy`,
3730
+ `prefer-set-inputs` and `no-unasserted-argument`**; five fix on their own, eighteen offer suggestions. Forty-five are syntactic; `no-private-member-access`, `no-mistyped-use-value` and
3662
3731
  `no-unknown-use-value-key` read types, and all three report nothing at all without `parserOptions.project` / `projectService`
3663
3732
  rather than guessing. `no-compile-components` waits the same way for a fact no file holds — which
3664
3733
  builder the project has — and reports nothing until `{ builder: 'inline-resources' }` states it. The config used to be a graded mix of `error` / `warn` / `off`, which decided for the
3665
3734
  consumer how much each finding mattered — a `warn` nothing reads is `off` with extra output. The
3666
- four `warn`s left are not judgements about how much a finding matters. `prefer-render-shallow` is
3735
+ seven `warn`s left are not judgements about how much a finding matters. `prefer-render-shallow` is
3667
3736
  about the _kind_ of finding: every other rule names something wrong or dead, while this one names a
3668
3737
  file that could render more cheaply, and `renderShallow` is a migration a suite either takes or does
3669
3738
  not. At `error` the plugin would gate that migration — 491 findings across 398 of one consumer's
@@ -3678,7 +3747,15 @@ follow a name into a `useValue` — 112 of those doubles are handed to Angular D
3678
3747
  a `provide:` settles it. That rule reports **154 times across 87 files** on the same suite, all at
3679
3748
  `error`. `no-instance-lifecycle-spy` is on the evidence too: Angular never calls an instance spy on a
3680
3749
  hook it read off the prototype, but a spec that calls `component.ngOnInit()` itself does, and so does
3681
- the injector for a service's `ngOnDestroy`. Set any of the four `warn`s to `'error'` once the batch is done. Three of
3750
+ the injector for a service's `ngOnDestroy`. `prefer-create-mock` (5.23.0) is graded on what its
3751
+ repair _costs_: the evidence is exact — the literal and the type it claims are written on the same
3752
+ line — but accepting the suggestion hands that literal to the compiler, and on a 2 032-file consumer
3753
+ that is 1 200 findings in 327 files going red on one day. `no-unasserted-argument` (5.23.0) is graded on what its
3754
+ repair _needs_ rather than on its evidence: both of its readings are facts out of the file — a
3755
+ subject some other test in the same file pins with `toHaveBeenCalledWith`, a title saying `with` over
3756
+ a body whose every assertion is a bare call — but the repair is the argument list the test should
3757
+ have named, which is the one thing the rule cannot supply. Every `error` here names an edit or names
3758
+ a helper; this one names a question for the author. Set any of the seven `warn`s to `'error'` once the batch is done. Three of
3682
3759
  them can report on a _correct_ project, and only one has an option:
3683
3760
  `jasmine-namespace-without-entry` takes `['error', { setupModules: ['./test-setup'] }]`, naming the
3684
3761
  file where `enableJasmineCompat()` is called; `prefer-native-spy-api` goes `'off'` for as long as a
@@ -3969,7 +4046,7 @@ packages, which a subpath export can never be.
3969
4046
  | a spec runs down the "browser" (or any flag-on) branch whatever its `useValue` says | `{ provide: IS_BROWSER, useValue: {} }` for an `InjectionToken<boolean>` — `useValue` is `any`, and an object is truthy | a value of the token's type (`useValue: false`) — `no-mistyped-use-value` (§16), with type information |
3970
4047
  | `no request matched …` (with the requests that were made listed) | the URL, verb or predicate does not describe anything the code under test sent, or nothing subscribed | read the list in the message — it is the requests that were actually made; then fix the URL, add `{ method }`, or subscribe (§13) |
3971
4048
  | `this TestBed has no HttpTestingController` | `expectRequest` / `expectNoRequest` on a module that never got the testing backend | `TestBed.configureTestingModule({ providers: [...provideHttpTesting()] })` from `vitest-auto-spy/angular-http` (§13) |
3972
- | `the test ended with N unanswered request(s)` | `provideHttpTesting({ verifyOnTeardown })` found a request the spec never flushed | answer it (`await expectRequest(url).flush(body)`), assert its absence (`expectNoRequest(url)`), or `{ verifyOnTeardown: false }` (§13) |
4049
+ | `the test ended with N unanswered request(s)` | `provideHttpTesting({ verifyOnTeardown })` found a request the spec never flushed | answer it (`await expectRequest(url).flush(body)`), assert its absence (`expectNoRequest(url)`), forgive an unsubscribed one with `{ verifyOnTeardown: { ignoreCancelled: true } }`, or `{ verifyOnTeardown: false }` (§13) |
3973
4050
  | `injectActivatedRoute(): nothing provides ActivatedRoute here` | no `ActivatedRoute` provider in the injector it read | `provideActivatedRoute({ … })` from `vitest-auto-spy/angular-router` in `providers`; `injectActivatedRoute(fixture.debugElement.injector)` for a component-level one (§13) |
3974
4051
  | `the ActivatedRoute here is … not one provideActivatedRoute() built` | a later provider of `ActivatedRoute` won — `provideRouter()`, `RouterModule`, a `useValue`, `provideAutoSpy(ActivatedRoute)` | list `provideActivatedRoute()` last, or drop the other provider (§13) |
3975
4052
  | `provideActivatedRoute: the installed @angular/router does not wire ActivatedRoute …` | a router major builds `ActivatedRoute` with different internal constructors | report it with the `@angular/router` version; provide the route by hand until then (§13) |
@@ -4041,6 +4118,10 @@ diagnostic and is free to change: print it, never assert on it.
4041
4118
  | `Object.defineProperty(document, 'cookie', { value })` | `mockValueProp(document, 'cookie', value)` |
4042
4119
  | `vi.spyOn(globalThis, 'Date')` | `mockSystemTime('2025-04-30T00:00:00Z')` |
4043
4120
  | ten `await Promise.resolve()` for a dynamic `import()` | `await settleDynamicImport(() => import('…'))` |
4121
+ | a bare `await import('…')` in a test body | `await settleDynamicImport(() => import('…'))` — `prefer-settle-dynamic-import` reports it |
4122
+ | a capture whose only assertions are `toEqual([])` / `not.toHaveBeenCalled()` | `await expectNoEmission(source$)` — `no-vacuous-absence-assertion` reports it |
4123
+ | `{ id: '1', isOffline: false } as Device` (a cast passes an excess key) | `createMock<Device>({ id: '1' })` — `prefer-create-mock` reports it |
4124
+ | `TestBed.inject(S).m as Mock` (`Mock` is `Mock<any>`) | `injectSpy(S).m` — `no-mock-cast` reports it |
4044
4125
  | `await fixture.whenRenderingDone()` | `await stable(fixture)` |
4045
4126
  | an exported `const` provider with `vi.fn()` inside | an exported **factory** returning it (§10) |
4046
4127
  | `.overrideProvider(X, provideAutoSpy(X))` (works, but says the wrong thing) | `.overrideProvider(X, overrideAutoSpy(X))` |