vitest-auto-spy 5.63.0 → 5.65.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 (68) hide show
  1. package/AGENTS.md +9 -0
  2. package/CHANGELOG.md +27 -1
  3. package/README.md +44 -3
  4. package/agent-docs/doubles.md +102 -2
  5. package/agent-docs/observables.md +37 -0
  6. package/dist/angular-diagnostics.js +11 -10
  7. package/dist/angular-doubles.d.ts +30 -2
  8. package/dist/angular-doubles.js +318 -14
  9. package/dist/angular-http.js +5 -4
  10. package/dist/angular-matchers.js +1 -1
  11. package/dist/angular-router.js +6 -6
  12. package/dist/angular.d.ts +3 -3
  13. package/dist/angular.js +1 -1
  14. package/dist/bun-angular.d.ts +2 -2
  15. package/dist/bun-angular.js +19 -19
  16. package/dist/bun.d.ts +1 -1
  17. package/dist/bun.js +11 -11
  18. package/dist/{chunk-BCT7C6DQ.js → chunk-2OYBXIQC.js} +3 -3
  19. package/dist/{chunk-XZ5JUXKT.js → chunk-5XYULG5M.js} +2 -2
  20. package/dist/{chunk-IATDAQMO.js → chunk-7A4QSHDK.js} +2 -1
  21. package/dist/{chunk-4JY7KIJG.js → chunk-BRKMJMTO.js} +1 -1
  22. package/dist/{chunk-6KS7F4YM.js → chunk-C4OFWAK7.js} +2 -2
  23. package/dist/{chunk-JCTHGJTY.js → chunk-C7K75NLS.js} +1 -31
  24. package/dist/{chunk-3OEYMUV3.js → chunk-FWXGEM2E.js} +1 -1
  25. package/dist/{chunk-R43PMWFH.js → chunk-IR5AZKBS.js} +1 -1
  26. package/dist/{chunk-75WPOZKE.js → chunk-JOLWHOGU.js} +1 -1
  27. package/dist/chunk-LGWVFIIJ.js +34 -0
  28. package/dist/{chunk-RQLUH6RE.js → chunk-LZ64J6OW.js} +3 -3
  29. package/dist/{chunk-F7UPX6YV.js → chunk-NGVSSYI4.js} +1 -1
  30. package/dist/{chunk-TXC3L3FO.js → chunk-P6P7UE7C.js} +1 -1
  31. package/dist/{chunk-RHGDQQNC.js → chunk-PHXYSUVL.js} +1 -1
  32. package/dist/{chunk-MXKNUVYR.js → chunk-SSTOOTI2.js} +1 -1
  33. package/dist/{chunk-OWZPZBJI.js → chunk-TH2H3MSZ.js} +4 -4
  34. package/dist/{chunk-UH2Q7RTQ.js → chunk-UON4XYJW.js} +2 -2
  35. package/dist/{chunk-JFROAGGY.js → chunk-VEYLI6J2.js} +2 -2
  36. package/dist/{chunk-VLEU3CWT.js → chunk-VFPT6JP6.js} +2 -2
  37. package/dist/{chunk-EFUZ44HT.js → chunk-VJV6QF4Z.js} +75 -7
  38. package/dist/{chunk-ZTT2QLMJ.js → chunk-Y3MYFJ75.js} +4 -4
  39. package/dist/cli.js +13 -1
  40. package/dist/console.js +8 -8
  41. package/dist/core.d.ts +1 -1
  42. package/dist/dom-stubs.d.ts +41 -87
  43. package/dist/dom-stubs.js +76 -8
  44. package/dist/element-stub-D3kchFNq.d.ts +97 -0
  45. package/dist/{emission-sync-ByzgYsbj.d.ts → emission-positive-sync-BrSc2lb-.d.ts} +20 -1
  46. package/dist/index.d.ts +1 -1
  47. package/dist/index.js +70 -2
  48. package/dist/jasmine-compat.js +2 -2
  49. package/dist/jasmine.js +12 -12
  50. package/dist/nestjs.js +10 -10
  51. package/dist/node.cjs +70 -0
  52. package/dist/node.d.cts +2 -0
  53. package/dist/node.d.ts +1 -1
  54. package/dist/node.js +69 -1
  55. package/dist/react.d.ts +1 -1
  56. package/dist/react.js +1 -1
  57. package/dist/rstest.d.ts +1 -1
  58. package/dist/rstest.js +10 -10
  59. package/dist/rxjs.js +2 -2
  60. package/dist/signal-forms.js +1 -1
  61. package/dist/svelte.d.ts +1 -1
  62. package/dist/svelte.js +1 -1
  63. package/dist/{track-signal-runs-D1Vr9BdG.d.ts → track-signal-runs-DLvVp9iI.d.ts} +1 -1
  64. package/dist/vue.d.ts +1 -1
  65. package/dist/vue.js +1 -1
  66. package/dist/zone.js +1 -1
  67. package/package.json +1 -1
  68. package/skills/vitest-auto-spy/SKILL.md +9 -0
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
 
