vitest-auto-spy 5.62.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.
- package/AGENTS.md +12 -0
- package/CHANGELOG.md +36 -1
- package/README.md +73 -3
- package/agent-docs/doubles.md +102 -2
- package/agent-docs/observables.md +37 -0
- package/agent-docs/return-helpers.md +8 -0
- package/dist/angular-diagnostics.js +11 -10
- package/dist/angular-doubles.d.ts +30 -2
- package/dist/angular-doubles.js +318 -14
- package/dist/angular-http.js +5 -4
- package/dist/angular-matchers.js +1 -1
- package/dist/angular-router.js +6 -6
- package/dist/angular.d.ts +3 -3
- package/dist/angular.js +1 -1
- package/dist/bun-angular.d.ts +3 -3
- package/dist/bun-angular.js +19 -19
- package/dist/bun.d.ts +2 -2
- package/dist/bun.js +11 -11
- package/dist/{chunk-BCT7C6DQ.js → chunk-2OYBXIQC.js} +3 -3
- package/dist/{chunk-XZ5JUXKT.js → chunk-5XYULG5M.js} +2 -2
- package/dist/{chunk-IATDAQMO.js → chunk-7A4QSHDK.js} +2 -1
- package/dist/{chunk-4JY7KIJG.js → chunk-BRKMJMTO.js} +1 -1
- package/dist/{chunk-6KS7F4YM.js → chunk-C4OFWAK7.js} +2 -2
- package/dist/{chunk-JCTHGJTY.js → chunk-C7K75NLS.js} +1 -31
- package/dist/{chunk-3OEYMUV3.js → chunk-FWXGEM2E.js} +1 -1
- package/dist/{chunk-R43PMWFH.js → chunk-IR5AZKBS.js} +1 -1
- package/dist/{chunk-75WPOZKE.js → chunk-JOLWHOGU.js} +1 -1
- package/dist/chunk-LGWVFIIJ.js +34 -0
- package/dist/{chunk-RQLUH6RE.js → chunk-LZ64J6OW.js} +3 -3
- package/dist/{chunk-F7UPX6YV.js → chunk-NGVSSYI4.js} +1 -1
- package/dist/{chunk-TXC3L3FO.js → chunk-P6P7UE7C.js} +1 -1
- package/dist/{chunk-RHGDQQNC.js → chunk-PHXYSUVL.js} +1 -1
- package/dist/{chunk-MXKNUVYR.js → chunk-SSTOOTI2.js} +1 -1
- package/dist/{chunk-OWZPZBJI.js → chunk-TH2H3MSZ.js} +4 -4
- package/dist/{chunk-UH2Q7RTQ.js → chunk-UON4XYJW.js} +2 -2
- package/dist/{chunk-JFROAGGY.js → chunk-VEYLI6J2.js} +2 -2
- package/dist/{chunk-VLEU3CWT.js → chunk-VFPT6JP6.js} +2 -2
- package/dist/{chunk-YAAE5465.js → chunk-VJV6QF4Z.js} +86 -7
- package/dist/{chunk-ZTT2QLMJ.js → chunk-Y3MYFJ75.js} +4 -4
- package/dist/cli.js +15 -1
- package/dist/console.js +8 -8
- package/dist/core.d.ts +14 -2
- package/dist/dom-stubs.d.ts +41 -87
- package/dist/dom-stubs.js +76 -8
- package/dist/element-stub-D3kchFNq.d.ts +97 -0
- package/dist/{emission-sync-ByzgYsbj.d.ts → emission-positive-sync-BrSc2lb-.d.ts} +20 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +81 -2
- package/dist/jasmine-compat.js +2 -2
- package/dist/jasmine.js +12 -12
- package/dist/nestjs.js +10 -10
- package/dist/node.cjs +82 -0
- package/dist/node.d.cts +3 -0
- package/dist/node.d.ts +2 -2
- package/dist/node.js +80 -1
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/rstest.d.ts +2 -2
- package/dist/rstest.js +10 -10
- package/dist/rxjs.js +2 -2
- package/dist/signal-forms.js +1 -1
- package/dist/svelte.d.ts +2 -2
- package/dist/svelte.js +1 -1
- package/dist/{track-signal-runs-D1Vr9BdG.d.ts → track-signal-runs-DLvVp9iI.d.ts} +1 -1
- package/dist/vue.d.ts +2 -2
- package/dist/vue.js +1 -1
- package/dist/zone.js +1 -1
- package/package.json +1 -1
- package/skills/vitest-auto-spy/SKILL.md +10 -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
|
|
|
@@ -250,6 +255,9 @@ Rules that carry most of the value:
|
|
|
250
255
|
In [`agent-docs/return-helpers.md`](./agent-docs/return-helpers.md). Read it when stubbing a return value: `calledWith`, `resolveWith`, `nextWith`, `returnSubject` and the rest, by return type.
|
|
251
256
|
|
|
252
257
|
<!-- agents-outline: agent-docs/return-helpers.md -->
|
|
258
|
+
|
|
259
|
+
- Keeping an awaited collaborator pending
|
|
260
|
+
|
|
253
261
|
<!-- /agents-outline -->
|
|
254
262
|
|
|
255
263
|
---
|
|
@@ -384,6 +392,7 @@ In [`agent-docs/observables.md`](./agent-docs/observables.md). Read it when asse
|
|
|
384
392
|
|
|
385
393
|
<!-- agents-outline: agent-docs/observables.md -->
|
|
386
394
|
|
|
395
|
+
- Synchronous outputs without subscriptions
|
|
387
396
|
- `createLog()` — the order between the spies
|
|
388
397
|
|
|
389
398
|
<!-- /agents-outline -->
|
|
@@ -445,6 +454,9 @@ In [`agent-docs/doubles.md`](./agent-docs/doubles.md). Read it when production c
|
|
|
445
454
|
- `localStorage` and `sessionStorage`
|
|
446
455
|
- Animation frames and element boxes
|
|
447
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
|
|
448
460
|
- The members the DOM environment leaves out — `fillMissingDomApis`
|
|
449
461
|
- A module mock that did nothing
|
|
450
462
|
|
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,38 @@ 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
|
+
|
|
37
|
+
## [5.63.0] - 2026-10-21
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- `createDeferred<T>()` returns a typed pending promise with detached `resolve` and `reject`
|
|
42
|
+
functions, for assertions before an awaited collaborator finishes. It defaults to `void` and
|
|
43
|
+
works on supported runtimes without `Promise.withResolvers`.
|
|
44
|
+
|
|
13
45
|
## [5.62.0] - 2026-10-20
|
|
14
46
|
|
|
15
47
|
### Fixed
|
|
@@ -6856,7 +6888,10 @@ moved under 1 %: `/angular` +192 B (+1.0 %) for the override queue and the HTTP
|
|
|
6856
6888
|
This copy ships with the package and carries 5.x only. Releases before 5.0.0 are in the
|
|
6857
6889
|
[full changelog](https://github.com/ASDAlexey/vitest-auto-spy/blob/master/CHANGELOG.md).
|
|
6858
6890
|
|
|
6859
|
-
[Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.
|
|
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
|
|
6894
|
+
[5.63.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.62.0...v5.63.0
|
|
6860
6895
|
[5.62.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.61.0...v5.62.0
|
|
6861
6896
|
[5.61.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.60.0...v5.61.0
|
|
6862
6897
|
[5.60.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.59.1...v5.60.0
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ replacement for [`jest-auto-spies`](https://www.npmjs.com/package/jest-auto-spie
|
|
|
15
15
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
16
16
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
17
17
|
[](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
|
|
18
|
-
[](#install)
|
|
19
19
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
20
20
|
[](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
|
|
21
21
|
[](./LICENSE)
|
|
@@ -42,9 +42,11 @@ replacement for [`jest-auto-spies`](https://www.npmjs.com/package/jest-auto-spie
|
|
|
42
42
|
|
|
43
43
|
- **A spy of every method from a class**, typed, with helpers that match each return type:
|
|
44
44
|
`resolveWith` for a `Promise`, `nextWith` for an `Observable`, `mockReturnValue` for the rest.
|
|
45
|
+
`createDeferred<T>()` keeps an awaited collaborator pending until the test releases it.
|
|
45
46
|
- **Answers per argument list**: `calledWith(1).mockReturnValue(...)`.
|
|
46
47
|
- **A spy from a type alone**: `createAutoMock<T>()` and `mockDeep<T>()`.
|
|
47
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.
|
|
48
50
|
- **One API on every runner**: Vitest, Bun, `node:test` and Rstest; helpers for Angular, NestJS,
|
|
49
51
|
React, Vue / Pinia and Svelte.
|
|
50
52
|
- **Checks that make silent tests fail**: strict mode, Observable assertions, cleanup between tests,
|
|
@@ -1167,6 +1169,7 @@ that keeps the cut inside; `node scripts/check-readme-npm-cut.mjs --write` resiz
|
|
|
1167
1169
|
- [Synchronous methods](#synchronous-methods)
|
|
1168
1170
|
- [Matching order, and re-configuring the same arguments](#matching-order-and-re-configuring-the-same-arguments)
|
|
1169
1171
|
- [Promise-returning methods](#promise-returning-methods)
|
|
1172
|
+
- [Keeping a promise pending](#keeping-a-promise-pending--createdeferred)
|
|
1170
1173
|
- [Observable-returning methods & Observable properties](#observable-returning-methods--observable-properties)
|
|
1171
1174
|
- [Standalone observable builder](#standalone-observable-builder)
|
|
1172
1175
|
- [Getters & setters](#getters--setters)
|
|
@@ -2360,6 +2363,33 @@ myService.getProducts.resolveWithPerCall([{ value: ['a'] }, { value: ['b'] }]);
|
|
|
2360
2363
|
myService.getProducts.calledWith(1).resolveWith(['one']);
|
|
2361
2364
|
```
|
|
2362
2365
|
|
|
2366
|
+
### Keeping a promise pending — `createDeferred`
|
|
2367
|
+
|
|
2368
|
+
`createDeferred<T>()` returns `{ promise, resolve, reject }`. Use it to check what happens before
|
|
2369
|
+
an awaited collaborator finishes, then release that collaborator without timers.
|
|
2370
|
+
|
|
2371
|
+
```ts
|
|
2372
|
+
import { expect } from 'vitest';
|
|
2373
|
+
import { createDeferred } from 'vitest-auto-spy';
|
|
2374
|
+
|
|
2375
|
+
const home = createDeferred<boolean>();
|
|
2376
|
+
const events: string[] = [];
|
|
2377
|
+
const navigation = home.promise.then((ok) => {
|
|
2378
|
+
events.push('deeplink');
|
|
2379
|
+
return ok;
|
|
2380
|
+
});
|
|
2381
|
+
expect(events).toEqual([]);
|
|
2382
|
+
home.resolve(true);
|
|
2383
|
+
await expect(navigation).resolves.toBe(true);
|
|
2384
|
+
expect(events).toEqual(['deeplink']);
|
|
2385
|
+
```
|
|
2386
|
+
|
|
2387
|
+
`resolve` accepts a value or a `PromiseLike<T>`; `reject(reason)` rejects with that reason.
|
|
2388
|
+
`createDeferred()` defaults to `void`, so `resolve()` needs no argument. Settle the promise before
|
|
2389
|
+
awaiting the operation that depends on it, or the test will wait forever. Each call creates an
|
|
2390
|
+
independent native promise; the first settlement wins. No fake timers or `Promise.withResolvers`
|
|
2391
|
+
are required. Import from `/bun`, `/node` or `/rstest` when using those runners.
|
|
2392
|
+
|
|
2363
2393
|
## Observable-returning methods & Observable properties
|
|
2364
2394
|
|
|
2365
2395
|
Methods that return an `Observable`, and `Observable` properties listed in `observablePropsToSpyOn`,
|
|
@@ -3053,8 +3083,29 @@ two thousand tests add about 0.02 s to the run. Numbers:
|
|
|
3053
3083
|
Besides the spy factories, the package has small standalone helpers. Each does one job; pick up
|
|
3054
3084
|
only the ones you need. Every helper, with its signature: [API](https://asdalexey.github.io/vitest-auto-spy/api).
|
|
3055
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
|
+
|
|
3056
3104
|
| Utility | Entry point | What it's for |
|
|
3057
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 |
|
|
3058
3109
|
| `injectSpy(token)` / `injectSpy(moduleRef, token)` | `/angular`, `/nestjs` | Pull a provided spy out of the DI container, already typed as `Spy<T>` — no casting |
|
|
3059
3110
|
| `provideAutoSpy(Class, config?)` | `/angular`, `/nestjs`, `/vue` | One-liner `{ provide, useValue }` (or Vue `global.provide`) that builds the spy for you |
|
|
3060
3111
|
| `createFunctionSpy(name)` | core | A single standalone function spy with the full helper set (`calledWith`, `resolveWith`, `nextWith`, …) — no class needed |
|
|
@@ -3083,7 +3134,7 @@ only the ones you need. Every helper, with its signature: [API](https://asdalexe
|
|
|
3083
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)) |
|
|
3084
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) |
|
|
3085
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` |
|
|
3086
|
-
| `createElementStub(opts?)` | `/dom-stubs` | A typed
|
|
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` |
|
|
3087
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 }` |
|
|
3088
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 |
|
|
3089
3140
|
| `flushEventLoopUntil(isDone, opts?)` | core | Real turns until a condition holds — a `resource()` leaving `loading` — with a budget instead of a hang |
|
|
@@ -3303,6 +3354,25 @@ Full page: [explainSpy](https://asdalexey.github.io/vitest-auto-spy/utilities/ex
|
|
|
3303
3354
|
|
|
3304
3355
|
## Observable assertions
|
|
3305
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
|
+
|
|
3306
3376
|
These helpers make the assertion an `await`, so a stream that never emits fails the test. An
|
|
3307
3377
|
`expect` inside `subscribe()` never runs when the stream stays silent, and the test passes.
|
|
3308
3378
|
|
|
@@ -3737,7 +3807,7 @@ Every export, one line each. Signatures, options and types: [API](https://asdale
|
|
|
3737
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>` |
|
|
3738
3808
|
| `stubAnimationFrame(opts?)` _(`/dom-stubs`)_ | Frames on the spec's schedule: `'immediate'` or `'queued'` + `flush()`, `pending`, `lastHandle`, both globals as spies; returns `AnimationFrameStub` |
|
|
3739
3809
|
| `stubElementRect(el, rect?)` _(`/dom-stubs`)_ | A box for `getBoundingClientRect()` — missing fields `0`, edges derived; returns `ElementRectRestore` (callable undo plus `.getBoundingClientRect`) |
|
|
3740
|
-
| `createElementStub(opts?)` / `fillMissingDomApis(opts?)` _(`/dom-stubs`)_ | A typed stub
|
|
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 |
|
|
3741
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 |
|
|
3742
3812
|
| `flushEventLoop(turns?)` / `settleDynamicImport(load, turns?)` | Real event-loop turns under fake timers; wait for a dynamic `import()` |
|
|
3743
3813
|
| `autoMocked<T>(overrides?, config?)` | `createAutoMock` typed as `T & Spy<T>`; `AutoMocked<T>` names it for a `let` |
|
package/agent-docs/doubles.md
CHANGED
|
@@ -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() }`.
|
|
194
|
-
|
|
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)`.
|
|
@@ -220,3 +220,11 @@ closures, not on the runner's mock. So when a spy outlives a test — a TestBed
|
|
|
220
220
|
a spy hoisted to `describe` scope — put `resetAutoSpy(spy)` in `beforeEach`. Inside one test the
|
|
221
221
|
sequence `nextWith(a)` then `throwWith(e)` still means "emit a, then fail"; only a reset or a
|
|
222
222
|
terminal call starts a new stream.
|
|
223
|
+
|
|
224
|
+
### Keeping an awaited collaborator pending
|
|
225
|
+
|
|
226
|
+
`createDeferred<T>()` from the core entry returns `{ promise, resolve, reject }`. Return `promise`
|
|
227
|
+
from the spy, start the operation, assert the intermediate state, then `resolve(value)` and await
|
|
228
|
+
the operation. `createDeferred()` defaults to `void`; `resolve()` then needs no argument.
|
|
229
|
+
Do not await the operation before resolving its dependency. No timer or `Promise.withResolvers`
|
|
230
|
+
is required. Use the runner's core entry (`/bun`, `/node`, `/rstest`) outside Vitest.
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { pendingRequestsReport,
|
|
2
|
-
export { disableTestBedDiagnostics, enableTestBedDiagnostics, formatSpecTiming, getTestBedTiming, instrumentTestBed, reportSpecTiming } from './chunk-
|
|
3
|
-
import {
|
|
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-
|
|
6
|
-
import './chunk-
|
|
7
|
-
import './chunk-
|
|
8
|
-
import './chunk-
|
|
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-
|
|
11
|
-
import { isAutoSpyLike } from './chunk-
|
|
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-
|
|
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
|
-
|
|
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 };
|