vitest-auto-spy 5.63.0 → 5.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/AGENTS.md +12 -0
  2. package/CHANGELOG.md +42 -1
  3. package/README.md +65 -3
  4. package/agent-docs/adapters.md +9 -0
  5. package/agent-docs/doubles.md +102 -2
  6. package/agent-docs/observables.md +37 -0
  7. package/dist/angular-diagnostics.js +11 -10
  8. package/dist/angular-doubles.d.ts +30 -2
  9. package/dist/angular-doubles.js +318 -14
  10. package/dist/angular-http.js +5 -4
  11. package/dist/angular-matchers.js +1 -1
  12. package/dist/angular-router.js +6 -6
  13. package/dist/angular.d.ts +3 -3
  14. package/dist/angular.js +1 -1
  15. package/dist/bun-angular.d.ts +2 -2
  16. package/dist/bun-angular.js +19 -19
  17. package/dist/bun.d.ts +1 -1
  18. package/dist/bun.js +11 -11
  19. package/dist/{chunk-TXC3L3FO.js → chunk-2CWSOIBW.js} +1 -1
  20. package/dist/{chunk-RQLUH6RE.js → chunk-5GKIQX65.js} +3 -3
  21. package/dist/{chunk-6KS7F4YM.js → chunk-7GOVSGXB.js} +2 -2
  22. package/dist/{chunk-IATDAQMO.js → chunk-7WU53KRS.js} +4 -1
  23. package/dist/{chunk-VLEU3CWT.js → chunk-A2X7O5EP.js} +2 -2
  24. package/dist/{chunk-JCTHGJTY.js → chunk-C7K75NLS.js} +1 -31
  25. package/dist/{chunk-RHGDQQNC.js → chunk-DCCURN7I.js} +1 -1
  26. package/dist/{chunk-MXKNUVYR.js → chunk-DWMIQ7VO.js} +1 -1
  27. package/dist/{chunk-75WPOZKE.js → chunk-EZKDQ4WO.js} +1 -1
  28. package/dist/{chunk-ZTT2QLMJ.js → chunk-EZTP5VUF.js} +4 -4
  29. package/dist/{chunk-UH2Q7RTQ.js → chunk-HIGHZ4BW.js} +2 -2
  30. package/dist/{chunk-JFROAGGY.js → chunk-IXARK3B6.js} +2 -2
  31. package/dist/{chunk-EFUZ44HT.js → chunk-KCQOZSNA.js} +75 -7
  32. package/dist/{chunk-XZ5JUXKT.js → chunk-NMXLXBXH.js} +2 -2
  33. package/dist/chunk-OIHOH2RY.js +34 -0
  34. package/dist/{chunk-OWZPZBJI.js → chunk-OXE6FCQX.js} +4 -4
  35. package/dist/{chunk-3OEYMUV3.js → chunk-PS6UQC4C.js} +1 -1
  36. package/dist/{chunk-F7UPX6YV.js → chunk-QATP6Q3G.js} +1 -1
  37. package/dist/{chunk-R43PMWFH.js → chunk-TRRI7TXE.js} +1 -1
  38. package/dist/{chunk-BCT7C6DQ.js → chunk-XLKBJSYQ.js} +3 -3
  39. package/dist/{chunk-4JY7KIJG.js → chunk-XZS56XV5.js} +1 -1
  40. package/dist/cli.js +20 -1
  41. package/dist/console.js +8 -8
  42. package/dist/core.d.ts +1 -1
  43. package/dist/dom-stubs.d.ts +41 -87
  44. package/dist/dom-stubs.js +76 -8
  45. package/dist/element-stub-D3kchFNq.d.ts +97 -0
  46. package/dist/{emission-sync-ByzgYsbj.d.ts → emission-positive-sync-BrSc2lb-.d.ts} +20 -1
  47. package/dist/index.d.ts +1 -1
  48. package/dist/index.js +70 -2
  49. package/dist/jasmine-compat.js +2 -2
  50. package/dist/jasmine.js +12 -12
  51. package/dist/nestjs.d.ts +65 -1
  52. package/dist/nestjs.js +93 -12
  53. package/dist/node.cjs +70 -0
  54. package/dist/node.d.cts +2 -0
  55. package/dist/node.d.ts +1 -1
  56. package/dist/node.js +69 -1
  57. package/dist/react.d.ts +1 -1
  58. package/dist/react.js +1 -1
  59. package/dist/rstest.d.ts +1 -1
  60. package/dist/rstest.js +10 -10
  61. package/dist/rxjs.js +2 -2
  62. package/dist/signal-forms.js +1 -1
  63. package/dist/svelte.d.ts +1 -1
  64. package/dist/svelte.js +1 -1
  65. package/dist/{track-signal-runs-D1Vr9BdG.d.ts → track-signal-runs-DLvVp9iI.d.ts} +1 -1
  66. package/dist/vue.d.ts +1 -1
  67. package/dist/vue.js +1 -1
  68. package/dist/zone.js +1 -1
  69. package/package.json +1 -1
  70. package/skills/vitest-auto-spy/SKILL.md +12 -1