package/CHANGELOG.md CHANGED
@@ -10,6 +10,30 @@ The latest released version here must always match the one published on
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [5.65.0] - 2026-10-26
14
+
15
+ ## [5.64.0] - 2026-10-23
16
+
17
+ ### Added
18
+
19
+ - `createElementStub({ elementClass: HTMLInputElement })` in `/dom-stubs` infers the element type
20
+ and supports `instanceof` checks without creating a DOM element. Missing members of the selected
21
+ class keep the stub's named error; supply their implementation in `overrides`.
22
+ - `/dom-stubs`: `createDomEvent` supplies readonly targets for direct handler calls;
23
+ `createInputEvent` supplies a typed input target and value; `createPasteEvent` and `createDropEvent`
24
+ supply text transfers and native cancellation without browser transfer constructors.
25
+ - `/angular/doubles`: `mockElementRefProp(component, 'trigger')` installs an inferred element
26
+ stub on an `ElementRef` signal query and returns the element spies, reference and writable query.
27
+ These DOM/query helpers add code only to their narrow entries.
28
+ - The root entry and `/angular`: `expectEmissionSync` and `expectEmissionsSync` replace manual
29
+ subscriptions and arrays with exact synchronous emission counts. They keep listening during
30
+ `advance` to catch extra emissions and tear down before returning or throwing.
31
+ - **Bundle size:** `/dom-stubs` grows by 491 B min+gzip over 5.63.0 for element prototypes and
32
+ handler events; `/angular/doubles` grows by 3.77 kB for the typed query/stub composition and its
33
+ existing signal-patching machinery. Shared bundle modules also change the import graph of
34
+ `/angular/diagnostics`, `/angular/matchers` and `/angular-http`; the recorded graph and sizes
35
+ include this reuse.
36
+
13
37
  ## [5.63.0] - 2026-10-21
14
38
 
15
39
  ### Added
@@ -6864,7 +6888,9 @@ moved under 1 %: `/angular` +192 B (+1.0 %) for the override queue and the HTTP
6864
6888
  This copy ships with the package and carries 5.x only. Releases before 5.0.0 are in the
