vitest-auto-spy 5.46.0 → 5.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -81,7 +81,7 @@ Add-ons, orthogonal to the runner:
81
81
  | --- | --- | --- |
82
82
  | Observable spies | `import 'vitest-auto-spy/rxjs'` | `nextWith` & friends. **Side-effect import, once**, in a setup file, a spec, or a `.d.ts` the `tsconfig` includes (§4) |
83
83
  | observer-spy shim | `vitest-auto-spy/observer-spy` | `subscribeSpyTo` — the `@hirez_io/observer-spy` surface (§20). Its own entry so `/rxjs` does not carry it |
84
- | Console spies | `vitest-auto-spy/console` | silent typed spies over the global `console` — `installConsoleSpies()` per test, `restoreConsole()` after; `consoleOutput()` is everything logged, keyed by channel |
84
+ | Console spies | `vitest-auto-spy/console` | silent typed spies over the global `console` — `useConsoleSpies()` in the `describe` (or `installConsoleSpies()` per test, `restoreConsole()` after); `consoleOutput()` is everything logged, keyed by channel |
85
85
  | DOM stubs | `vitest-auto-spy/dom-stubs` | `stubIntersectionObserver` / `stubResizeObserver` / `stubMutationObserver` / `stubObserver`, `stubMediaElement`, `stubAbortController`, `stubWebStorage` (§12), `stubAnimationFrame`, `stubElementRect`, `intersectionEntry` / `resizeEntry` / `mutationRecord`. **Moved off the root in 4.0.0** |
86
86
  | Run diagnostics | `vitest-auto-spy/diagnostics` | `compareTestRuns`, `summarizeTestRun`, `formatTestRunComparison`, `diffByField`. **Moved off the root in 4.0.0** |
87
87
  | Angular HTTP | `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest` — `httpResource()` / `HttpClient` (§13). Optional `@angular/common` peer, this entry only |
package/CHANGELOG.md CHANGED
@@ -10,6 +10,52 @@ The latest released version here must always match the one published on
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [5.47.0] - 2026-09-28
14
+
15
+ ### Added
16
+
17
+ - **`prefer-to-have-signal-value`** (ESLint plugin, `warn`, type-aware, autofix) rewrites
18
+ `expect(component.total()).toBe(3)` into `expect(component.total).toHaveSignalValue(3)`:
19
+ the matcher keeps the signal's name in the failure output and refuses a value that is not a
20
+ signal. `toEqual` maps the same way, `toStrictEqual` keeps its comparison through
21
+ `{ strict: true }`. The signal is recognised by its type — callable and branded with Angular's
22
+ signal brand, spelled `__@SIGNAL@…` by the checker — so methods, plain functions and getters stay
23
+ unreported, and without parser services the rule says nothing. Safe to autofix since 5.46.0, which
24
+ made `toHaveSignalValue` compare nested `Set` and `Map` values.
25
+ - **`registerSignalMatchers({ strict: true })`** flips the default of one registration: every
26
+ `toHaveSignalValue` of the suite compares like `toStrictEqual` without repeating the option per
27
+ assertion, and a single assertion can still pass `{ strict: false }`.
28
+ - **`expectUnhandledObservableErrors([...])` from `vitest-auto-spy/setup`** asserts the rxjs errors
29
+ nothing handled are exactly the ones a test deliberately left — each expected entry the error
30
+ itself, its class, or a message (a string or a pattern), compared in order — and hands the
31
+ flushed entries back. Zero-argument form asserts none were left.
32
+ - **`prepareShallow(Component, options).create(overrides)` from `vitest-auto-spy/angular`** binds a
33
+ component and the options every test of a suite repeats; `create()` runs the `renderShallow`
34
+ sequence with the overrides applied per key — `inputs` for an `it.each`, `providers` for a
35
+ one-off. One instance per test: the runner resets the TestBed between tests, and two `create()`
36
+ calls inside a single test need a manual `TestBed.resetTestingModule()`.
37
+ - **`useConsoleSpies()` from `vitest-auto-spy/console`** registers `beforeEach(installConsoleSpies)`
38
+ and `afterEach(restoreConsole)` in the enclosing `describe` and returns the spies, replacing the
39
+ hook pair every console-asserting spec wrote by hand. It registers Vitest hooks (Bun resolves them
40
+ to its own); on `node:test` and Rstest the pair stays manual. `no-import-time-console-spies`
41
+ accepts it as the file's install, and the stray-console report and docs recommend it.
42
+
43
+ ### Fixed
44
+
45
+ - **An app error in `expectRequest` / `expectNoRequest`'s tick under `provideDocumentDouble()` is no
46
+ longer blamed on the double.** 5.46.0 rewrapped every tick failure whenever `DOCUMENT` was not the
47
+ real document, so an effect that threw under the library's own double read as "a partial double
48
+ does not have" the members Angular reads, and pointed at `{ tick: false }`, which hides the error.
49
+ The message is now kept for a `DOCUMENT` that is not a document, or whose `body` has no
50
+ `querySelector`; any other failure is rethrown as it is.
51
+
52
+ ### Documentation
53
+
54
+ - **`provideDocumentDouble()` needs no `{ tick: false }`**, and a JSON error body is
55
+ `flush(body, { status })` — `.error(status, { error })` takes a `ProgressEvent`, as Angular's
56
+ `TestRequest.error()` does. The HTTP page, `agent-docs/angular.md` and `agent-docs/errors.md` say so.
57
+ - **The README's `SubjectLike` member list gains `unsubscribe`**, as `agent-docs` did in 5.46.0.
58
+
13
59
  ## [5.46.0] - 2026-09-27
14
60
 
15
61
  ### Added
@@ -6000,7 +6046,8 @@ moved under 1 %: `/angular` +192 B (+1.0 %) for the override queue and the HTTP
6000
6046
  This copy ships with the package and carries 5.x only. Releases before 5.0.0 are in the