package/AGENTS.md CHANGED
@@ -154,6 +154,11 @@ In [`agent-docs/factories.md`](./agent-docs/factories.md). Read it before writin
154
154
 
155
155
  ## 3. The 90% recipe
156
156
 
157
+ Direct handler tests: `/dom-stubs` supplies `createDomEvent`, `createInputEvent`, `createPasteEvent`
158
+ and `createDropEvent`. `/angular/doubles` supplies `mockElementRefProp` for an element signal query
159
+ (§12). For synchronous outputs, `expectEmissionSync` / `expectEmissionsSync` from core or `/angular`
160
+ check exact counts around `advance` and unsubscribe (§8). Async work needs the awaited helpers.
161
+
157
162
  In an Angular app the spy almost always arrives through DI: `provideAutoSpy` in the providers,
158
163
  `injectSpy` to read it. Write that shape first.
159
164
 
@@ -387,6 +392,7 @@ In [`agent-docs/observables.md`](./agent-docs/observables.md). Read it when asse
387
392
 
388
393
  <!-- agents-outline: agent-docs/observables.md -->
389
394
 
395
+ - Synchronous outputs without subscriptions
390
396
  - `createLog()` — the order between the spies
391
397
 
392
398
  <!-- /agents-outline -->
@@ -448,6 +454,9 @@ In [`agent-docs/doubles.md`](./agent-docs/doubles.md). Read it when production c
448
454
  - `localStorage` and `sessionStorage`
449
455
  - Animation frames and element boxes
450
456
  - An element for `ElementRef` — `createElementStub`
457
+ - `mockElementRefProp` — an element for a signal query
458
+ - Events for direct handler calls
459
+ - Text paste and drop events
451
460
  - The members the DOM environment leaves out — `fillMissingDomApis`
452
461
  - A module mock that did nothing
453
462
 
@@ -527,6 +536,9 @@ file, and `globals: true`.
527
536
 
528
537
  In [`agent-docs/adapters.md`](./agent-docs/adapters.md). Read it for NestJS, Vue, React, Svelte, `bun test`, `node:test`, Rstest and the console entry.
529
538
 
539
+ Nest HTTP tests: `createHttpExecutionContext` supplies the context; `invokeNestParamDecorator`
540
+ calls a custom parameter factory by index with its recorded data. Both come from `/nestjs` (§15).
541
+
530
542
  <!-- agents-outline: agent-docs/adapters.md -->
531
543
 
532
544
  - `trackNodeMocks` for `node:test` memory
package/CHANGELOG.md CHANGED
@@ -10,6 +10,44 @@ The latest released version here must always match the one published on
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [5.66.0] - 2026-10-28
14
+
15
+ ### Added
16
+
17
+ - `/nestjs`: `createHttpExecutionContext` supplies an HTTP context for guards, interceptors,
18
+ exception filters and parameter decorators. Request, response and argument accessors share the
19
+ same values; missing handler/class metadata and non-HTTP transports produce explicit errors.
20
+ - `/nestjs`: `invokeNestParamDecorator` runs a custom parameter decorator's recorded factory by
21
+ parameter index and forwards its stored data. It refuses missing or ambiguous factories and
22
+ parameter pipes or schemas that need Nest's request pipeline.
23
+ - **Bundle size:** `/nestjs` grows from 20.06 kB to 20.85 kB min+gzip (+0.79 kB, +3.9%) for
24
+ the HTTP context and parameter metadata helpers, without importing NestJS or Vitest. The root
25
+ entry does not re-export these helpers.
26
+
27
+ ## [5.65.0] - 2026-10-26
28
+
29
+ ## [5.64.0] - 2026-10-23
30
+
31
+ ### Added
32
+
33
+ - `createElementStub({ elementClass: HTMLInputElement })` in `/dom-stubs` infers the element type
34
+ and supports `instanceof` checks without creating a DOM element. Missing members of the selected
35
+ class keep the stub's named error; supply their implementation in `overrides`.
36
+ - `/dom-stubs`: `createDomEvent` supplies readonly targets for direct handler calls;
37
+ `createInputEvent` supplies a typed input target and value; `createPasteEvent` and `createDropEvent`
38
+ supply text transfers and native cancellation without browser transfer constructors.
39
+ - `/angular/doubles`: `mockElementRefProp(component, 'trigger')` installs an inferred element
40
+ stub on an `ElementRef` signal query and returns the element spies, reference and writable query.
41
+ These DOM/query helpers add code only to their narrow entries.
42
+ - The root entry and `/angular`: `expectEmissionSync` and `expectEmissionsSync` replace manual
43
+ subscriptions and arrays with exact synchronous emission counts. They keep listening during
44
+ `advance` to catch extra emissions and tear down before returning or throwing.
45
+ - **Bundle size:** `/dom-stubs` grows by 491 B min+gzip over 5.63.0 for element prototypes and
46
+ handler events; `/angular/doubles` grows by 3.77 kB for the typed query/stub composition and its
47
+ existing signal-patching machinery. Shared bundle modules also change the import graph of
48
+ `/angular/diagnostics`, `/angular/matchers` and `/angular-http`; the recorded graph and sizes
49
+ include this reuse.
50
+
13
51
  ## [5.63.0] - 2026-10-21