6865
6889
  [full changelog](https://github.com/ASDAlexey/vitest-auto-spy/blob/master/CHANGELOG.md).
6866
6890
 
6867
- [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.63.0...HEAD
6891
+ [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.65.0...HEAD
6892
+ [5.65.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.64.0...v5.65.0
6893
+ [5.64.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.63.0...v5.64.0
6868
6894
  [5.63.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.62.0...v5.63.0
6869
6895
  [5.62.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.61.0...v5.62.0
6870
6896
  [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,7 @@ 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.
49
50
  - **One API on every runner**: Vitest, Bun, `node:test` and Rstest; helpers for Angular, NestJS,
50
51
  React, Vue / Pinia and Svelte.
51
52
  - **Checks that make silent tests fail**: strict mode, Observable assertions, cleanup between tests,
@@ -3082,8 +3083,29 @@ two thousand tests add about 0.02 s to the run. Numbers:
3082
3083
  Besides the spy factories, the package has small standalone helpers. Each does one job; pick up
3083
3084
  only the ones you need. Every helper, with its signature: [API](https://asdalexey.github.io/vitest-auto-spy/api).
3084
3085
 
3086
+ For direct handler calls, replace local event factories and query setup with these helpers:
3087
+
3088
+ ```ts
3089
+ import { mockElementRefProp } from 'vitest-auto-spy/angular/doubles';
3090
+ import { createInputEvent } from 'vitest-auto-spy/dom-stubs';
3091
+
3092
+ const trigger = mockElementRefProp(component, 'triggerButton');
3093
+ component.store.open(0, 0);
3094
+ component.onMinutesChange(createInputEvent('15'));
3095
+ component.confirm();
3096
+ expect(trigger.focus).toHaveBeenCalledOnce();
3097
+ ```
3098
+
3099
+ `mockElementRefProp` infers the element type from the signal query. Set `elementClass` for runtime
3100
+ `instanceof` checks. Patch before a live consumer reads the query; `setupAutoSpy()` restores replaced
3101
+ properties. Events with supplied targets are for direct handler calls. Paste/drop support text reads
3102
+ only. Full contracts: [element and event stubs](https://asdalexey.github.io/vitest-auto-spy/utilities/element-stub).
3103
+
3085
3104
  | Utility | Entry point | What it's for |
3086
3105
  | ----------------------------------------------------------------------------------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
3106
+ | `createDomEvent(type, init?)` / `createInputEvent(value, init?)` | `/dom-stubs` | Native events for direct handlers, with readonly targets or an input value |
3107
+ | `createPasteEvent(text, init?)` / `createDropEvent(text, init?)` | `/dom-stubs` | Text transfers with native `defaultPrevented`; `null` represents absent transfer data |
3108
+ | `mockElementRefProp(component, key, opts?)` | `/angular/doubles` | Inferred element spies, `ref` and writable `query`, installed together |
3087
3109
  | `injectSpy(token)` / `injectSpy(moduleRef, token)` | `/angular`, `/nestjs` | Pull a provided spy out of the DI container, already typed as `Spy<T>` — no casting |
3088
3110
  | `provideAutoSpy(Class, config?)` | `/angular`, `/nestjs`, `/vue` | One-liner `{ provide, useValue }` (or Vue `global.provide`) that builds the spy for you |
3089
3111
  | `createFunctionSpy(name)` | core | A single standalone function spy with the full helper set (`calledWith`, `resolveWith`, `nextWith`, …) — no class needed |
@@ -3112,7 +3134,7 @@ only the ones you need. Every helper, with its signature: [API](https://asdalexe
3112
3134
  | `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
3135
  | `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
3136
  | `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 |
3137
+ | `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
3138
  | `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
3139
  | `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
3140
  | `flushEventLoopUntil(isDone, opts?)` | core | Real turns until a condition holds — a `resource()` leaving `loading` — with a budget instead of a hang |
@@ -3332,6 +3354,25 @@ Full page: [explainSpy](https://asdalexey.github.io/vitest-auto-spy/utilities/ex
3332
3354
 
3333
3355
  ## Observable assertions
3334
3356
 
3357
+ For a synchronous handler, check the exact output count without managing a subscription:
3358
+
3359
+ ```ts
3360
+ import { expectEmissionSync, expectEmissionsSync } from 'vitest-auto-spy';
3361
+
3362
+ expect(expectEmissionSync(component.page, { advance: () => component.next() })).toBe(3);
3363
+ expect(
3364
+ expectEmissionsSync(component.page, 2, {
3365
+ advance: () => {
3366
+ component.next();
3367
+ component.next();
3368
+ },
3369
+ }),
3370
+ ).toEqual([4, 5]);
3371
+ ```
3372
+
3373
+ Both helpers unsubscribe before returning and fail on missing or extra synchronous values.
3374
+ For timers, promises or other asynchronous work, use the awaited helpers below.
3375
+
3335
3376
  These helpers make the assertion an `await`, so a stream that never emits fails the test. An
3336
3377
  `expect` inside `subscribe()` never runs when the stream stays silent, and the test passes.
3337
3378
 
@@ -3766,7 +3807,7 @@ Every export, one line each. Signatures, options and types: [API](https://asdale
3766
3807
  | `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
3808
  | `stubAnimationFrame(opts?)` _(`/dom-stubs`)_ | Frames on the spec's schedule: `'immediate'` or `'queued'` + `flush()`, `pending`, `lastHandle`, both globals as spies; returns `AnimationFrameStub` |
3768
3809
  | `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 |
3810
+ | `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
3811
  | `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
3812
  | `flushEventLoop(turns?)` / `settleDynamicImport(load, turns?)` | Real event-loop turns under fake timers; wait for a dynamic `import()` |
3772
3813
  | `autoMocked<T>(overrides?, config?)` | `createAutoMock` typed as `T & Spy<T>`; `AutoMocked<T>` names it for a `let` |
@@ -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-LGWVFIIJ.js';
4
+ import { failOnUnspiedProvider, onTestBedFileEnd, isLazyDoubleFactory } from './chunk-TH2H3MSZ.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-BRKMJMTO.js';
7
+ import './chunk-LZ64J6OW.js';
8
+ import './chunk-2OYBXIQC.js';
9
+ import './chunk-NGVSSYI4.js';
9
10
  import './chunk-PC3FAZT3.js';
10
- import './chunk-UH2Q7RTQ.js';
11
- import { isAutoSpyLike } from './chunk-TXC3L3FO.js';
11
+ import './chunk-UON4XYJW.js';
12
+ import { isAutoSpyLike } from './chunk-P6P7UE7C.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-7A4QSHDK.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 };