vitest-auto-spy 5.21.1 → 5.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +78 -18
- package/README.md +51 -36
- package/dist/angular-diagnostics.js +8 -8
- package/dist/angular-doubles.js +7 -7
- package/dist/angular-http.d.ts +14 -2
- package/dist/angular-http.js +17 -9
- package/dist/angular-router.d.ts +93 -6
- package/dist/angular-router.js +112 -15
- package/dist/bun-angular.d.ts +1 -1
- package/dist/bun-angular.js +16 -16
- package/dist/bun.d.ts +103 -1
- package/dist/bun.js +9 -9
- package/dist/{chunk-NEFATXWT.js → chunk-45W5LBPY.js} +5 -5
- package/dist/{chunk-R3DF5ZEO.js → chunk-BBPYNKGB.js} +1 -1
- package/dist/{chunk-ITOFGQTX.js → chunk-CPLUHRHD.js} +1 -1
- package/dist/{chunk-I6JZIWIZ.js → chunk-DQ3SPSCH.js} +3 -3
- package/dist/{chunk-OEE74SMY.js → chunk-E5M7DBWB.js} +31 -7
- package/dist/{chunk-RPCKHTDN.js → chunk-JBV35SU3.js} +1 -1
- package/dist/{chunk-4SJY7ZHM.js → chunk-LZVIH54N.js} +2 -2
- package/dist/{chunk-S5ARWUD4.js → chunk-MBSPAUUD.js} +3 -3
- package/dist/{chunk-SA2QIER3.js → chunk-MG7LKE4R.js} +1 -1
- package/dist/{chunk-2KBFEQOV.js → chunk-MUZZBRUP.js} +2 -2
- package/dist/{chunk-JJVTDL5R.js → chunk-QO4BPR3V.js} +3 -0
- package/dist/{chunk-DPTWTLIG.js → chunk-U5NJJ4ZN.js} +1 -1
- package/dist/{chunk-BN2QV45R.js → chunk-V6TIK7XU.js} +2 -2
- package/dist/{chunk-NS3Y6AQB.js → chunk-WYYKUPXW.js} +1 -1
- package/dist/{chunk-QT5JIDOK.js → chunk-YEQQIKQR.js} +1 -1
- package/dist/{chunk-TQAYWU5J.js → chunk-ZCWAFGHX.js} +2 -2
- package/dist/cli.js +10 -1
- package/dist/console.js +7 -7
- package/dist/eslint-plugin.cjs +50 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +25 -1
- package/dist/jasmine-compat.js +2 -2
- package/dist/jasmine.js +10 -10
- package/dist/nestjs.js +10 -10
- package/dist/node.cjs +25 -0
- package/dist/node.d.cts +1 -0
- package/dist/node.d.ts +1 -1
- package/dist/node.js +25 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +25 -1
- package/dist/rstest.d.ts +1 -1
- package/dist/rstest.js +9 -9
- package/dist/rxjs.js +2 -2
- package/dist/setup.d.ts +12 -3
- package/dist/setup.js +8 -2
- package/dist/signal-forms.js +1 -1
- package/dist/svelte.d.ts +1 -1
- package/dist/svelte.js +25 -1
- package/dist/vue.d.ts +1 -1
- package/dist/vue.js +25 -1
- package/package.json +1 -1
- package/skills/vitest-auto-spy/SKILL.md +6 -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()`
|
|
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
|
-
-
|
|
2777
|
-
|
|
2778
|
-
|
|
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
|
|
@@ -3640,6 +3699,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
|
|
|
3640
3699
|
| `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
3700
|
| `prefer-observer-stub` | `error` | — | a hand-rolled observer global → `stubIntersectionObserver()` / `stubResizeObserver()` / `stubMutationObserver()`; the manual save-and-restore goes too, `restoreMockedProps()` runs the undo |
|
|
3642
3701
|
| `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` |
|
|
3702
|
+
| `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 |
|
|
3643
3703
|
| `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
3704
|
| `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
3705
|
| `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` |
|
|
@@ -3657,7 +3717,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
|
|
|
3657
3717
|
| `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()` — a no-op here, so the spec silently asserts on post-mutation state |
|
|
3658
3718
|
| `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
3719
|
|
|
3660
|
-
|
|
3720
|
+
Forty rules, **every one an `error` since 4.0.0 except `prefer-render-shallow`,
|
|
3661
3721
|
`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
|
|
3662
3722
|
`no-unknown-use-value-key` read types, and all three report nothing at all without `parserOptions.project` / `projectService`
|
|
3663
3723
|
rather than guessing. `no-compile-components` waits the same way for a fact no file holds — which
|
|
@@ -3969,7 +4029,7 @@ packages, which a subpath export can never be.
|
|
|
3969
4029
|
| 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
4030
|
| `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
4031
|
| `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)
|
|
4032
|
+
| `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
4033
|
| `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
4034
|
| `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
4035
|
| `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) |
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ faster at suite scale ([benchmarks](#benchmarks)) — and for
|
|
|
23
23
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
24
24
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
25
25
|
[](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
|
|
26
|
-
[](#install)
|
|
27
27
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
28
28
|
[](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
|
|
29
29
|
[](./LICENSE)
|
|
@@ -80,7 +80,7 @@ faster at suite scale ([benchmarks](#benchmarks)) — and for
|
|
|
80
80
|
- 🧩 Module mocks that prove they applied — `assertMocked`, `moduleNamespace`, for a `vi.mock()` a bundler quietly ignored; `moduleNamespace(await importOriginal(), { passthrough: true })` is Vitest's `{ spy: true }` with `calledWith` on top, on any runner
|
|
81
81
|
- 🧾 Fixtures without casts — deep-partial `createMock`, `createFixture` / `createFixtureFactory`, `narrow()`, `withOverrides()`, `asInstances()`, `captureArg()`
|
|
82
82
|
- 🚚 A migration you can verify — `vitest-auto-spy/diagnostics`: `compareTestRuns` on the two JSON reports, `summarizeTestRun` / `formatTestRunComparison` to read the answer, `diffByField` for the assertion the reporter collapses, `explainSpy` for a double that answered something you did not configure
|
|
83
|
-
- 📏 Lint rules and one-line test-run hygiene —
|
|
83
|
+
- 📏 Lint rules and one-line test-run hygiene — forty rules in `vitest-auto-spy/eslint-plugin` (four `--fix`, thirteen suggestions, four of them for a suite mid-migration off jasmine), `setupAutoSpy()` — with `preset: 'strict'` for every guard at its strictest grade
|
|
84
84
|
- 🩺 [Editor diagnostics](#editor-diagnostics--webstorm--vs-code) — the same anti-patterns underlined while you type: native ESLint inspections in **WebStorm** and the other JetBrains IDEs, the ESLint extension in **VS Code**, no extra plugin either way
|
|
85
85
|
- 🔎 [`npx vitest-auto-spy doctor`](#the-cli--doctor-perf-codemod-and-init) — suite-level defects **that never fail a run**: a `tsconfig` `include` matching no file, a production module importing a spec, a `@jest-environment` pragma the runner never reads, config left behind for a runner that is gone. Read-only, no config, exits 1 in CI
|
|
86
86
|
- ⏱️ [`npx vitest-auto-spy perf`](#perf--where-the-cpu-time-actually-goes) — where a suite's CPU time actually goes, phase by phase, and which spec files to act on: the ones that reach no DOM and could run under `node`, the ones that import a barrel. Runs Vitest once with a reporter this package ships, reads `TestModule.diagnostic()`, names files, states the rule behind each finding
|
|
@@ -1291,11 +1291,19 @@ runner's cleanups (`vi.restoreAllMocks()` restores spies, `vi.unstubAllGlobals()
|
|
|
1291
1291
|
`vi.stubGlobal`), so the fake answers every later test of the file. The `no-hand-assigned-global` lint
|
|
1292
1292
|
rule reports it. `vi.stubGlobal('fetch', …)` with `unstubGlobals: true` in the config is the runner's
|
|
1293
1293
|
own equivalent. `stubResponse` builds a real `Response` from the environment's own constructor —
|
|
1294
|
-
plain data goes out as JSON
|
|
1294
|
+
plain data goes out as JSON, `null` included, while `undefined` and an omitted `body` send no body
|
|
1295
|
+
at all — and a body reads once, so answer with a fresh one per call
|
|
1295
1296
|
(`vi.fn(async () => stubResponse(…))`) rather than `mockResolvedValue`. A spec that only has to stay
|
|
1296
1297
|
off the network wants [`blockNetwork()`](#test-run-hygiene), which leaves `fetch` to MSW's interceptor
|
|
1297
1298
|
when `server.listen()` installed one.
|
|
1298
1299
|
|
|
1300
|
+
`body: null` is a **behaviour change**: it used to send no body, so `.json()` threw
|
|
1301
|
+
`Unexpected end of JSON input` on the one JSON literal that did not round-trip while `0`, `false`,
|
|
1302
|
+
`[]` and `{}` all did. It is now the literal — which is what a backend answering "nothing here"
|
|
1303
|
+
sends — and "no body" is `undefined` or an omitted field, the spelling every caller already used.
|
|
1304
|
+
A spec that wanted the old reading drops the field; `{ body: null, status: 204 }` throws by name,
|
|
1305
|
+
since those statuses carry no body.
|
|
1306
|
+
|
|
1299
1307
|
### How to mock: the console
|
|
1300
1308
|
|
|
1301
1309
|
A test that expects output absorbs it, and asserts on it:
|
|
@@ -1442,34 +1450,34 @@ the conditional types that pick helpers from a return type — see
|
|
|
1442
1450
|
The library ships a framework-agnostic core plus runtime and framework layers, so a plain
|
|
1443
1451
|
Node / Bun / React / Vue project pulls **neither rxjs nor Angular into its runtime bundle**:
|
|
1444
1452
|
|
|
1445
|
-
| Import | Provides
|
|
1446
|
-
| ------------------------------------- |
|
|
1447
|
-
| `vitest-auto-spy` | `createSpyFromClass`, `createAutoMock`, `createFunctionSpy`, sync + promise + accessor spies, `errorHandler`, types
|
|
1448
|
-
| `vitest-auto-spy/rxjs` | observable spies (`nextWith`, `nextWithValues`, `observablePropsToSpyOn`, …) and `createObservableWithValues`
|
|
1449
|
-
| `vitest-auto-spy/angular` | `provideAutoSpy`, `injectSpy`, `renderShallow`, `setInputs`, `createWithAutoSpies`, `stable`/`flushEffects`, `settleResource`, `mockResourceProp`, the `mock*Prop` helpers, `trackRecomputations` / `trackEffectRuns` — the diagnostics, doubles and matchers below moved off this entry in 5.21.0
|
|
1450
|
-
| `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
|
|
1451
|
-
| `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
|
|
1452
|
-
| `vitest-auto-spy/angular/matchers` | `registerDirectiveMatchers`, `registerResourceMatchers`, `registerSignalMatchers` — a companion to `/angular`, no core re-export; moved off `/angular` in 5.21.0
|
|
1453
|
-
| `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest`, `expectNoRequest`, `verifyNoPendingRequests` — the `httpResource()` / `HttpClient` recipe in two lines, settling included
|
|
1454
|
-
| `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
|
|
1455
|
-
| `vitest-auto-spy/signal-forms` | `createForm` — Angular's own `form()` built where it can inject — and `registerFormMatchers()` for `toHaveFieldErrors`
|
|
1456
|
-
| `vitest-auto-spy/bun` | the same core, driven by Bun's `bun:test` mocks
|
|
1457
|
-
| `vitest-auto-spy/bun-angular` | Angular's `TestBed` under `bun test` — DOM, JIT `templateUrl` resolution and a zoneless environment, from one preload
|
|
1458
|
-
| `vitest-auto-spy/node` | the same core, driven by `node:test`'s `mock.fn()`, plus `trackNodeMocks()` — a private `MockTracker` so dropped spies are actually freed
|
|
1459
|
-
| `vitest-auto-spy/rstest` | the same core, driven by Rstest's `rstest.fn()` / `rstest.spyOn()`
|
|
1460
|
-
| `vitest-auto-spy/nestjs` | `provideAutoSpy`, `injectSpy` for `Test.createTestingModule`
|
|
1461
|
-
| `vitest-auto-spy/react` | the core, with a natural import for React Testing Library suites
|
|
1462
|
-
| `vitest-auto-spy/vue` | `provideAutoSpy` for `global.provide` + Pinia store spying
|
|
1463
|
-
| `vitest-auto-spy/svelte` | the core, with a natural import for Svelte suites
|
|
1464
|
-
| `vitest-auto-spy/console` | `consoleInfoSpy` & friends — silent typed spies over the global `console`, installed on import
|
|
1465
|
-
| `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`
|
|
1466
|
-
| `vitest-auto-spy/jasmine-compat` | `enableJasmineCompat()` alone — the same `.and` / `.calls` layer, registering no adapter, for `bun test` and `node --test`
|
|
1467
|
-
| `vitest-auto-spy/observer-spy` | `subscribeSpyTo` / `ObserverSpy` / `SubscriberSpy` — the `@hirez_io/observer-spy` surface, so `/rxjs` does not carry it
|
|
1468
|
-
| `vitest-auto-spy/dom-stubs` | the globals a component builds for itself — `stubIntersectionObserver`, `stubResizeObserver`, `stubMutationObserver`, `stubObserver`, `stubMediaElement`, `stubAbortController`, the entry builders, and `stubWebStorage` with its `snapshot()`. On the root entry until 4.0.0
|
|
1469
|
-
| `vitest-auto-spy/diagnostics` | `compareTestRuns` / `summarizeTestRun` / `formatTestRunComparison`, `diffByField` and `explainSpy` — the two reports a counter cannot give; pure functions, so a plain Node script can import this one too. On the root entry until 4.0.0
|
|
1470
|
-
| `vitest-auto-spy/setup` | `setupAutoSpy()` — property restore, duplicate-copy detection and mock-registry hygiene in one call; `setupFakeTimers()` / `advanceTimers()`
|
|
1471
|
-
| `vitest-auto-spy/zone` | `fakeAsync` / `waitForAsync` on Vitest — the ProxyZone patch `zone.js/testing` does not ship. Reads the `zone.js` **you** loaded; imports none of it
|
|
1472
|
-
| `vitest-auto-spy/eslint-plugin` | the lint rules that steer a suite onto these helpers
|
|
1453
|
+
| Import | Provides | Pulls in | Status |
|
|
1454
|
+
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | :----: |
|
|
1455
|
+
| `vitest-auto-spy` | `createSpyFromClass`, `createAutoMock`, `createFunctionSpy`, sync + promise + accessor spies, `errorHandler`, types | `vitest` | ✅ |
|
|
1456
|
+
| `vitest-auto-spy/rxjs` | observable spies (`nextWith`, `nextWithValues`, `observablePropsToSpyOn`, …) and `createObservableWithValues` | `rxjs` | ✅ |
|
|
1457
|
+
| `vitest-auto-spy/angular` | `provideAutoSpy`, `injectSpy`, `renderShallow`, `setInputs`, `createWithAutoSpies`, `stable`/`flushEffects`, `settleResource`, `mockResourceProp`, the `mock*Prop` helpers, `trackRecomputations` / `trackEffectRuns` — the diagnostics, doubles and matchers below moved off this entry in 5.21.0 | `@angular/core` | ✅ |
|
|
1458
|
+
| `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` | ✅ |
|
|
1459
|
+
| `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` | ✅ |
|
|
1460
|
+
| `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` | ✅ |
|
|
1461
|
+
| `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest`, `expectNoRequest`, `verifyNoPendingRequests` — the `httpResource()` / `HttpClient` recipe in two lines, settling included | `@angular/common` | ✅ |
|
|
1462
|
+
| `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` | ✅ |
|
|
1463
|
+
| `vitest-auto-spy/signal-forms` | `createForm` — Angular's own `form()` built where it can inject — and `registerFormMatchers()` for `toHaveFieldErrors` | `@angular/forms` (>=22) | ✅ |
|
|
1464
|
+
| `vitest-auto-spy/bun` | the same core, driven by Bun's `bun:test` mocks | `bun:test` | ✅ |
|
|
1465
|
+
| `vitest-auto-spy/bun-angular` | Angular's `TestBed` under `bun test` — DOM, JIT `templateUrl` resolution and a zoneless environment, from one preload | `bun:test`, `@angular/core`, `@angular/platform-browser` | ✅ |
|
|
1466
|
+
| `vitest-auto-spy/node` | the same core, driven by `node:test`'s `mock.fn()`, plus `trackNodeMocks()` — a private `MockTracker` so dropped spies are actually freed | `node:test` | ✅ |
|
|
1467
|
+
| `vitest-auto-spy/rstest` | the same core, driven by Rstest's `rstest.fn()` / `rstest.spyOn()` | `@rstest/core` | ✅ |
|
|
1468
|
+
| `vitest-auto-spy/nestjs` | `provideAutoSpy`, `injectSpy` for `Test.createTestingModule` | — (your `@nestjs/*`) | ✅ |
|
|
1469
|
+
| `vitest-auto-spy/react` | the core, with a natural import for React Testing Library suites | — (your `react`) | ✅ |
|
|
1470
|
+
| `vitest-auto-spy/vue` | `provideAutoSpy` for `global.provide` + Pinia store spying | — (your `vue`/`pinia`) | ✅ |
|
|
1471
|
+
| `vitest-auto-spy/svelte` | the core, with a natural import for Svelte suites | — (your `svelte`) | ✅ |
|
|
1472
|
+
| `vitest-auto-spy/console` | `consoleInfoSpy` & friends — silent typed spies over the global `console`, installed on import | `vitest` | ✅ |
|
|
1473
|
+
| `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` | ✅ |
|
|
1474
|
+
| `vitest-auto-spy/jasmine-compat` | `enableJasmineCompat()` alone — the same `.and` / `.calls` layer, registering no adapter, for `bun test` and `node --test` | — (your runner) | ✅ |
|
|
1475
|
+
| `vitest-auto-spy/observer-spy` | `subscribeSpyTo` / `ObserverSpy` / `SubscriberSpy` — the `@hirez_io/observer-spy` surface, so `/rxjs` does not carry it | `rxjs` | ✅ |
|
|
1476
|
+
| `vitest-auto-spy/dom-stubs` | the globals a component builds for itself — `stubIntersectionObserver`, `stubResizeObserver`, `stubMutationObserver`, `stubObserver`, `stubMediaElement`, `stubAbortController`, the entry builders, and `stubWebStorage` with its `snapshot()`. On the root entry until 4.0.0 | — | ✅ |
|
|
1477
|
+
| `vitest-auto-spy/diagnostics` | `compareTestRuns` / `summarizeTestRun` / `formatTestRunComparison`, `diffByField` and `explainSpy` — the two reports a counter cannot give; pure functions, so a plain Node script can import this one too. On the root entry until 4.0.0 | — | ✅ |
|
|
1478
|
+
| `vitest-auto-spy/setup` | `setupAutoSpy()` — property restore, duplicate-copy detection and mock-registry hygiene in one call; `setupFakeTimers()` / `advanceTimers()` | `vitest` | ✅ |
|
|
1479
|
+
| `vitest-auto-spy/zone` | `fakeAsync` / `waitForAsync` on Vitest — the ProxyZone patch `zone.js/testing` does not ship. Reads the `zone.js` **you** loaded; imports none of it | — (your `zone.js`) | ✅ |
|
|
1480
|
+
| `vitest-auto-spy/eslint-plugin` | the lint rules that steer a suite onto these helpers | — (your `eslint`) | ✅ |
|
|
1473
1481
|
|
|
1474
1482
|
✅ all entry points published (see [Availability](#availability)).
|
|
1475
1483
|
|
|
@@ -3099,6 +3107,7 @@ single-purpose utility you can pick up independently — they all ride on the sa
|
|
|
3099
3107
|
| `stubMediaElement(opts?)` | `/dom-stubs` | A `<video>` / `<audio>` that plays, reports a duration and fires the media events jsdom never does |
|
|
3100
3108
|
| `assertMocked(namespace, opts?)` | core | Fail when the `vi.mock()` a spec relies on silently did not apply (a bundled alias, `isolate: false`) |
|
|
3101
3109
|
| `moduleNamespace(exports, opts?)` | core | The `vi.mock` factory result an interop probe recognises — `default` + `__esModule` in place — a `default` the factory spells out is kept; `{ passthrough: true }` spies every function export through to the real one |
|
|
3110
|
+
| `createLog<Step>()` | core | The order of calls **across** collaborators as one comparable value — `add`, `fn(value)` for a labelled callback, `clear`, `items`, `result()`; `Step` is a string union, so a step nobody declared is a compile error rather than a typo the journal records |
|
|
3102
3111
|
| `diffByField(actual, expected)` | `/diagnostics` | Which field of an array of records moved, and in how many elements — the diff the reporter collapses |
|
|
3103
3112
|
| `captureArg<T>(options?)` | core | Take hold of a callback or config the code under test built, instead of describing its shape — assertions only, never `calledWith`; `{ where }` records only what it accepts and fails the position on anything else ([details](#taking-hold-of-an-argument--capturearg)) |
|
|
3104
3113
|
| `explainSpy(spy, method?)` | `/diagnostics` | Every configured argument list next to every recorded call, each attributed to the config it hit — before anything failed |
|
|
@@ -3127,11 +3136,13 @@ single-purpose utility you can pick up independently — they all ride on the sa
|
|
|
3127
3136
|
| `setInputs(fixture, inputs, opts?)` | `/angular` | Change a component input mid-test — one `setInput` per name, checked against the compiled definition, then one wait |
|
|
3128
3137
|
| `trackRecomputations(signal)` / `trackEffectRuns(ref)` | `/angular` | Count what the reactive graph actually did: `{ count, stop() }`, so "the filter change did not re-run the sync effect" is an assertion |
|
|
3129
3138
|
| `provideRouterDouble(init?)` / `injectRouterDouble()` | `/angular-router` | A `Router` derived from one URL — real `serializeUrl` / `createUrlTree`, `navigate` spies, `emitNavigation()` over a `BehaviorSubject`, `currentNavigation()` and `setCurrentNavigation()` |
|
|
3139
|
+
| `collectRouterEvents(events)` | `/angular-router` | The router events that followed, as one comparable value — `expect([[NavigationStart, '/checkout'], [NavigationEnd, '/checkout']])`, a class-and-URL pair per event, failing with the event that was there instead |
|
|
3140
|
+
| `provideLocationDouble()` / `injectLocationDouble()` / `createLocationDouble()` | `/angular-router` | Angular's own `SpyLocation` and `MockLocationStrategy` in one call — a real history, the `urlChanges` journal, `simulateUrlPop()` / `simulateHashChange()` for the browser's half of the contract; `Location` is `providedIn: 'root'`, so a spec that forgets the provider silently drives the platform's real one |
|
|
3130
3141
|
| `createForm(model, schema?)` / `registerFormMatchers()` | `/signal-forms` | A signal form built where `form()` can inject, and `expect(field).toHaveFieldErrors(['required'])` over what it produced |
|
|
3131
3142
|
| `provideWindowDouble(TOKEN, over?)` / `provideDocumentDouble(over?)` | `/angular/doubles` | A `window` / `document` merged **over** the real jsdom one, so members the spec never named still answer; the globals stay untouched |
|
|
3132
3143
|
| `provideMatDialogData(TOKEN, data)` / `provideMatDialogRef(Ref, init?)` | `/angular/doubles` | The Material dialog trio without `@angular/material` as a dependency — the token and the ref class are arguments, `afterClosed()` still answers after the close |
|
|
3133
3144
|
| `blockNetwork(options?)` | `/setup` | Close `fetch`, `XMLHttpRequest` and `sendBeacon`, naming what was requested; called twice, the last caller's mode wins; `fetch` is left to an applied MSW / nock interceptor ([details](#test-run-hygiene)) |
|
|
3134
|
-
| `stubResponse(init?)` | `/setup` | A real `Response` for a stubbed `fetch` — `body` (plain data → JSON), `status`, `ok`, `statusText`, `headers`, `url`; no cast ([recipe](#how-to-mock-fetch-and-other-globals))
|
|
3145
|
+
| `stubResponse(init?)` | `/setup` | A real `Response` for a stubbed `fetch` — `body` (plain data and `null` → JSON, `undefined` → no body), `status`, `ok`, `statusText`, `headers`, `url`; no cast ([recipe](#how-to-mock-fetch-and-other-globals)) |
|
|
3135
3146
|
| `trackStrayRejections()` / `flushStrayRejections()` / `countStrayRejections()` | `/setup` | Read back the promise rejections zone.js swallowed into `console.error`, so one can fail a test ([details](#test-run-hygiene)) |
|
|
3136
3147
|
| `trackStrayTimers()` / `cancelStrayTimers()` / `countStrayTimers()` | `/setup` | Timers a file left pending: record every timeout, interval and frame it hands out, cancel what is still outstanding when the file ends (and how many that was), or count what stands and fail on it — the two halves `setupAutoSpy({ strayTimers: true })` installs ([details](#test-run-hygiene)) |
|
|
3137
3148
|
| `guardGlobalPatches(reaction)` | `/setup` | Name the test that redefined a property of `document` / `navigator` / `globalThis` as non-configurable |
|
|
@@ -3786,6 +3797,7 @@ export can never be.
|
|
|
3786
3797
|
| `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` |
|
|
3787
3798
|
| `prefer-observer-stub` | `error` | — | a hand-rolled `IntersectionObserver` / `ResizeObserver` / `MutationObserver` written into a global → `stubIntersectionObserver()` and friends, whose undo `restoreMockedProps()` already runs |
|
|
3788
3799
|
| `no-hand-assigned-global` | `error` | — | `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 |
|
|
3800
|
+
| `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 |
|
|
3789
3801
|
| `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 |
|
|
3790
3802
|
| `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)` |
|
|
3791
3803
|
| `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 |
|
|
@@ -3807,7 +3819,7 @@ Every message ends with a link to the matching [recipe](#how-to-mock): a rule th
|
|
|
3807
3819
|
"don't" moves the problem rather than solving it. Rules travel with the API they recommend, so they
|
|
3808
3820
|
are versioned together and stop being re-written in every project that installs the package.
|
|
3809
3821
|
|
|
3810
|
-
**Four of the
|
|
3822
|
+
**Four of the forty fix on their own, thirteen offer suggestions**, and the split is not about how hard
|
|
3811
3823
|
the rewrite is. `no-mocked-for-spy` touches a _declaration_: get it wrong and the file stops
|
|
3812
3824
|
compiling, which is the loudest, cheapest failure there is — so `--fix` rewrites the type, adds
|
|
3813
3825
|
`import type { Spy } from 'vitest-auto-spy'` and drops the `Mocked` import once nothing else uses
|
|
@@ -3978,7 +3990,7 @@ package's own — it needs its ESLint integration switched on.
|
|
|
3978
3990
|
### WebStorm and the other JetBrains IDEs
|
|
3979
3991
|
|
|
3980
3992
|
No plugin to install: WebStorm, IntelliJ IDEA Ultimate, PhpStorm, PyCharm Professional and RubyMine
|
|
3981
|
-
all run ESLint natively, so the
|
|
3993
|
+
all run ESLint natively, so the forty rules appear inline, in the **Problems** tool window, and
|
|
3982
3994
|
under **Code → Inspect Code** for the whole project.
|
|
3983
3995
|
|
|
3984
3996
|
```js
|
|
@@ -3995,7 +4007,7 @@ has supported flat config since 2023.3); scope the block to spec files yourself;
|
|
|
3995
4007
|
the fixes and suggestions live.
|
|
3996
4008
|
|
|
3997
4009
|
A native JetBrains plugin is **not** planned — it would duplicate an integration the IDE already has
|
|
3998
|
-
and then keep a second copy of
|
|
4010
|
+
and then keep a second copy of forty rules, in Kotlin, in step with the TypeScript ones.
|
|
3999
4011
|
|
|
4000
4012
|
### VS Code, Cursor, Windsurf, VSCodium
|
|
4001
4013
|
|
|
@@ -4103,10 +4115,12 @@ another, and the next one is in that same file.
|
|
|
4103
4115
|
| `stable(fixture, opts?)` / `flushEffects()` _(Angular)_ | Zoneless waiting: flush effects, then await the fixture, with a 2 s budget that names the cause |
|
|
4104
4116
|
| `settleResource(resource, opts?)` _(Angular)_ | Tick until an `httpResource()` / `resource()` / `rxResource()` leaves `loading` |
|
|
4105
4117
|
| `provideHttpTesting(opts?)` / `expectRequest(matcher, opts?)` _(`/angular-http`)_ | `provideHttpClient()` + `provideHttpClientTesting()` in one spread; find the one matching request and `flush` / `error` it with the settling included |
|
|
4106
|
-
| `expectNoRequest(matcher?, opts?)` / `verifyNoPendingRequests()` _(`/angular-http`)_
|
|
4118
|
+
| `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 |
|
|
4107
4119
|
| `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 |
|
|
4108
4120
|
| `createActivatedRoute(init?)` _(`/angular-router`)_ | The same double without a `TestBed` — `.route` plus the setters |
|
|
4109
4121
|
| `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 |
|
|
4122
|
+
| `collectRouterEvents(events)` _(`/angular-router`)_ | The double's router events as one comparable value — `expect([[NavigationStart, '/checkout'], …])`, a class-and-URL pair per event |
|
|
4123
|
+
| `provideLocationDouble()` / `injectLocationDouble(injector?)` / `createLocationDouble()` _(`/angular-router`)_ | Angular's own `SpyLocation` and `MockLocationStrategy` in one call — a real history, the `urlChanges` journal, `simulateUrlPop()` for the popstate no method call can cause |
|
|
4110
4124
|
| `createForm(model, schema?, options?)` / `registerFormMatchers()` _(`/signal-forms`)_ | Angular's own signal `form()`, built in the `TestBed`'s injection context, and the `toHaveFieldErrors` matcher over its `errors()` |
|
|
4111
4125
|
| `registerSignalMatchers()` _(Angular)_ | Adds `expect(sig).toHaveSignalValue(value)` |
|
|
4112
4126
|
| `trackRecomputations(signal)` / `trackEffectRuns(effectRef)` _(Angular)_ | Count what the reactive graph re-ran — `{ count, stop() }`, undone by `restoreMockedProps()` |
|
|
@@ -4117,6 +4131,7 @@ another, and the next one is in that same file.
|
|
|
4117
4131
|
| `stubMediaElement(opts?)` | A `<video>` / `<audio>` that plays, reports a duration and fires the media events |
|
|
4118
4132
|
| `assertMocked(namespace, opts?)` / `moduleNamespace(exports, opts?)` | Prove a `vi.mock()` applied; give its factory the shape an interop probe recognises, or spy the real module through (`passthrough`) |
|
|
4119
4133
|
| `flushEventLoopUntil(isDone, opts?)` | Real event-loop turns until a condition holds, with a budget instead of a hang |
|
|
4134
|
+
| `createLog<Step>()` | The order of calls across collaborators as one comparable value — `add`, `fn(value)`, `items`, `result()`; the step union makes an undeclared step a compile error |
|
|
4120
4135
|
| `diffByField(actual, expected)` | Which field of an array of records moved, and in how many elements |
|
|
4121
4136
|
| `guardGlobalPatches(reaction)` / `installPerTest(install)` _(`/setup`)_ | Name the test that sealed a global property; re-install a stub before every test |
|
|
4122
4137
|
| `guardStrayConsole(reaction)` / `withoutStrayTimerTracking(work)` _(`/setup`)_ | Fail a test on console output nothing absorbed; keep setup work's timers out of the stray-timer count |
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { instrumentTestBed, onTestingModuleConfigured, onComponentCreated, beforeTestBedReset, verifyOnTeardown } from './chunk-KOR5OK4H.js';
|
|
2
2
|
export { disableTestBedDiagnostics, enableTestBedDiagnostics, formatSpecTiming, getTestBedTiming, instrumentTestBed, reportSpecTiming } from './chunk-KOR5OK4H.js';
|
|
3
|
-
import { failOnUnspiedProvider } from './chunk-
|
|
4
|
-
import { assertAngularInternals } from './chunk-
|
|
5
|
-
import './chunk-
|
|
6
|
-
import './chunk-
|
|
7
|
-
import './chunk-
|
|
3
|
+
import { failOnUnspiedProvider } from './chunk-45W5LBPY.js';
|
|
4
|
+
import { assertAngularInternals } from './chunk-WYYKUPXW.js';
|
|
5
|
+
import './chunk-MBSPAUUD.js';
|
|
6
|
+
import './chunk-DQ3SPSCH.js';
|
|
7
|
+
import './chunk-MG7LKE4R.js';
|
|
8
8
|
import './chunk-PC3FAZT3.js';
|
|
9
|
-
import './chunk-
|
|
10
|
-
import { isAutoSpyLike } from './chunk-
|
|
11
|
-
import { DOCS_LINKS } from './chunk-
|
|
9
|
+
import './chunk-LZVIH54N.js';
|
|
10
|
+
import { isAutoSpyLike } from './chunk-BBPYNKGB.js';
|
|
11
|
+
import { DOCS_LINKS } from './chunk-QO4BPR3V.js';
|
|
12
12
|
import { withDocs } from './chunk-TZ7DRSRD.js';
|
|
13
13
|
import { getTestBed } from '@angular/core/testing';
|
|
14
14
|
import { beforeEach, afterEach } from 'vitest';
|
package/dist/angular-doubles.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { useVitestAdapter } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
import { useVitestAdapter } from './chunk-V6TIK7XU.js';
|
|
2
|
+
import './chunk-MUZZBRUP.js';
|
|
3
3
|
import './chunk-YHDBRBGU.js';
|
|
4
|
-
import './chunk-
|
|
5
|
-
import './chunk-
|
|
4
|
+
import './chunk-CPLUHRHD.js';
|
|
5
|
+
import './chunk-MG7LKE4R.js';
|
|
6
6
|
import './chunk-PC3FAZT3.js';
|
|
7
|
-
import { createFunctionSpy } from './chunk-
|
|
8
|
-
import './chunk-
|
|
9
|
-
import { DOCS_LINKS } from './chunk-
|
|
7
|
+
import { createFunctionSpy } from './chunk-LZVIH54N.js';
|
|
8
|
+
import './chunk-BBPYNKGB.js';
|
|
9
|
+
import { DOCS_LINKS } from './chunk-QO4BPR3V.js';
|
|
10
10
|
import { withDocs } from './chunk-TZ7DRSRD.js';
|
|
11
11
|
import { TestBed } from '@angular/core/testing';
|
|
12
12
|
import { ReplaySubject } from 'rxjs';
|
package/dist/angular-http.d.ts
CHANGED
|
@@ -49,8 +49,14 @@ interface HttpTestingOptions {
|
|
|
49
49
|
* A request nobody flushed is a spec whose code under test is still waiting on a response — every
|
|
50
50
|
* assertion after that call is about a state the test never reached. Left alone it also leaks:
|
|
51
51
|
* the next test's `expectRequest` matches a request the previous one made.
|
|
52
|
+
*
|
|
53
|
+
* An object keeps the check on and carries its option to it: `{ ignoreCancelled: true }` is the
|
|
54
|
+
* same opt-in as `verifyNoPendingRequests({ ignoreCancelled: true })` — a request the code under
|
|
55
|
+
* test cancelled, by unsubscribing, no longer fails the test.
|
|
52
56
|
*/
|
|
53
|
-
verifyOnTeardown?: boolean
|
|
57
|
+
verifyOnTeardown?: boolean | {
|
|
58
|
+
ignoreCancelled?: boolean;
|
|
59
|
+
};
|
|
54
60
|
}
|
|
55
61
|
/** The body types `TestRequest#flush` takes, read off Angular rather than restated here. */
|
|
56
62
|
type ResponseBody = Parameters<TestRequest['flush']>[0];
|
|
@@ -121,8 +127,14 @@ declare function expectNoRequest(matcher?: RequestMatcher, options?: ExpectReque
|
|
|
121
127
|
* it — and because a suite that turned `verifyOnTeardown` off still wants it in the two specs where
|
|
122
128
|
* it matters.
|
|
123
129
|
*
|
|
130
|
+
* `ignoreCancelled: true` is the opt-in `HttpTestingController.verify({ ignoreCancelled })` names:
|
|
131
|
+
* a request the code under test cancelled — an unsubscribed `httpResource()`, a `takeUntil` that
|
|
132
|
+
* cut a call — is taken but no longer held against the test. Default `false`, like Angular's own.
|
|
133
|
+
*
|
|
124
134
|
* A no-op when the test configured no HTTP testing at all.
|
|
125
135
|
*/
|
|
126
|
-
declare function verifyNoPendingRequests(
|
|
136
|
+
declare function verifyNoPendingRequests(options?: {
|
|
137
|
+
ignoreCancelled?: boolean;
|
|
138
|
+
}): void;
|
|
127
139
|
|
|
128
140
|
export { type ExpectRequestOptions, type FlushOptions, type HttpTestingOptions, type RequestErrorOptions, type RequestExpectation, type RequestMatcher, type ResponseBody, expectNoRequest, expectRequest, provideHttpTesting, verifyNoPendingRequests };
|