6001
6047
  [full changelog](https://github.com/ASDAlexey/vitest-auto-spy/blob/master/CHANGELOG.md).
6002
6048
 
6003
- [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.46.0...HEAD
6049
+ [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.47.0...HEAD
6050
+ [5.47.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.46.0...v5.47.0
6004
6051
  [5.46.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.45.0...v5.46.0
6005
6052
  [5.45.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.44.0...v5.45.0
6006
6053
  [5.44.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.43.0...v5.44.0
package/README.md CHANGED
@@ -81,7 +81,7 @@ faster at suite scale ([benchmarks](#benchmarks)) — and for
81
81
  - 🧩 Module mocks that prove they applied — `assertMocked`, `moduleNamespace`, for a `vi.mock()` a bundler quietly ignored; `moduleNamespace(await importOriginal(), { passthrough: true })` is Vitest's `{ spy: true }` with `calledWith` on top, on any runner
82
82
  - 🧾 Fixtures without casts — deep-partial `createMock`, `createFixture` / `createFixtureFactory`, `narrow()`, `withOverrides()`, `asInstances()`, `captureArg()`, and `outOfType()` for the fixture that is outside its type on purpose
83
83
  - 🚚 A migration you can verify — `vitest-auto-spy/diagnostics`: `compareTestRuns` on the two JSON reports, `summarizeTestRun` / `formatTestRunComparison` to read the answer, `diffByField` for the assertion the reporter collapses, `explainSpy` for a double that answered something you did not configure
84
- - 📏 Lint rules and one-line test-run hygiene — forty-nine rules in `vitest-auto-spy/eslint-plugin` (six `--fix`, nineteen suggestions, four of them for a suite mid-migration off jasmine), `setupAutoSpy()` — with `preset: 'strict'` for every guard at its strictest grade, and under `isolate: false` `strayListeners` / `restoreGlobals` for the listeners and raw global assignments a file leaves the next one, `restoreStorageSpies` (on by default) for the storage spy happy-dom will not let `mockRestore()` remove
84
+ - 📏 Lint rules and one-line test-run hygiene — fifty rules in `vitest-auto-spy/eslint-plugin` (eight `--fix`, nineteen suggestions, four of them for a suite mid-migration off jasmine), `setupAutoSpy()` — with `preset: 'strict'` for every guard at its strictest grade, and under `isolate: false` `strayListeners` / `restoreGlobals` for the listeners and raw global assignments a file leaves the next one, `restoreStorageSpies` (on by default) for the storage spy happy-dom will not let `mockRestore()` remove
85
85
  - 🩺 [Editor diagnostics](#editor-diagnostics--webstorm--vs-code) — the same anti-patterns underlined while you type: native ESLint inspections in **WebStorm** and the other JetBrains IDEs, the ESLint extension in **VS Code**, no extra plugin either way
86
86
  - 🔎 [`npx vitest-auto-spy doctor`](#the-cli--doctor-perf-codemod-init-and-ng-test) — suite-level defects **that never fail a run**: a `tsconfig` `include` matching no file, a production module importing a spec, a `@jest-environment` pragma the runner never reads, config left behind for a runner that is gone. Read-only, no config, exits 1 in CI
87
87
  - ⏱️ [`npx vitest-auto-spy perf`](#perf--where-the-cpu-time-actually-goes) — where a suite's CPU time actually goes, phase by phase, and which spec files to act on: the ones that reach no DOM and could run under `node`, the ones that import a barrel. Runs Vitest once with a reporter this package ships, reads `TestModule.diagnostic()`, names files, states the rule behind each finding
@@ -876,24 +876,21 @@ since those statuses carry no body.
876
876
  A test that expects output absorbs it, and asserts on it:
877
877
 
878
878
  ```ts
879
- import { type ConsoleSpies, installConsoleSpies, restoreConsole } from 'vitest-auto-spy/console';
879
+ import { useConsoleSpies } from 'vitest-auto-spy/console';
880
880
 
881
- let consoleSpies: ConsoleSpies;
882
-
883
- beforeEach(() => {
884
- consoleSpies = installConsoleSpies();
885
- });
886
-
887
- afterEach(() => restoreConsole());
881
+ const { consoleErrorSpy } = useConsoleSpies();
888
882
 
889
883
  it('reports a failed load', () => {
890
884
  service.load();
891
885
 
892
- expect(consoleSpies.consoleErrorSpy).toHaveBeenCalledWith('load failed', expect.any(Error));
886
+ expect(consoleErrorSpy).toHaveBeenCalledWith('load failed', expect.any(Error));
893
887
  });
894
888
  ```
895
889
 
896
- The exported `consoleErrorSpy` is the same object, so `expect(consoleErrorSpy)` works too. Do not
890
+ `useConsoleSpies()` registers `beforeEach(installConsoleSpies)` and `afterEach(restoreConsole)` in
891
+ the enclosing `describe` (or the file) and returns the spies, which are the same objects in every
892
+ test. It registers Vitest hooks, which Bun resolves to its own; on `node:test` and Rstest write that
893
+ pair in the runner's hooks yourself. Do not
897
894
  rely on the **import** to install them: it does so once per worker, so under `isolate: false` the
898
895
  spies go on in whichever file imported them first and silence every later file of the worker — on a
899
896
  1759-file Angular consumer, 32 of the 39 files importing the entry relied on that, and three files
@@ -2531,7 +2528,7 @@ subject.next([{ name: 'manual' }]);
2531
2528
 
2532
2529
  `returnSubject()` and `nextWithPerCall()` are typed `SubjectOf<T>`: rxjs's own `Subject<T>` wherever
2533
2530
  `import 'vitest-auto-spy/rxjs'` is in the TypeScript program, and the structural `SubjectLike<T>`
2534
- (`next` / `error` / `complete` / `asObservable` / `closed`) where it is not. That is how the
2531
+ (`next` / `error` / `complete` / `unsubscribe` / `asObservable` / `closed`) where it is not. That is how the
2535
2532
  published declarations name no rxjs type while a suite that has rxjs keeps the exact type it had.
2536
2533
  The seam is one augmentable interface, `AutoSpyRxjsTypes<T>`, which the `/rxjs` entry fills in; what
2537
2534
  decides that a member _is_ an observable is the other new type, `ObservableLike<T>` — `subscribe`
@@ -2945,6 +2942,19 @@ function. The matcher reads it, deep-compares (as `toEqual` does; `{ strict: tru
2945
2942
  `toStrictEqual` does), and rejects anything that is not a zero-argument getter, so the
2946
2943
  missing-parentheses mistake fails instead of quietly passing.
2947
2944
 
2945
+ A suite that wants the strict comparison everywhere passes it once instead of per assertion;
2946
+ a single assertion can still opt back out:
2947
+
2948
+ ```ts
2949
+ registerSignalMatchers({ strict: true });
2950
+
2951
+ expect(component.items).toHaveSignalValue(items); // compares like toStrictEqual
2952
+ expect(component.draft).toHaveSignalValue({}, { strict: false });
2953
+ ```
2954
+
2955
+ The `prefer-to-have-signal-value` lint rule rewrites the inline spellings — `expect(component.total()).toBe(3)`
2956
+ — into the matcher with a `--fix`.
2957
+
2948
2958
  #### Where a spec spends its time
2949
2959
 
2950
2960
  ```ts
@@ -3141,7 +3151,7 @@ and `flush()` / `error()` settle before they resolve, which is why the value rea
3141
3151
  Match by URL (either `url` or `urlWithParams`), by `RegExp`, or by a predicate over the request, with
3142
3152
  `{ method }` to tell a read from a write. `error(0)` is a network failure, and
3143
3153
  `error(status, { error })` sets the payload the `HttpErrorResponse` carries. `{ tick: false }` skips
3144
- the tick, for a module whose partial `DOCUMENT` double the tick cannot run against. `provideHttpTesting()` fails, by default, any test that
3154
+ the tick, for a module whose partial `DOCUMENT` double the tick cannot run against (`provideDocumentDouble()` is not partial). `provideHttpTesting()` fails, by default, any test that
3145
3155
  ends holding a request nothing answered; `provideHttpTesting({ verifyOnTeardown: false })` turns that
3146
3156
  off, and `verifyNoPendingRequests()` runs the same check by hand. An interceptor under test goes in
3147
3157
  the same spread — `provideHttpTesting({ interceptors: [authInterceptor] })`, or
@@ -3342,6 +3352,7 @@ single-purpose utility you can pick up independently — they all ride on the sa
3342
3352
  | `withoutStrayTimerTracking(work)` | `/setup` | Run setup work whose timers the stray-timer tracker neither counts nor cancels — jsdom schedules one per Web Storage write |
3343
3353
  | `describeStrayTimers()` | `/setup` | Every timer still pending, with its kind and delay, the spec file that scheduled it and the scheduling frames — the list `onStrayTimers` gets. Blind to anything scheduled under fake timers — `vi.useFakeTimers()` assigns over the wrapper |
3344
3354
  | `flushUnhandledObservableErrors()` | `/setup` | Run rxjs's pending rethrows of Observable errors nothing handled, now, and return `{ error, test }` (or `outsideTest`) for each — the check `strayTimers` runs after every test, which fails the test with `Unhandled Observable error` |
3355
+ | `expectUnhandledObservableErrors(expected?)` | `/setup` | The same flush as an assertion: the errors nothing handled must be exactly the ones listed — each entry the error itself, its class, or a message (string or pattern) — or the test fails with the diff; no argument asserts none were left |
3345
3356
  | `guardPrototypePollution(reaction)` | `/setup` | Name the test that left a key on `Object.prototype` — it stops later files collecting ([details](#test-run-hygiene)) |
3346
3357
  | `guardDocumentPollution(option)` | `/setup` | Name the test that left an attribute on `<html>` / `<body>`, and put it back ([details](#test-run-hygiene)) |
3347
3358
  | `installPerTest(install)` | `/setup` | Re-install a stub before every test of the block — a `describe`-level stub is restored away after the first |
@@ -3377,15 +3388,14 @@ onSave.calledWith(1).resolveWith();
3377
3388
  `installConsoleSpies()` replaces `console.debug` / `error` / `info` / `log` / `time` / `timeEnd` /
3378
3389
  `trace` / `warn` with **silent, fully-typed spies**, and the entry exports each one ready to assert —
3379
3390
  no `vi.spyOn(console, 'info')` boilerplate in every suite, no log output polluting the test run.
3380
- Install them in a `beforeEach` and `restoreConsole()` in the `afterEach` (see
3381
- [How to mock: the console](#how-to-mock-the-console)); importing the entry installs them too, but only
3382
- once per worker, which under `isolate: false` silences every later file:
3391
+ `useConsoleSpies()` installs them before each test of the block and restores the console after
3392
+ (see [How to mock: the console](#how-to-mock-the-console)); importing the entry installs them too, but
3393
+ only once per worker, which under `isolate: false` silences every later file:
3383
3394
 
3384
3395
  ```ts
3385
- import { consoleInfoSpy, consoleWarnSpy, installConsoleSpies, restoreConsole } from 'vitest-auto-spy/console';
3396
+ import { consoleInfoSpy, consoleWarnSpy, useConsoleSpies } from 'vitest-auto-spy/console';
3386
3397
 
3387
- beforeEach(() => installConsoleSpies());
3388
- afterEach(() => restoreConsole());
3398
+ useConsoleSpies();
3389
3399
 
3390
3400
  service.doWork();
3391
3401
 
@@ -3405,8 +3415,8 @@ the worker and every log from then on went nowhere.
3405
3415
 
3406
3416
  Under `setupAutoSpy({ strayConsole })` importing the entry **installs nothing**: under
3407
3417
  `isolate: false` the import ran once per worker and left the spies silencing every later file, which
3408
- is exactly the output the guard exists to see. Call `installConsoleSpies()` in a `beforeEach` (for
3409
- each test) or at the top of the spec file (for the file); the guard takes them off again when that
3418
+ is exactly the output the guard exists to see. Call `useConsoleSpies()` (or `installConsoleSpies()` in
3419
+ a `beforeEach`) for each test, or `installConsoleSpies()` at the top of the spec file for the file; the guard takes them off again when that
3410
3420
  scope ends. See [How to mock: the console](#how-to-mock-the-console).
3411
3421
 
3412
3422
  > The spies use the registered `MockAdapter` — import your runtime entry
@@ -4007,6 +4017,7 @@ export can never be.
4007
4017
  | `no-import-time-spread` | `error` | suggest | `export const x = [...Imported]` at module scope → a `TypeError`, or a silently empty object, while the bundle loads |
4008
4018
  | `no-unregistered-inject-spy` | `error` | — | `injectSpy(X)` for a token this file never registered → the real instance, whose spy helpers exist only for the compiler |
4009
4019
  | `prefer-render-shallow` | `warn` | suggest | `TestBed.createComponent` in a file that never reads the template → `renderShallow(X)`; 0.24× the per-test cycle at 100 children |
4020
+ | `prefer-to-have-signal-value` | `warn` | `--fix` | `expect(signal()).toBe(…)` → `expect(signal).toHaveSignalValue(…)`; the matcher names the signal in the failure and refuses a non-signal. `toStrictEqual` keeps `{ strict: true }`. **Type-aware** — the callee must be callable and carry Angular's signal brand — and silent without `parserOptions.project`; safe to autofix since 5.46.0 fixed the `Set`/`Map` comparison |
4010
4021
  | `prefer-set-inputs` | `warn` | suggest | a run of `fixture.componentRef.setInput('title', v)` on one fixture → `await setInputs(fixture, { title: v })`, which resolves every name against the compiled definition before it writes one — an undeclared name is an `NG0303` on the console and no change at all — and types the value. The suggestion collapses the run, drops a `detectChanges()` under it and makes the callback `async`; it is offered rather than applied, because `stable()` ticks and a zone.js suite can answer that with `NG0101` |
4011
4022
  | `prefer-spy-on-own-method` | `warn` | `--fix` / suggest | `createSpyFromInstance(x, { onlyMethodsToSpyOn: ['m'], passthrough: true })` read for `m` alone — `.m` on the call, `const { m } = …`, a bare statement, or a name read only as `v.m` → `spyOnOwnMethod(x, 'm')`; the same whitelist with `returns: { m: undefined }` → `spyOnVoidMethod(x, 'm')`. Both fix, import and all; a `Spy<X>` annotation becomes `Spy<X>['m']` in a suggestion. A bare `returns: { m: undefined }` seed is a suggestion, offered only on a real event or element — `new MouseEvent(…)`, `document.createElement(…)`, `fixture.nativeElement` — never on a double |
4012
4023
  | `prefer-observer-stub` | `error` | — | a hand-rolled `IntersectionObserver` / `ResizeObserver` / `MutationObserver` written into a global → `stubIntersectionObserver()` and friends, whose undo `restoreMockedProps()` already runs |
@@ -4015,7 +4026,7 @@ export can never be.
4015
4026
  | `prefer-provide-activated-route` | `error` | — | a hand-built `ActivatedRoute` — any `useValue` / `useClass` / `useFactory`, and `provideAutoSpy(ActivatedRoute)` too → `provideActivatedRoute({ … })`; the double knows either the streams or the snapshot, never both, and `injectActivatedRoute().setParams(…)` moves them together mid-test |
4016
4027
  | `no-passthrough-console-spy` | `error` | suggest | `vi.spyOn(console, m)` nothing gives an implementation — it calls through and still prints → `installConsoleSpies()` from `vitest-auto-spy/console`, or `.mockImplementation(() => undefined)` |
4017
4028
  | `no-console-in-spec` | `error` | — | a spec that calls `console.x(…)` itself, or replaces a method with `console.x = …`, which nothing puts back — under `isolate: false` every later file inherits it |
4018
- | `no-import-time-console-spies` | `error` | — | an import of `vitest-auto-spy/console` in a file that never calls `installConsoleSpies()` — the import installs once per worker and, under `isolate: false`, silences every later file |
4029
+ | `no-import-time-console-spies` | `error` | — | an import of `vitest-auto-spy/console` in a file that never calls `installConsoleSpies()` or `useConsoleSpies()` — the import installs once per worker and, under `isolate: false`, silences every later file |
4019
4030
  | `no-mistyped-use-value` | `error` | — | `{ provide: TOKEN, useValue }` whose value is not assignable to the primitive `TOKEN` declares — `useValue` is `any`, so `{}` for an `InjectionToken<boolean>` compiles and is truthy; **type-aware**, and silent without `parserOptions.project` |
4020
4031
  | `no-unknown-use-value-key` | `error` | — | a key of an object `useValue` literal the provided type does not have — `T` of an `InjectionToken<T>`, or a class's instance; keys only, never the values; silent on `any` / `unknown`, an index signature, a spread and `multi: true`; **type-aware** |
4021
4032
  | `no-instance-lifecycle-spy` | `warn` | — | `vi.spyOn(component, 'ngOnInit')` and the other hooks a view reads off the prototype — Angular never calls the instance spy → spy on `Cls.prototype` before the component is created, or assert the hook's effect |
@@ -4040,7 +4051,7 @@ Every message ends with a link to the matching [recipe](#how-to-mock): a rule th
4040
4051
  "don't" moves the problem rather than solving it. Rules travel with the API they recommend, so they
4041
4052
  are versioned together and stop being re-written in every project that installs the package.
4042
4053
 
4043
- **Six of the forty-nine fix on their own, nineteen offer suggestions**, and the split is not about how hard
4054
+ **Eight of the fifty fix on their own, nineteen offer suggestions**, and the split is not about how hard
4044
4055
  the rewrite is. `no-mocked-for-spy` touches a _declaration_: get it wrong and the file stops
4045
4056
  compiling, which is the loudest, cheapest failure there is — so `--fix` rewrites the type, adds
4046
4057
  `import type { Spy } from 'vitest-auto-spy'` and drops the `Mocked` import once nothing else uses
@@ -4211,7 +4222,7 @@ package's own — it needs its ESLint integration switched on.
4211
4222
  ### WebStorm and the other JetBrains IDEs
4212
4223
 
4213
4224
  No plugin to install: WebStorm, IntelliJ IDEA Ultimate, PhpStorm, PyCharm Professional and RubyMine
4214
- all run ESLint natively, so the forty-nine rules appear inline, in the **Problems** tool window, and
4225
+ all run ESLint natively, so the fifty rules appear inline, in the **Problems** tool window, and
4215
4226
  under **Code → Inspect Code** for the whole project.
4216
4227
 
4217
4228
  ```js
@@ -4228,7 +4239,7 @@ has supported flat config since 2023.3); scope the block to spec files yourself;
4228
4239
  the fixes and suggestions live.
4229
4240
 
4230
4241
  A native JetBrains plugin is **not** planned — it would duplicate an integration the IDE already has
4231
- and then keep a second copy of forty-nine rules, in Kotlin, in step with the TypeScript ones.
4242
+ and then keep a second copy of fifty rules, in Kotlin, in step with the TypeScript ones.
4232
4243
 
4233
4244
  ### VS Code, Cursor, Windsurf, VSCodium
4234
4245
 
@@ -4334,6 +4345,7 @@ another, and the next one is in that same file.
4334
4345
  | `setupFakeTimers(config?)` / `advanceTimers(ms?)` _(`/setup`)_ | Paired fake-timer install/restore, and an advance that also settles queued microtasks |
4335
4346
  | `describeDuplicateCopies()` / `getPackageCopies()` | The duplicate-install report, and the copies behind it |
4336
4347
  | `renderShallow(Component, opts?)` _(Angular)_ | `TestBed` component, minus its children and (by default) its template |
4348
+ | `prepareShallow(Component, opts?)` _(Angular)_ | `renderShallow` bound to shared options — `.create(overrides?)` per test, one instance each |
4337
4349
  | `setInputs(fixture, inputs, opts?)` _(Angular)_ | Change an input after the first render — one `setInput` per name, checked against the compiled definition, then one wait |
4338
4350
  | `createWithAutoSpies(Class, opts?)` _(Angular)_ | Build a class through Angular DI with every unprovided token auto-spied |
4339
4351
  | `createNestUnit(Class, opts?)` _(NestJS)_ | Build a provider from its DI metadata with every unprovided token auto-spied; `expose` builds collaborators for real, `providers` wins over both |
@@ -4366,6 +4378,7 @@ another, and the next one is in that same file.
4366
4378
  | `guardDocumentPollution(option)` _(`/setup`)_ | Name the test that left an attribute on `<html>` / `<body>`, before a later file takes another branch on it |
4367
4379
  | `consoleDebugSpy` … `consoleWarnSpy` _(`/console`)_ | Silent typed spies replacing the global `console` methods on import |
4368
4380
  | `installConsoleSpies()` / `resetConsoleSpies()` / `restoreConsole()` | Install / clear / undo the console spies |
4381
+ | `useConsoleSpies()` _(`/console`)_ | Install the console spies before each test of the block, restore after; returns the spies |
4369
4382
  | `consoleOutput()` _(`/console`)_ | Everything the console spies recorded, keyed by channel, only the channels written to — `toStrictEqual({})` is silence |
4370
4383
  | `explainSpy(spy, method?)` _(`/diagnostics`)_ | Every configured argument list next to every recorded call, attributed to the config it hit |
4371
4384
  | `errorHandler` | The `mustBeCalledWith` argument-mismatch error helper |
@@ -28,21 +28,19 @@ import { createSpyFromClass } from 'vitest-auto-spy/react';
28
28
 
29
29
  Console spies — `installConsoleSpies()` replaces `console.debug` / `error` / `info` / `log` / `time` /
30
30
  `timeEnd` / `trace` / `warn` with silent typed spies, named `console<Method>Spy`. Install them per test
31
- and take them off after it:
31
+ and take them off after it — `useConsoleSpies()` registers exactly that pair in the enclosing `describe`:
32
32
 
33
33
  ```ts
34
- import { type ConsoleSpies, installConsoleSpies, restoreConsole } from 'vitest-auto-spy/console';
34
+ import { useConsoleSpies } from 'vitest-auto-spy/console';
35
35
 
36
- let consoleSpies: ConsoleSpies;
36
+ const { consoleInfoSpy } = useConsoleSpies();
37
37
 
38
- beforeEach(() => {
39
- consoleSpies = installConsoleSpies();
40
- });
41
- afterEach(() => restoreConsole());
42
-
43
- expect(consoleSpies.consoleInfoSpy).toHaveBeenCalledWith('done'); // the output is silenced, not printed
38
+ expect(consoleInfoSpy).toHaveBeenCalledWith('done'); // the output is silenced, not printed
44
39
  ```
45
40
 
41
+ It registers Vitest hooks (Bun resolves them to its own); on `node:test` and Rstest write
42
+ `installConsoleSpies()` in a `beforeEach` and `restoreConsole()` in an `afterEach` yourself.
43
+
46
44
  The exported `consoleInfoSpy` & co. are the same objects; `restoreConsole()` keeps them and clears
47
45
  their calls. **Do not rely on the import to install them**: it does so once per worker, so under
48
46
  `isolate: false` the spies go on in whichever file imported them first and silence every later file
@@ -203,7 +203,10 @@ replaced where nothing would notice it — the refusal now names `computed()` sp
203
203
  that driving the signal the computation reads is the way through. An `input()` is refused outright:
204
204
  Angular sets an input through the input node rather than the property, so a replaced one breaks the
205
205
  host's next write. Drive an input with `await setInputs(fixture, { … })`, or with
206
- `renderShallow(Component, { inputs: { … } })` for the value it starts at.
206
+ `renderShallow(Component, { inputs: { … } })` for the value it starts at. When every test of a
207
+ describe renders the same component with the same providers, `prepareShallow(Component, opts)` binds
208
+ them once and each test calls `.create({ inputs: { … } })` — the overrides replace a key outright,
209
+ one instance per test.
207
210
 
208
211
  ### Observers the component constructs itself
209
212
 
@@ -511,6 +514,8 @@ provided after it, so it wins over `withXhr()`.
511
514
  carries (default `new ProgressEvent('error')`). A module that provides a partial `DOCUMENT` double
512
515
  fails Angular's tick (`inject(...).body?.querySelector is not a function`, reported as a doubled
513
516
  `DOCUMENT`): pass `{ tick: false }` to `expectRequest` / `expectNoRequest` when the request is already out.
517
+ `provideDocumentDouble()` is not partial and needs no option. A JSON error body is a response:
518
+ `flush(body, { status: 409 })`; `{ error }` on `.error()` is a `ProgressEvent`.
514
519
  `flush()` and `error()` are `async` because settling requires letting a microtask run — `await` them,
515
520
  and the next line reads the settled value.
516
521
 
@@ -61,8 +61,9 @@ Part of the agent reference [`AGENTS.md`](../AGENTS.md), which maps every sectio
61
61
  | an assertion containing a date, with no clock set | `mockSystemTime(iso)` first |
62
62
  | `configureTestingModule` inside every `it()` | one per `describe` |
63
63
  | `vi.mock('@angular/core')` to neutralise `effect()` | set the signals, `await stable(fixture)`, assert the result |
64
- | a second `vi.spyOn(console, 'error')` | `consoleErrorSpy` from `vitest-auto-spy/console`, installed with `installConsoleSpies()` |
65
- | `import { consoleErrorSpy } from 'vitest-auto-spy/console'` as the install | `installConsoleSpies()` in `beforeEach`, `restoreConsole()` in `afterEach` — the import installs once per worker |
64
+ | a second `vi.spyOn(console, 'error')` | `consoleErrorSpy` from `vitest-auto-spy/console`, installed with `useConsoleSpies()` |
65
+ | `import { consoleErrorSpy } from 'vitest-auto-spy/console'` as the install | `useConsoleSpies()` in the `describe` (the `installConsoleSpies()` / `restoreConsole()` hook pair) — the import installs once per worker |
66
+ | `beforeEach(installConsoleSpies)` with `afterEach(restoreConsole)` repeated per spec | `useConsoleSpies()` — registers both hooks and returns the spies |
66
67
  | a bare `vi.spyOn(console, 'error')` to keep a spec quiet | `.mockImplementation(() => undefined)` — without it the line still prints |
67
68
  | `mockReadonlyProp(c, 'items', vi.fn(() => []))` | `mockReadonlyProp(c, 'items', signal([]))` — a real signal |
68
69
  | `spy.m.mockReturnValue(subject$)` for a `vi.fn(() => subject$)` | `spy.m.mockImplementation(() => subject$)` — the variable is re-read |
@@ -168,7 +168,7 @@ Part of the agent reference [`AGENTS.md`](../AGENTS.md), which maps every sectio
168
168
  | `expectRequest: no GET …?page=2 — but GET …?page=3 was made; the query differs` | the same path, another query string | name the query the code sends, or match the path alone: `expectRequest('/api/products')` (§13) |
169
169
  | `expectRequest: no request matched … — nothing was requested at all` | an `httpResource()` whose `request()` returned no URL yet, or an `HttpClient` call nothing subscribed to | set the signal the resource reads, or subscribe to the call (§13) |
170
170
  | `this TestBed has no HttpTestingController` | `expectRequest` / `expectNoRequest` on a module that never got the testing backend | `TestBed.configureTestingModule({ providers: [...provideHttpTesting()] })` from `vitest-auto-spy/angular-http` (§13) |
171
- | `TestBed.tick() failed, and this TestBed provides its own DOCUMENT` (cause: `inject(...).body?.querySelector is not a function`) | `expectRequest` / `expectNoRequest` ticked the app, and Angular's tick reads members a partial `DOCUMENT` double lacks | `expectRequest(url, { tick: false })` when the request is already out (an `HttpClient` call), or provide a real document |
171
+ | `TestBed.tick() failed, and this TestBed provides its own DOCUMENT` (cause: `inject(...).body?.querySelector is not a function`) | `expectRequest` / `expectNoRequest` ticked the app, and Angular's tick reads members a partial `DOCUMENT` double lacks | `expectRequest(url, { tick: false })` when the request is already out (an `HttpClient` call), or provide a real document — `provideDocumentDouble()` is one: it keeps every member, and an app error under it is rethrown as it is |
172
172
  | `X was never answered (end of "…")` / `N requests were never answered` | `provideHttpTesting({ verifyOnTeardown })` or `enableAngularDiagnostics({ pendingRequests })` — the test left a request open; the message names its URL and the test | answer it with the line the message prints (`await expectRequest(url).flush(body)`, `controller.expectOne(url).flush(body)` under diagnostics in a module built on `provideHttpClientTesting()`), or assert its absence (`expectNoRequest(url)`); a cancelled one is forgiven with the `ignoreCancelled` opt-in the message names (§13) |
173
173
  | `injectActivatedRoute(): nothing provides ActivatedRoute here` | no `ActivatedRoute` provider in the injector it read | `provideActivatedRoute({ … })` from `vitest-auto-spy/angular-router` in `providers`; `injectActivatedRoute(fixture.debugElement.injector)` for a component-level one (§13) |
174
174
  | `the ActivatedRoute here is … not one provideActivatedRoute() built` | a later provider of `ActivatedRoute` won — `provideRouter()`, `RouterModule`, a `useValue`, `provideAutoSpy(ActivatedRoute)` | list `provideActivatedRoute()` last, or drop the other provider (§13) |
@@ -56,6 +56,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
56
56
  | `no-unregistered-inject-spy` | `error` | — | `injectSpy(X)` for a token this file never registered → the real instance, whose spy helpers exist only for the compiler |
57
57
  | `prefer-render-shallow` | `warn` | suggest | `TestBed.createComponent` in a file that never reads the template → `renderShallow(X)`; 0.24× the per-test cycle at 100 children |
58
58
  | `prefer-set-inputs` | `warn` | suggest | a run of `fixture.componentRef.setInput('title', v)` on one fixture → `await setInputs(fixture, { title: v })` — the name is resolved against the compiled definition before the first write (an undeclared one is an `NG0303` and no change) and the value is typed. The run collapses into one call and a `detectChanges()` under it goes; offered, not applied, because `stable()` ticks and a zone.js suite answers that with `NG0101` |
59
+ | `prefer-to-have-signal-value` | `warn` | `--fix` | `expect(component.total()).toBe(3)` / `toEqual(…)` → `expect(component.total).toHaveSignalValue(3)`, `toStrictEqual` → `{ strict: true }`; the matcher names the signal in the failure and refuses a value that is not one. **Type-aware**: the signal is recognised by Angular's brand on its type, so methods, functions and getters are never reported, and without `parserOptions.project` it reports nothing |
59
60
  | `prefer-spy-on-own-method` | `warn` | `--fix` / suggest | `createSpyFromInstance(x, { onlyMethodsToSpyOn: ['m'], passthrough: true })` read for `m` alone — `.m` on the call, `const { m } = …`, a bare statement, or a name read only as `v.m` → `spyOnOwnMethod(x, 'm')`; the same whitelist with `returns: { m: undefined }` → `spyOnVoidMethod(x, 'm')`. Both fix, import and all; a `Spy<X>` annotation becomes `Spy<X>['m']` in a suggestion. A bare `returns: { m: undefined }` seed is a suggestion, offered only on a real event or element — `new MouseEvent(…)`, `document.createElement(…)`, `fixture.nativeElement` — never on a double |
60
61
  | `prefer-observer-stub` | `error` | — | a hand-rolled observer global → `stubIntersectionObserver()` / `stubResizeObserver()` / `stubMutationObserver()`; the manual save-and-restore goes too, `restoreMockedProps()` runs the undo |
61
62
  | `no-hand-assigned-global` | `error` | `--fix` | a double assigned to a global (`global.fetch = vi.fn()`, `window.matchMedia = vi.fn()`, `window.localStorage = { getItem: vi.fn() }`) with no restore in `afterEach` / `afterAll` / `onTestFinished` → `mockValueProp(globalThis, name, value)` or `vi.stubGlobal` + `unstubGlobals`; `blockNetwork()` for network globals, `stubWebStorage()` for the storages, `stubWorker({ respond })` for `Worker`; the three observers stay with `prefer-observer-stub`; any value written into an imported object (`environment.production = true`) → `mockValueProp(environment, 'production', true)`, fixed in a test or `beforeEach` |
@@ -63,7 +64,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
63
64
  | `prefer-provide-activated-route` | `error` | — | a hand-built `ActivatedRoute` — any `useValue` / `useClass` / `useFactory` / `useExisting`, and `provideAutoSpy(ActivatedRoute)` too → `provideActivatedRoute({ … })`; the double knows either the streams or the snapshot, never both, and `injectActivatedRoute().setParams(…)` moves them together mid-test |
64
65
  | `no-passthrough-console-spy` | `error` | suggest | `vi.spyOn(console, m)` nothing gives an implementation — it calls through and prints → `installConsoleSpies()` + `consoleXSpy`, or `.mockImplementation(() => undefined)` |
65
66
  | `no-console-in-spec` | `error` | — | a spec calling `console.x(…)` itself, or `console.x = …`, which nothing restores → absorb the code's output through `vitest-auto-spy/console` |
66
- | `no-import-time-console-spies` | `error` | — | an import of `vitest-auto-spy/console` in a file that never calls `installConsoleSpies()` — the import installs once per worker and silences every later file → `installConsoleSpies()` in `beforeEach`, `restoreConsole()` in `afterEach` |
67
+ | `no-import-time-console-spies` | `error` | — | an import of `vitest-auto-spy/console` in a file that never calls `installConsoleSpies()` or `useConsoleSpies()` — the import installs once per worker and silences every later file → `installConsoleSpies()` in `beforeEach`, `restoreConsole()` in `afterEach` |
67
68
  | `no-mistyped-use-value` | `error` | — | `{ provide: TOKEN, useValue }` whose value is not assignable to the primitive `T` of `InjectionToken<T>` (string, number, boolean, bigint, enum, their literals, `null`, `undefined`) — `useValue` is `any`, so `{}` for a `boolean` token compiles and is truthy. **Type-aware**: silent without `parserOptions.project`; object-typed tokens are `no-unknown-use-value-key`'s |
68
69
  | `no-unknown-use-value-key` | `error` | — | a key of an object `useValue` literal the provided type does not have — `T` of `InjectionToken<T>`, or the instance type of a `provide:` class; `{ provide: ActivatedRoute, useValue: { queryParams$: … } }` compiles, since `useValue` is `any`. Keys only, never assignability (a partial fixture is fine). **Type-aware**; silent on `any` / `unknown` / `object`, an index signature, a spread's keys, `multi: true` |
69
70
  | `no-instance-lifecycle-spy` | `warn` | — | `vi.spyOn(instance, 'ngOnInit')` (and `ngOnDestroy`, `ngDoCheck`, `ngAfterContent*`, `ngAfterView*`) — a view calls the hook read off the prototype, so the instance spy is never called and its stub never runs → `vi.spyOn(Cls.prototype, …)` before `createComponent`, or assert the effect. `ngOnChanges` is exempt: Angular calls it through the instance |
@@ -84,14 +85,14 @@ blanket downgrade so those keep their severity; do not copy the two names into a
84
85
  | `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()` — a no-op here, so the spec silently asserts on post-mutation state |
85
86
  | `prefer-native-spy-api` | `error` | `--fix` / suggest | `.and` / `.calls` where the spy's own API says the same thing — turn it on for the last mile off the jasmine shim |
86
87
 
87
- Forty-nine rules, **every one an `error` since 4.0.0 except `prefer-render-shallow`,
88
+ Fifty rules, **every one an `error` since 4.0.0 except `prefer-render-shallow`,
88
89
  `no-stub-class-double`, `no-structural-double`, `prefer-create-mock`, `no-instance-lifecycle-spy`,
89
- `prefer-set-inputs`, `no-unasserted-argument` and `prefer-spy-on-own-method`**; six fix on their own, nineteen offer suggestions. Forty-six are syntactic; `no-private-member-access`, `no-mistyped-use-value` and
90
- `no-unknown-use-value-key` read types, and all three report nothing at all without `parserOptions.project` / `projectService`
90
+ `prefer-set-inputs`, `no-unasserted-argument`, `prefer-spy-on-own-method` and `prefer-to-have-signal-value`**; eight fix on their own, nineteen offer suggestions. Forty-six are syntactic; `no-private-member-access`, `no-mistyped-use-value`,
91
+ `no-unknown-use-value-key` and `prefer-to-have-signal-value` read types, and all four report nothing at all without `parserOptions.project` / `projectService`
91
92
  rather than guessing. `no-compile-components` waits the same way for a fact no file holds — which
92
93
  builder the project has — and reports nothing until `{ builder: 'inline-resources' }` states it. The config used to be a graded mix of `error` / `warn` / `off`, which decided for the
93
94
  consumer how much each finding mattered — a `warn` nothing reads is `off` with extra output. The
94
- eight `warn`s left are not judgements about how much a finding matters. `prefer-render-shallow` is
95
+ nine `warn`s left are not judgements about how much a finding matters. `prefer-render-shallow` is
95
96
  about the _kind_ of finding: every other rule names something wrong or dead, while this one names a
96
97
  file that could render more cheaply, and `renderShallow` is a migration a suite either takes or does
97
98
  not. At `error` the plugin would gate that migration — 491 findings across 398 of one consumer's
@@ -94,7 +94,11 @@ nothing. Fix it where the stream is subscribed — an error callback or `catchEr
94
94
  error. `flushUnhandledObservableErrors()` is the same check by hand: it returns `{ error, test }` (or
95
95
  `{ error, outsideTest }`) for each rethrow that threw; assert the errors alone with
96
96
  `expect(flushUnhandledObservableErrors()).toMatchObject([{ error: new Error('502') }])` — it checks the
97
- count and ignores `test`, with no `.map(({ error }) => error)`.
97
+ count and ignores `test`, with no `.map(({ error }) => error)`. A test that deliberately leaves the
98
+ error unhandled asserts it in place instead: `expectUnhandledObservableErrors([{ message: /502/ }])`
99
+ flushes and compares — each entry the error itself, its class, or a message (a string or a pattern),
100
+ in order — and fails with a diff listing what was expected and what was found; no argument asserts
101
+ nothing was left.
98
102
 
99
103
  **`countStrayTimers()` cannot see a timer scheduled under fake timers, and that does not compose
100
104
  away.** `vi.useFakeTimers()` assigns its own `setTimeout` over the tracking wrapper, so everything
@@ -722,7 +726,7 @@ frame outside `node_modules`, and naming the spy that absorbs it:
722
726
  [vitest-auto-spy] "CartService > reports a failed load" wrote to console.error 1 time and nothing absorbed it:
723
727
  - console.error: Error: load failed
724
728
  at CartService.load (src/app/cart.service.ts:41:15)
725
- Absorb what the test expects — installConsoleSpies() in a beforeEach, then assert consoleErrorSpy — or fix the code if the output is a defect.
729
+ Absorb what the test expects — useConsoleSpies() in the describe, then assert consoleErrorSpy — or fix the code if the output is a defect.
726
730
  Docs: https://asdalexey.github.io/vitest-auto-spy/utilities/setup#_16-console-output-nothing-absorbed
727
731
  ```
728
732
 
@@ -748,7 +752,7 @@ Docs: https://asdalexey.github.io/vitest-auto-spy/utilities/setup#_16-console-ou
748
752
  `It is reported at the end of b.spec.ts: the file-end check of a.spec.ts did not run.`
749
753
  - Every console method a test replaced is put back after it; one a file replaced, after the file.
750
754
  - Under the guard, **importing `vitest-auto-spy/console` installs nothing** — call
751
- `installConsoleSpies()` in a `beforeEach` (or at the top of the file). Spies an import installed
755
+ `useConsoleSpies()` in the `describe` (or `installConsoleSpies()` at the top of the file). Spies an import installed
752
756
  before the guard armed are taken off.
753
757
  - `strayConsole: { allow: ['…', /…/] }` is the last resort, for a line from a dependency no spec can
754
758
  reach — the import-time report offers it, with the pattern, only when the quoted frame is inside
@@ -97,7 +97,7 @@ declare function provideHttpTesting(options?: HttpTestingOptions): (EnvironmentP
97
97
  * that was never sent rather than one that was sent wrongly.
98
98
  *
99
99
  * @param matcher The URL, a pattern for it, or a predicate over the request.
100
- * @param options `{ method }`, for a URL that is both read and written in the same test.
100
+ * @param options `{ method }`, for a URL that is both read and written in the same test; `{ tick: false }` for a doubled `DOCUMENT`.
101
101
  */
102
102
  declare function expectRequest(matcher: RequestMatcher, options?: ExpectRequestOptions): RequestExpectation;
103
103
  /**
@@ -175,11 +175,22 @@ async function settle(tick) {
175
175
  }
176
176
  }
177
177
  }
178
+ function isPartialDocument(doc) {
179
+ const real = globalThis.document;
180
+ if (doc === real) {
181
+ return false;
182
+ }
183
+ if (Reflect.getPrototypeOf(Object(doc)) !== Reflect.getPrototypeOf(Object(real))) {
184
+ return true;
185
+ }
186
+ const body = Reflect.get(Object(doc), "body");
187
+ return typeof Reflect.get(Object(body), "querySelector") !== "function";
188
+ }
178
189
  function tickApp(caller) {
179
190
  try {
180
191
  flushEffects();
181
192
  } catch (error) {
182
- if (TestBed.inject(DOCUMENT) === globalThis.document) {
193
+ if (!isPartialDocument(TestBed.inject(DOCUMENT))) {
183
194
  throw error;
184
195
  }
185
196
  throw new Error(
@@ -89,6 +89,11 @@ interface SignalValueOptions {
89
89
  /** Count `undefined` properties, array holes and the object's class, as `toStrictEqual` does. */
90
90
  readonly strict?: boolean;
91
91
  }
92
+ /** Suite-wide defaults for the matchers {@link registerSignalMatchers} registers. */
93
+ interface RegisterSignalMatchersOptions {
94
+ /** Compare every `toHaveSignalValue` like `toStrictEqual` does, unless an assertion opts out with `{ strict: false }`. */
95
+ readonly strict?: boolean;
96
+ }
92
97
  /**
93
98
  * Register {@link toHaveSignalValue} with the runner. Call once, from your setup file.
94
99
  *
@@ -98,7 +103,18 @@ interface SignalValueOptions {
98
103
  *
99
104
  * expect(component.total).toHaveSignalValue(3);
100
105
  * ```
106
+ *
107
+ * A suite that wants the `toStrictEqual` semantics everywhere passes it once instead of on every
108
+ * assertion; a single assertion can still opt back out.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * registerSignalMatchers({ strict: true });
113
+ *
114
+ * expect(component.items).toHaveSignalValue(items); // compares strictly
115
+ * expect(component.draft).toHaveSignalValue({}, { strict: false });
116
+ * ```
101
117
  */
102
- declare function registerSignalMatchers(): void;
118
+ declare function registerSignalMatchers(options?: RegisterSignalMatchersOptions): void;
103
119
 
104
- export { type ResourceLike, type SignalLike, type SignalValueOptions, registerDirectiveMatchers, registerResourceMatchers, registerSignalMatchers };
120
+ export { type RegisterSignalMatchersOptions, type ResourceLike, type SignalLike, type SignalValueOptions, registerDirectiveMatchers, registerResourceMatchers, registerSignalMatchers };
@@ -244,9 +244,10 @@ function registerResourceMatchers() {
244
244
  function isSpy(received) {
245
245
  return "mock" in received || "calls" in received;
246
246
  }
247
- function registerSignalMatchers() {
247
+ function registerSignalMatchers(options = {}) {
248
+ const strictByDefault = options.strict === true;
248
249
  expect.extend({
249
- toHaveSignalValue(received, expected, { strict = false } = {}) {
250
+ toHaveSignalValue(received, expected, { strict = strictByDefault } = {}) {
250
251
  if (typeof received !== "function") {
251
252
  throw new Error(`expected a signal (a zero-argument getter), received ${this.utils.printReceived(received)}`);
252
253
  }
package/dist/angular.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { u as AngularTokenProvider, A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, E as ElementConstructor, N as NativeElementHolder, R as RenderShallowOptions, c as ResourceStatusLike, d as RunCounter, S as SettleResourceOptions, e as ShallowRender, f as SpyRegistry, g as StableOptions, h as createWithAutoSpies, i as flushEffects, j as hostElement, k as injectSpy, p as provideAutoSpy, v as provideAutoSpyForToken, q as queryElement, r as renderShallow, l as runEffect, s as setInputs, m as settleResource, n as stable, t as trackEffectRuns, o as trackRecomputations } from './track-signal-runs-CyPA-p4I.js';
1
+ export { v as AngularTokenProvider, A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, E as ElementConstructor, N as NativeElementHolder, P as PreparedShallow, R as RenderShallowOptions, c as ResourceStatusLike, d as RunCounter, S as SettleResourceOptions, e as ShallowRender, f as SpyRegistry, g as StableOptions, h as createWithAutoSpies, i as flushEffects, j as hostElement, k as injectSpy, p as prepareShallow, l as provideAutoSpy, w as provideAutoSpyForToken, q as queryElement, r as renderShallow, m as runEffect, s as setInputs, n as settleResource, o as stable, t as trackEffectRuns, u as trackRecomputations } from './track-signal-runs-CUdp5dJZ.js';
2
2
  import { o as DeepPartial, C as ClassType, m as ClassSpyConfiguration, S as Spy, s as OnlyMethodKeysOf, h as AddSpyMethodsByReturnTypes, r as NotAPublicKey } from './types-Dxw-oWfr.js';
3
3
  export { H as SubjectOf } from './types-Dxw-oWfr.js';
4
4
  import { InjectionToken, Provider, Type, WritableSignal, Signal } from '@angular/core';
package/dist/angular.js CHANGED
@@ -1006,6 +1006,13 @@ function renderShallow(component, options = {}) {
1006
1006
  }
1007
1007
  return { fixture, component: fixture.componentInstance };
1008
1008
  }
1009
+ function prepareShallow(component, options = {}) {
1010
+ return {
1011
+ create(overrides = {}) {
1012
+ return renderShallow(component, { ...options, ...overrides });
1013
+ }
1014
+ };
1015
+ }
1009
1016
  function readReactiveNode(candidate) {
1010
1017
  if (typeof candidate !== "object" && typeof candidate !== "function" || candidate === null) {
1011
1018
  return void 0;
@@ -1406,4 +1413,4 @@ function mockSignalProp(object, property, initialValue) {
1406
1413
  // src/angular.ts
1407
1414
  _use_vitest_adapter$useVitestAdapter();
1408
1415
 
1409
- export { assertComponentDefIntact, assertNgModuleScopes, clearAutoSpyDefaults2 as clearAutoSpyDefaults, createComponentStub, createDirectiveHost, createWithAutoSpies, extendWithAutoSpies, flushEffects, hostElement, injectSpy, mockResourceProp, mockSignalProp, overrideAutoSpy, overrideComponentProvider, provideAutoSpy, provideAutoSpyForToken, queryElement, registerAutoSpyDefaults2 as registerAutoSpyDefaults, renderShallow, runEffect, setInputs, settleResource, setupAngularTestEnv, stable, trackEffectRuns, trackInjections, trackRecomputations };
1416
+ export { assertComponentDefIntact, assertNgModuleScopes, clearAutoSpyDefaults2 as clearAutoSpyDefaults, createComponentStub, createDirectiveHost, createWithAutoSpies, extendWithAutoSpies, flushEffects, hostElement, injectSpy, mockResourceProp, mockSignalProp, overrideAutoSpy, overrideComponentProvider, prepareShallow, provideAutoSpy, provideAutoSpyForToken, queryElement, registerAutoSpyDefaults2 as registerAutoSpyDefaults, renderShallow, runEffect, setInputs, settleResource, setupAngularTestEnv, stable, trackEffectRuns, trackInjections, trackRecomputations };
@@ -3,7 +3,7 @@ export { AdoptMockOptions, AdoptedMock, ArgCaptor, AsInstances, AssertMockedOpti
3
3
  export { A as AutoMockConfiguration, a as AutoMocked, b as AutoSpyDefaultEntry, C as CallbackSubscribable, E as EmissionObserver, c as EmissionOptions, d as EmissionSource, S as SubscribableLike, e as autoMocked, f as clearAutoSpyDefaults, g as createAutoMock, h as expectAllEmissions, i as expectCompletion, j as expectEmission, k as expectEmissions, l as expectError, m as expectNoEmission, r as registerAutoSpyDefaults, s as setEmissionTimeout } from './expect-emission-DMQZF5QR.js';
4
4
  export { c as createFunctionSpy, d as describeDuplicateCopies, g as getPackageCopies } from './package-identity-5kYzh3ad.js';
5
5
  export { A as AccessorImplementations, O as OutsideHookReaction, R as RestoreProp, c as countMockedProps, m as mockAccessorsProp, a as mockReadonlyProp, b as mockReadonlyPropGetter, d as mockValueProp, r as reportPropsOutsideHooks, e as restoreMockedProps } from './prop-mock-WOpRQPUc.js';
6
- export { A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, E as ElementConstructor, N as NativeElementHolder, R as RenderShallowOptions, c as ResourceStatusLike, d as RunCounter, S as SettleResourceOptions, e as ShallowRender, f as SpyRegistry, g as StableOptions, h as createWithAutoSpies, i as flushEffects, j as hostElement, k as injectSpy, p as provideAutoSpy, q as queryElement, r as renderShallow, l as runEffect, s as setInputs, m as settleResource, n as stable, t as trackEffectRuns, o as trackRecomputations } from './track-signal-runs-CyPA-p4I.js';
6
+ export { A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, E as ElementConstructor, N as NativeElementHolder, P as PreparedShallow, R as RenderShallowOptions, c as ResourceStatusLike, d as RunCounter, S as SettleResourceOptions, e as ShallowRender, f as SpyRegistry, g as StableOptions, h as createWithAutoSpies, i as flushEffects, j as hostElement, k as injectSpy, p as prepareShallow, l as provideAutoSpy, q as queryElement, r as renderShallow, m as runEffect, s as setInputs, n as settleResource, o as stable, t as trackEffectRuns, u as trackRecomputations } from './track-signal-runs-CUdp5dJZ.js';
7
7
  import 'vitest';
8
8
  import '@angular/core';
9
9
  import '@angular/core/testing';
@@ -570,6 +570,13 @@ function renderShallow(component, options = {}) {
570
570
  }
571
571
  return { fixture, component: fixture.componentInstance };
572
572
  }
573
+ function prepareShallow(component, options = {}) {
574
+ return {
575
+ create(overrides = {}) {
576
+ return renderShallow(component, { ...options, ...overrides });
577
+ }
578
+ };
579
+ }
573
580
  function readReactiveNode(candidate) {
574
581
  if (typeof candidate !== "object" && typeof candidate !== "function" || candidate === null) {
575
582
  return void 0;
@@ -775,4 +782,4 @@ afterEach(() => {
775
782
  await domReady;
776
783
  initTestEnvironment();
777
784
 
778
- export { copyWindowGlobals, createGlobalRegistratorRegistrar, createJsdomRegistrar, createWithAutoSpies, hostElement, inlineAngularResources, queryElement, registerDomGlobals, renderShallow, runEffect, setInputs, settleResource, trackEffectRuns, trackRecomputations };
785
+ export { copyWindowGlobals, createGlobalRegistratorRegistrar, createJsdomRegistrar, createWithAutoSpies, hostElement, inlineAngularResources, prepareShallow, queryElement, registerDomGlobals, renderShallow, runEffect, setInputs, settleResource, trackEffectRuns, trackRecomputations };