14
52
 
15
53
  ### Added
@@ -6864,7 +6902,10 @@ moved under 1 %: `/angular` +192 B (+1.0 %) for the override queue and the HTTP
6864
6902
  This copy ships with the package and carries 5.x only. Releases before 5.0.0 are in the
6865
6903
  [full changelog](https://github.com/ASDAlexey/vitest-auto-spy/blob/master/CHANGELOG.md).
6866
6904
 
6867
- [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.63.0...HEAD
6905
+ [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.66.0...HEAD
6906
+ [5.66.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.65.0...v5.66.0
6907
+ [5.65.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.64.0...v5.65.0
6908
+ [5.64.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.63.0...v5.64.0
6868
6909
  [5.63.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.62.0...v5.63.0
6869
6910
  [5.62.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.61.0...v5.62.0
6870
6911
  [5.61.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.60.0...v5.61.0
package/README.md CHANGED
@@ -15,7 +15,7 @@ replacement for [`jest-auto-spies`](https://www.npmjs.com/package/jest-auto-spie
15
15
  [![downloads per month](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.npmjs.org%2Fdownloads%2Fpoint%2Flast-month%2Fvitest-auto-spy&query=%24.downloads&color=brightgreen&logo=npm&label=downloads%2Fmonth)](https://www.npmjs.com/package/vitest-auto-spy)
16
16
  [![downloads over 18 months](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.npmjs.org%2Fdownloads%2Fpoint%2F2026-06-21%3A2030-01-01%2Fvitest-auto-spy&query=%24.downloads&color=brightgreen&logo=npm&label=downloads%2F18mo)](https://www.npmjs.com/package/vitest-auto-spy)
17
17
  [![CI](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml/badge.svg)](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
18
- [![minzipped size](https://img.shields.io/badge/minzip-30.4%20kB-brightgreen)](#install)
18
+ [![minzipped size](https://img.shields.io/badge/minzip-30.7%20kB-brightgreen)](#install)
19
19
  [![types](https://img.shields.io/npm/types/vitest-auto-spy?logo=typescript&logoColor=white)](https://www.npmjs.com/package/vitest-auto-spy)
20
20
  [![coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
21
21
  [![license](https://img.shields.io/npm/l/vitest-auto-spy?color=blue)](./LICENSE)
@@ -46,6 +46,9 @@ replacement for [`jest-auto-spies`](https://www.npmjs.com/package/jest-auto-spie
46
46
  - **Answers per argument list**: `calledWith(1).mockReturnValue(...)`.
47
47
  - **A spy from a type alone**: `createAutoMock<T>()` and `mockDeep<T>()`.
48
48
  - **Angular `TestBed` in one line**: `provideAutoSpy(ApiService)`, then `injectSpy(ApiService)`.
49
+ - **Short handler tests**: typed input/paste/drop events, element query stubs, and exact synchronous output assertions.
50
+ - **Nest HTTP tests without starting the app**: `createHttpExecutionContext` supplies the context;
51
+ `invokeNestParamDecorator` runs a custom parameter decorator with its recorded data.
49
52
  - **One API on every runner**: Vitest, Bun, `node:test` and Rstest; helpers for Angular, NestJS,
50
53
  React, Vue / Pinia and Svelte.
51
54
  - **Checks that make silent tests fail**: strict mode, Observable assertions, cleanup between tests,
@@ -2932,6 +2935,25 @@ Pass `{ expose: [Dependency] }` to keep a collaborator real, and `providers` to
2932
2935
  `vitest`. On `node --test`, Bun or Rstest, import the runtime entry first. `createNestUnit` is also
2933
2936
  exported from `vitest-auto-spy/node` and `vitest-auto-spy/bun`.
2934
2937
 
2938
+ Test a custom parameter decorator without starting Nest or reading its metadata by hand.
2939
+ Here `ProfileController.profile` has `@CurrentUser('id')` on its first parameter:
2940
+
2941
+ ```ts
2942
+ import 'reflect-metadata';
2943
+
2944
+ import { expect } from 'vitest';
2945
+ import { createHttpExecutionContext, invokeNestParamDecorator } from 'vitest-auto-spy/nestjs';
2946
+
2947
+ import { ProfileController } from './profile.controller';
2948
+
2949
+ const context = createHttpExecutionContext({ request: { user: { id: 'user-7' } } });
2950
+ expect(invokeNestParamDecorator(ProfileController, 'profile', 0, context)).toBe('user-7');
2951
+ ```
2952
+
2953
+ The parameter index starts at zero. The helper runs the recorded factory with its decorator data;
2954
+ pipes and schemas require Nest's validation pipeline and are refused.
2955
+ Use the same context helper for guards, interceptors and exception filters.
2956
+
2935
2957
  Full page: [NestJS](https://asdalexey.github.io/vitest-auto-spy/adapters/nestjs).
2936
2958
 
2937
2959
  #### `httpResource()` and `HttpClient` — `vitest-auto-spy/angular-http`
@@ -3082,8 +3104,29 @@ two thousand tests add about 0.02 s to the run. Numbers:
3082
3104
  Besides the spy factories, the package has small standalone helpers. Each does one job; pick up
3083
3105
  only the ones you need. Every helper, with its signature: [API](https://asdalexey.github.io/vitest-auto-spy/api).
3084
3106
 
3107
+ For direct handler calls, replace local event factories and query setup with these helpers:
3108
+
3109
+ ```ts
3110
+ import { mockElementRefProp } from 'vitest-auto-spy/angular/doubles';
3111
+ import { createInputEvent } from 'vitest-auto-spy/dom-stubs';
3112
+
3113
+ const trigger = mockElementRefProp(component, 'triggerButton');
3114
+ component.store.open(0, 0);
3115
+ component.onMinutesChange(createInputEvent('15'));
3116
+ component.confirm();
3117
+ expect(trigger.focus).toHaveBeenCalledOnce();
3118
+ ```
3119
+
3120
+ `mockElementRefProp` infers the element type from the signal query. Set `elementClass` for runtime
3121
+ `instanceof` checks. Patch before a live consumer reads the query; `setupAutoSpy()` restores replaced
3122
+ properties. Events with supplied targets are for direct handler calls. Paste/drop support text reads
3123
+ only. Full contracts: [element and event stubs](https://asdalexey.github.io/vitest-auto-spy/utilities/element-stub).
3124
+
3085
3125
  | Utility | Entry point | What it's for |
3086
3126
  | ----------------------------------------------------------------------------------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
3127
+ | `createDomEvent(type, init?)` / `createInputEvent(value, init?)` | `/dom-stubs` | Native events for direct handlers, with readonly targets or an input value |
3128
+ | `createPasteEvent(text, init?)` / `createDropEvent(text, init?)` | `/dom-stubs` | Text transfers with native `defaultPrevented`; `null` represents absent transfer data |
3129
+ | `mockElementRefProp(component, key, opts?)` | `/angular/doubles` | Inferred element spies, `ref` and writable `query`, installed together |
3087
3130
  | `injectSpy(token)` / `injectSpy(moduleRef, token)` | `/angular`, `/nestjs` | Pull a provided spy out of the DI container, already typed as `Spy<T>` — no casting |
3088
3131
  | `provideAutoSpy(Class, config?)` | `/angular`, `/nestjs`, `/vue` | One-liner `{ provide, useValue }` (or Vue `global.provide`) that builds the spy for you |
3089
3132
  | `createFunctionSpy(name)` | core | A single standalone function spy with the full helper set (`calledWith`, `resolveWith`, `nextWith`, …) — no class needed |
@@ -3112,7 +3155,7 @@ only the ones you need. Every helper, with its signature: [API](https://asdalexe
3112
3155
  | `stubWorker(opts?)` | `/dom-stubs` | A `Worker` whose script is the spec — listeners stack, replies arrive on a microtask, `emit()` / `fail()` from the worker's side ([details](#how-to-mock-a-web-worker)) |
3113
3156
  | `stubAnimationFrame(opts?)` | `/dom-stubs` | `requestAnimationFrame` / `cancelAnimationFrame` run on the spot or on `flush(timestamp?)`; a frame requested inside a frame waits for the next flush; `onError` intercepts a throwing callback; `lastHandle` is the latest handle issued (they start above 2^30) |
3114
3157
  | `stubElementRect(el, rect?)` | `/dom-stubs` | `getBoundingClientRect()` answering a real `DOMRect` built from a partial `DOMRectInit`; returns the undo, plus the installed spy as `.getBoundingClientRect` |
3115
- | `createElementStub(opts?)` | `/dom-stubs` | A typed `HTMLElement` for `ElementRef` — `classList`, `style`, attributes and listeners on spies that keep state; a member it does not implement throws by name |
3158
+ | `createElementStub(opts?)` | `/dom-stubs` | A typed element for `ElementRef`; `elementClass` enables `instanceof` checks and infers the type. Spies keep state; unconfigured DOM members require `overrides` |
3116
3159
  | `fillMissingDomApis(opts?)` | `/dom-stubs` | For the setup file: `PointerEvent`, a no-op `ResizeObserver`, no-op scrolling and `document.doctype`, only where missing; `{ cheapComputedStyle: true }` |
3117
3160
  | `flushEventLoop(turns?)` / `settleDynamicImport(load, turns?)` | core | Real event-loop turns while the timers are faked — for a dynamic `import()` or native `async` in a dependency |
3118
3161
  | `flushEventLoopUntil(isDone, opts?)` | core | Real turns until a condition holds — a `resource()` leaving `loading` — with a budget instead of a hang |
@@ -3332,6 +3375,25 @@ Full page: [explainSpy](https://asdalexey.github.io/vitest-auto-spy/utilities/ex
3332
3375
 
3333
3376
  ## Observable assertions
3334
3377
 
3378
+ For a synchronous handler, check the exact output count without managing a subscription:
3379
+
3380
+ ```ts
3381
+ import { expectEmissionSync, expectEmissionsSync } from 'vitest-auto-spy';
3382
+
3383
+ expect(expectEmissionSync(component.page, { advance: () => component.next() })).toBe(3);
3384
+ expect(
3385
+ expectEmissionsSync(component.page, 2, {
3386
+ advance: () => {
3387
+ component.next();
3388
+ component.next();
3389
+ },
3390
+ }),
3391
+ ).toEqual([4, 5]);
3392
+ ```
3393
+
3394
+ Both helpers unsubscribe before returning and fail on missing or extra synchronous values.
3395
+ For timers, promises or other asynchronous work, use the awaited helpers below.
3396
+
3335
3397
  These helpers make the assertion an `await`, so a stream that never emits fails the test. An
3336
3398
  `expect` inside `subscribe()` never runs when the stream stays silent, and the test passes.
3337
3399
 
@@ -3766,7 +3828,7 @@ Every export, one line each. Signatures, options and types: [API](https://asdale
3766
3828
  | `stubWorker(opts?)` _(`/dom-stubs`)_ | A `Worker` a spec drives from the worker's side: `respond` per message, `messages` / `terminated` to assert, `emit()` / `fail()`; returns `WorkerStub<TIn, TOut>` |
3767
3829
  | `stubAnimationFrame(opts?)` _(`/dom-stubs`)_ | Frames on the spec's schedule: `'immediate'` or `'queued'` + `flush()`, `pending`, `lastHandle`, both globals as spies; returns `AnimationFrameStub` |
3768
3830
  | `stubElementRect(el, rect?)` _(`/dom-stubs`)_ | A box for `getBoundingClientRect()` — missing fields `0`, edges derived; returns `ElementRectRestore` (callable undo plus `.getBoundingClientRect`) |
3769
- | `createElementStub(opts?)` / `fillMissingDomApis(opts?)` _(`/dom-stubs`)_ | A typed stub `HTMLElement` for `ElementRef`; the DOM members jsdom / happy-dom leave out, filled once in the setup file |
3831
+ | `createElementStub(opts?)` / `fillMissingDomApis(opts?)` _(`/dom-stubs`)_ | A typed stub for `ElementRef`, with optional `elementClass` for `instanceof`; missing DOM APIs filled once in the setup file |
3770
3832
  | `createComponentStub(Class, overrides?, opts?)` _(`/angular`)_ | A standalone stand-in for a child component, directive or pipe, built from its compiled definition so the selector cannot drift |
3771
3833
  | `flushEventLoop(turns?)` / `settleDynamicImport(load, turns?)` | Real event-loop turns under fake timers; wait for a dynamic `import()` |
3772
3834
  | `autoMocked<T>(overrides?, config?)` | `createAutoMock` typed as `T & Spy<T>`; `AutoMocked<T>` names it for a `let` |
@@ -28,6 +28,15 @@ mount(Greeting, { global: { provide } });
28
28
  import { createSpyFromClass } from 'vitest-auto-spy/react';
29
29
  ```
30
30
 
31
+ **Nest HTTP tests.** `createHttpExecutionContext({ request, response, handler, class: Controller })`
32
+ from `/nestjs` supplies an HTTP `ExecutionContext` / `ArgumentsHost` without starting Nest. Omit
33
+ the options you do not need; `getClass()` / `getHandler()` throw if their option is missing.
34
+ `invokeNestParamDecorator(Controller, 'method', parameterIndex, context)` runs a custom parameter
35
+ factory by zero-based index and forwards its recorded `data`. Import `reflect-metadata` before
36
+ the controller. The result is `unknown`; async factories return their promise unchanged. Native
37
+ parameter decorators, pipes and schemas require Nest's pipeline and are refused. Examples:
38
+ [Nest HTTP helpers](https://asdalexey.github.io/vitest-auto-spy/adapters/nestjs#createhttpexecutioncontext).
39
+
31
40
  **Types without Vitest.** `/node`, `/bun`, `/bun-angular`, `/rstest`, `/jasmine-compat`,
32
41
  `/angular-router`, `/console`, `/nestjs`, `/observer-spy` and `/rxjs` type-check with no Vitest
33
42
  installed and `skipLibCheck: false`. `Spy<T>` is one type on every entry: once the program imports an
@@ -189,9 +189,109 @@ expect(host.classes()).toEqual(['card', 'highlighted']); // attributes(), styles
189
189
  host.emit('mouseleave'); // fires the listeners; records nothing on dispatchEvent
190
190
  ```
191
191
 
192
+ For `instanceof` checks, pass `elementClass`:
193
+
194
+ ```ts
195
+ const input = createElementStub({ elementClass: HTMLInputElement, tagName: 'input', overrides: { value: '15' } });
196
+
197
+ expect(input.element).toBeInstanceOf(HTMLInputElement);
198
+ expect(input.element.value).toBe('15'); // inferred as HTMLInputElement
199
+ ```
200
+
201
+ `elementClass?: { readonly prototype: T }` defaults to none and infers `T` from the class.
202
+ It uses the prototype without calling the constructor. `tagName` remains independent and defaults to `'div'`.
203
+ The supplied class adds no implementations. Configure missing properties and methods, such as `value` or `select`, in `overrides`.
204
+ Existing stub methods, such as `focus`, remain usable. Reading an unconfigured class member throws with its name.
205
+
206
+ ### `mockElementRefProp` — an element for a signal query
207
+
208
+ Replace an optional or required `ElementRef` signal query and get the element spies in one call.
209
+ For example, `component.dateTrigger` has type `Signal<ElementRef<HTMLButtonElement> | undefined>`.
210
+ Its `focusTrigger()` method calls `focus()` on the queried element.
211
+
212
+ ```ts
213
+ import { mockElementRefProp } from 'vitest-auto-spy/angular/doubles';
214
+
215
+ const trigger = mockElementRefProp(component, 'dateTrigger', {
216
+ elementClass: HTMLButtonElement,
217
+ tagName: 'button',
218
+ });
219
+ component.focusTrigger();
220
+ expect(trigger.focus).toHaveBeenCalledExactlyOnceWith();
221
+ ```
222
+
223
+ This replaces a local element factory followed by `mockSignalProp(component, 'dateTrigger', new ElementRef(element))`.
224
+ The query determines the element type. Options are those of `createElementStub` and cannot change that type.
225
+ The handle also returns `ref` (the installed `ElementRef`) and `query` (a writable test signal).
226
+ Use `trigger.query.set(undefined)` to represent a missing optional query; required queries reject `undefined` in TypeScript.
227
+ Queries returning component or directive instances, and `viewChildren`, need `mockSignalProp` instead.
228
+
229
+ Patch before rendering a template, effect, or `computed` that consumes the original query.
230
+ Patching an already consumed query throws. This helper creates no rendered element and does not resolve Angular queries.
231
+ `setupAutoSpy()` restores the original query after each test. Without it, add cleanup:
232
+
233
+ ```ts
234
+ import { afterEach } from 'vitest';
235
+ import { restoreMockedProps } from 'vitest-auto-spy';
236
+
237
+ afterEach(() => restoreMockedProps());
238
+ ```
239
+
192
240
  Reading an `HTMLElement` member the stub does not implement throws with the member's name — pass it
193
- in `overrides: { offsetWidth: 120, querySelector: vi.fn() }`. Nothing is patched, so there is nothing
194
- to restore.
241
+ in `overrides: { offsetWidth: 120, querySelector: vi.fn() }`. `createElementStub` itself patches nothing and needs no cleanup.
242
+
243
+ ### Events for direct handler calls
244
+
245
+ Use `createDomEvent` to supply readonly targets without a cast or `Object.defineProperty` in each test.
246
+ Here, `component.onBlur()` reads `currentTarget` and removes the `active` class.
247
+
248
+ ```ts
249
+ import { createDomEvent, createElementStub, createInputEvent } from 'vitest-auto-spy/dom-stubs';
250
+
251
+ const input = createElementStub({ tagName: 'input', classes: ['active'] });
252
+ component.onBlur(createDomEvent('blur', { currentTarget: input.element }));
253
+ expect(input.classes()).toEqual([]);
254
+
255
+ component.onInput(createInputEvent('15'));
256
+ expect(component.value()).toBe('15');
257
+ ```
258
+
259
+ `createDomEvent(type, init?)` returns a native `Event`. `bubbles` and `cancelable` default to `true`.
260
+ The optional `target` and `currentTarget` stay fixed. For propagation tests, omit them and dispatch on a real DOM element.
261
+
262
+ `createInputEvent(value, init?)` returns an `Event` of type `'input'` with an input stub as `target`.
263
+ It supports `instanceof HTMLInputElement` when that class exists. The supplied value wins over `init.element.overrides.value`.
264
+ Pass stub options through `element`, for example `{ element: { overrides: { disabled: true } } }`.
265
+ Pass other native event flags directly in `init`.
266
+
267
+ **Common mistake:** expecting `data`, `inputType`, or a browser input event constructor check to work.
268
+ This helper returns `Event`, not `InputEvent`. Use a native `InputEvent` when testing those browser features.
269
+
270
+ ### Text paste and drop events
271
+
272
+ Replace local clipboard and drag event factories when a handler only reads plain text.
273
+ Here, `onPaste` and `onDrop` read the text and cancel the event.
274
+
275
+ ```ts
276
+ import { createDropEvent, createPasteEvent } from 'vitest-auto-spy/dom-stubs';
277
+
278
+ const paste = createPasteEvent('15');
279
+ component.onPaste(paste);
280
+ expect(paste.defaultPrevented).toBe(true);
281
+
282
+ component.onDrop(createDropEvent('20'));
283
+ expect(component.value()).toBe('20');
284
+ ```
285
+
286
+ Both accept `(text: string | null, init?: EventInit)` and default to bubbling, cancelable events.
287
+ `null` gives `clipboardData: null` or `dataTransfer: null`, so you can test absent data.
288
+ `getData('text')` and `getData('text/plain')` return the supplied text, ignoring case; other formats return `''`.
289
+ `types` is a readonly `['text/plain']` array. These events can also be dispatched on DOM elements.
290
+
291
+ The transfer supports only `getData()` and `types`. Reading `files`, `items`, `setData`, or any other transfer member throws.
292
+ These are native `Event` instances with the text transfer property added.
293
+ They need no `ClipboardEvent`, `DragEvent`, or `DataTransfer` constructor and do not pass those constructor checks.
294
+ For files, coordinates, transfer writes, or browser drag behavior, supply your own event.
195
295
 
196
296
  ### The members the DOM environment leaves out — `fillMissingDomApis`
197
297
 
@@ -35,6 +35,43 @@ store.dispatch(noop());
35
35
  expectNoEmissionSync(store.saved$, { skip: 1 }); // skip the replayed value
36
36
  ```
37
37
 
38
+ ### Synchronous outputs without subscriptions
39
+
40
+ Use `expectEmissionSync` for exactly one synchronous emission, or `expectEmissionsSync` for an exact count.
41
+ They return values immediately, so you need no listener spy, collection array, manual unsubscribe, or `await`.
42
+
43
+ ```ts
44
+ import { expectEmissionSync, expectEmissionsSync } from 'vitest-auto-spy';
45
+
46
+ expect(
47
+ expectEmissionSync(component.closed, {
48
+ advance: () => component.close(),
49
+ }),
50
+ ).toBeUndefined();
51
+
52
+ expect(
53
+ expectEmissionsSync(component.selected, 2, {
54
+ advance: () => {
55
+ component.select(15);
56
+ component.select(20);
57
+ },
58
+ }),
59
+ ).toEqual([15, 20]);
60
+ ```
61
+
62
+ The helper subscribes first, runs `advance` once, then unsubscribes before returning or throwing.
63
+ Values emitted during subscription count too. Use `{ skip: 1 }` to ignore a replayed initial value.
64
+ `until` filters accepted values; `label` names the source in failures. These are the only other options: there is no `timeout`.
65
+ Both helpers also come from `vitest-auto-spy/angular` and accept Angular outputs or observable-like sources.
66
+
67
+ A missing or extra accepted value fails immediately. Unlike `expectEmission`, which stops at the first value,
68
+ `expectEmissionSync` checks everything emitted during subscription and the synchronous action.
69
+ `expectEmissionsSync` requires a positive integer count; use `expectNoEmissionSync` for zero.
70
+ Source errors and thrown callbacks fail the assertion and close the subscription.
71
+
72
+ **Common mistake:** using this for a timer or microtask emission. The subscription is already closed when that value arrives.
73
+ Use `expectEmission` or `expectEmissions` with `await` for asynchronous work.
74
+
38
75
  A failure that counts emissions also shows the first five values, then `… N more`:
39
76
  `the stream completed after 7 emissions (1, 2, 3, 4, 5, … 2 more), expected 9`,
40
77
  `did not complete within 20 ms (3 emissions received: 1, 2, 3)`.
@@ -1,16 +1,17 @@
1
- import { pendingRequestsReport, assertAngularInternals, instrumentTestBed, onTestingModuleConfigured, onComponentCreated, HTTP_TESTING_BRAND, beforeTestBedReset, verifyOnTeardown } from './chunk-JCTHGJTY.js';
2
- export { disableTestBedDiagnostics, enableTestBedDiagnostics, formatSpecTiming, getTestBedTiming, instrumentTestBed, reportSpecTiming } from './chunk-JCTHGJTY.js';
3
- import { failOnUnspiedProvider, onTestBedFileEnd, isLazyDoubleFactory } from './chunk-OWZPZBJI.js';
1
+ import { pendingRequestsReport, instrumentTestBed, onTestingModuleConfigured, onComponentCreated, HTTP_TESTING_BRAND, beforeTestBedReset, verifyOnTeardown } from './chunk-C7K75NLS.js';
2
+ export { disableTestBedDiagnostics, enableTestBedDiagnostics, formatSpecTiming, getTestBedTiming, instrumentTestBed, reportSpecTiming } from './chunk-C7K75NLS.js';
3
+ import { assertAngularInternals } from './chunk-OIHOH2RY.js';
4
+ import { failOnUnspiedProvider, onTestBedFileEnd, isLazyDoubleFactory } from './chunk-OXE6FCQX.js';
4
5
  import './chunk-T5MQSNRX.js';
5
- import './chunk-4JY7KIJG.js';
6
- import './chunk-RQLUH6RE.js';
7
- import './chunk-BCT7C6DQ.js';
8
- import './chunk-F7UPX6YV.js';
6
+ import './chunk-XZS56XV5.js';
7
+ import './chunk-5GKIQX65.js';
8
+ import './chunk-XLKBJSYQ.js';
9
+ import './chunk-QATP6Q3G.js';
9
10
  import './chunk-PC3FAZT3.js';
10
- import './chunk-UH2Q7RTQ.js';
11
- import { isAutoSpyLike } from './chunk-TXC3L3FO.js';
11
+ import './chunk-HIGHZ4BW.js';
12
+ import { isAutoSpyLike } from './chunk-2CWSOIBW.js';
12
13
  import { count, sourceClassName, taskName } from './chunk-DYAS4OK4.js';
13
- import { angularPendingRequests, angularShadowedProviders, angularDeadSchemas, angularNgModuleScopesAuto } from './chunk-IATDAQMO.js';
14
+ import { angularPendingRequests, angularShadowedProviders, angularDeadSchemas, angularNgModuleScopesAuto } from './chunk-7WU53KRS.js';
14
15
  import { withDocs } from './chunk-ENCEEH23.js';
15
16
  import { getTestBed } from '@angular/core/testing';
16
17
  import { beforeEach, afterEach } from 'vitest';
@@ -1,7 +1,8 @@
1
1
  import './vitest-mock-types-B17-xaWt.js';
2
- import { InjectionToken, AbstractType, Injector, ValueProvider, FactoryProvider, ProviderToken, ChangeDetectorRef } from '@angular/core';
2
+ import { InjectionToken, AbstractType, Injector, ValueProvider, FactoryProvider, ProviderToken, ChangeDetectorRef, ElementRef, WritableSignal, Signal } from '@angular/core';
3
3
  import { h as AddSpyMethodsByReturnTypes, S as Spy, F as Func, p as FunctionSpy } from './types-BdYUK_Zv.js';
4
4
  import { DomSanitizer } from '@angular/platform-browser';
5
+ import { E as ElementStub, a as ElementStubOptions } from './element-stub-D3kchFNq.js';
5
6
  import 'vitest';
6
7
 
7
8
  /**
@@ -501,4 +502,31 @@ type ValidSelection<T, Selection> = Selection extends readonly unknown[] ? reado
501
502
  */
502
503
  declare function createWindowSpies<const Selection extends WindowSpySelection>(selection: Selection & ValidSelection<Window, Selection>): WindowSpies<Window, Selection>;
503
504
 
504
- export { type AttachedComponent, type DialogComponent, type DialogDataOf, type DialogRefLike, type DialogResult, type MatDialogRefDouble, type MatDialogRefInit, type OverlayDouble, type OverlayDoubleInit, type OverlayLike, type OverlayRefDouble, type OverlayRefStub, type PlatformFlagTokens, type PlatformName, type PlatformOverrides, type PositionCall, type WindowSpies, type WindowSpySelection, createChangeDetectorRefDouble, createDocumentDouble, createDomSanitizerDouble, createMatDialogRef, createOverlayDouble, createWindowDouble, createWindowSpies, injectMatDialogRef, injectOverlayDouble, provideChangeDetectorRefDouble, provideDocumentDouble, provideDomSanitizerDouble, provideMatDialogData, provideMatDialogRef, provideOverlayDouble, providePlatform, provideWindowDouble };
505
+ type QueryValue<T> = T extends Signal<infer V> ? V : never;
506
+ type QueryElement<T> = [Exclude<QueryValue<T>, undefined>] extends [never] ? never : Exclude<QueryValue<T>, undefined> extends ElementRef<infer E extends HTMLElement> ? E : never;
507
+ type ElementQueryKey<T> = {
508
+ [K in keyof T]-?: [QueryElement<T[K]>] extends [never] ? never : K;
509
+ }[keyof T];
510
+ /** Element spies together with the reference installed on the query and its writable test handle. */
511
+ type ElementRefPropStub<T extends HTMLElement, V = ElementRef<T> | undefined> = ElementStub<T> & {
512
+ readonly ref: ElementRef<T>;
513
+ readonly query: WritableSignal<V>;
514
+ };
515
+ /**
516
+ * Replace an ElementRef-valued signal query with a DOM stub and return its spies.
517
+ *
518
+ * ```ts
519
+ * const trigger = mockElementRefProp(component, 'dateTrigger');
520
+ * component.close();
521
+ * expect(trigger.focus).toHaveBeenCalledExactlyOnceWith();
522
+ * ```
523
+ *
524
+ * The queried element type is inferred from the property. Options are those of `createElementStub`;
525
+ * they do not infer a different element type. `ref` is the installed `ElementRef`; `query` can drive
526
+ * a later reference, or `undefined` for an optional query. Patch before rendering a live consumer.
527
+ * Like `mockSignalProp`, replacement patches are undone by `restoreMockedProps()` / `setupAutoSpy()`.
528
+ * This does not render a DOM element or cause Angular to resolve a view query.
529
+ */
530
+ declare function mockElementRefProp<T, K extends ElementQueryKey<T> & keyof T>(object: T, property: K, options?: ElementStubOptions<NoInfer<QueryElement<T[K]>>>): ElementRefPropStub<QueryElement<T[K]>, QueryValue<T[K]>>;
531
+
532
+ export { type AttachedComponent, type DialogComponent, type DialogDataOf, type DialogRefLike, type DialogResult, type ElementRefPropStub, type MatDialogRefDouble, type MatDialogRefInit, type OverlayDouble, type OverlayDoubleInit, type OverlayLike, type OverlayRefDouble, type OverlayRefStub, type PlatformFlagTokens, type PlatformName, type PlatformOverrides, type PositionCall, type WindowSpies, type WindowSpySelection, createChangeDetectorRefDouble, createDocumentDouble, createDomSanitizerDouble, createMatDialogRef, createOverlayDouble, createWindowDouble, createWindowSpies, injectMatDialogRef, injectOverlayDouble, mockElementRefProp, provideChangeDetectorRefDouble, provideDocumentDouble, provideDomSanitizerDouble, provideMatDialogData, provideMatDialogRef, provideOverlayDouble, providePlatform, provideWindowDouble };