vitest-auto-spy 5.45.0 → 5.46.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/CHANGELOG.md +80 -1
- package/README.md +7 -4
- package/agent-docs/angular.md +5 -0
- package/agent-docs/doubles.md +4 -1
- package/agent-docs/errors.md +1 -0
- package/agent-docs/return-helpers.md +5 -1
- package/agent-docs/setup.md +5 -1
- package/dist/angular-http.d.ts +23 -37
- package/dist/angular-http.js +36 -8
- package/dist/angular-matchers.js +3 -8
- package/dist/chunk-B6JUBFBA.js +25 -0
- package/dist/cli.js +4 -1
- package/dist/console.d.ts +2 -1
- package/dist/dom-stubs.d.ts +13 -4
- package/dist/dom-stubs.js +9 -5
- package/dist/jasmine.js +4 -3
- package/package.json +1 -1
- package/skills/vitest-auto-spy/SKILL.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,84 @@ The latest released version here must always match the one published on
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [5.46.0] - 2026-09-27
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`stubAnimationFrame(...).lastHandle`: the handle the latest `requestAnimationFrame` returned.**
|
|
18
|
+
5.40.0 moved the stub's handles above 2^30 so a native frame's cancel could be told apart, and a
|
|
19
|
+
spec that asserted `expect(frames.cancelAnimationFrame).toHaveBeenLastCalledWith(1)` broke on the
|
|
20
|
+
upgrade with nothing in that entry saying so. `lastHandle` is `undefined` before the first request,
|
|
21
|
+
so the assertion reads `toHaveBeenLastCalledWith(frames.lastHandle)` and survives any numbering.
|
|
22
|
+
- **`injectHttpTesting()` from `/angular-http`: the `HttpTestingController` `provideHttpTesting()`
|
|
23
|
+
installed.** A spec of a service that returns an Observable has its request out before the method
|
|
24
|
+
returns, so it calls `expectOne` / `match` / `expectNone` synchronously, and `expectRequest` (which
|
|
25
|
+
ticks and flushes asynchronously) is not a drop-in there; such specs kept a
|
|
26
|
+
`TestBed.inject(HttpTestingController)` beside `...provideHttpTesting()`. Without the providers it
|
|
27
|
+
throws the message `expectRequest` gives, naming `provideHttpTesting()`.
|
|
28
|
+
- **`expectRequest(...).error(status, { error })`: the payload the `HttpErrorResponse` carries.** The
|
|
29
|
+
helper always failed the request with `new ProgressEvent('error')`, so a spec that pinned
|
|
30
|
+
`HttpErrorResponse.error` stayed on `HttpTestingController`. The default is unchanged. `error(0)` is
|
|
31
|
+
documented as the network failure it is.
|
|
32
|
+
- **`{ tick: false }` on `expectRequest` and `expectNoRequest`.** Both tick the app first, and a module
|
|
33
|
+
that provides a partial `DOCUMENT` double fails that tick inside Angular
|
|
34
|
+
(`inject(...).body?.querySelector is not a function`). The option skips the tick before the lookup and
|
|
35
|
+
after the answer, for an `HttpClient` call whose request is already out.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **`toHaveSignalValue` and `toHaveResourceValue` compare the contents of a `Set` and a `Map`.** They
|
|
40
|
+
called the matcher context's `equals` without `iterableEquality`, so a collection had no own keys to
|
|
41
|
+
compare and any two Sets, or any two Maps, were equal:
|
|
42
|
+
`expect(signal(new Set(['a']))).toHaveSignalValue(new Set(['b']), { strict: true })` passed, nested
|
|
43
|
+
in an object as well. Both now compare with the testers `toEqual` uses, and `{ strict: true }` with
|
|
44
|
+
those of `toStrictEqual` (array holes inside a collection and `ArrayBuffer` bytes included). The
|
|
45
|
+
non-strict branch also ignored testers registered through `expect.addEqualityTesters`; both modes
|
|
46
|
+
apply them now. An assertion on a Set- or Map-valued signal that passed only because of this can
|
|
47
|
+
newly fail.
|
|
48
|
+
- **A tick that fails on a doubled `DOCUMENT` says so.** `expectRequest` / `expectNoRequest` failed with
|
|
49
|
+
Angular's bare `TypeError`; when the module's `DOCUMENT` is not the real document, the error now names
|
|
50
|
+
it and `{ tick: false }`, with Angular's error as `cause`. A tick failure under the real document is
|
|
51
|
+
rethrown as it was.
|
|
52
|
+
- **`jasmine.mapContaining` / `setContaining` / `arrayWithExactContents` compare a nested `Set` or
|
|
53
|
+
`Map` by its contents**, for the same reason: `new Map([['a', new Set([1])]])` contained
|
|
54
|
+
`new Map([['a', new Set([2])]])`.
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
|
|
58
|
+
- **`stubAnimationFrame`'s spies are typed `Mock<RequestAnimationFrameFn>` /
|
|
59
|
+
`Mock<CancelAnimationFrameFn>`** instead of `(...args: any[]) => any`, so
|
|
60
|
+
`frames.requestAnimationFrame.mock.calls` compiles and is typed. Both function types are exported
|
|
61
|
+
from `/dom-stubs` and spelled without the DOM lib. A spec that called the spy with arguments
|
|
62
|
+
`requestAnimationFrame` does not take now fails to compile.
|
|
63
|
+
|
|
64
|
+
### Size
|
|
65
|
+
|
|
66
|
+
`/angular/matchers` **2799 → 2936 B** min+gzip (+137 B, +4.9 %) and +1.0 kB to import (22906 →
|
|
67
|
+
23887 B, one module more): the shared equality module the signal and resource matchers now use.
|
|
68
|
+
`/jasmine` 19161 → 19203 B (+42 B) and +1.3 kB to import (148816 → 150096 B, one module more): the
|
|
69
|
+
same module, for the collection matchers. `/angular-http` 3770 → 3997 B (+227 B, +6.0 %) and +0.9 kB
|
|
70
|
+
to import (31370 → 32313 B): `injectHttpTesting`, the `{ tick }` option, the doubled-`DOCUMENT`
|
|
71
|
+
message and the `error` payload. `/dom-stubs` +14 B.
|
|
72
|
+
|
|
73
|
+
### Documentation
|
|
74
|
+
|
|
75
|
+
- **The 5.40.0 handle range is a change a spec may have to act on.** A literal handle (`1`, `2`) in an
|
|
76
|
+
assertion against `frames.cancelAnimationFrame` fails from 5.40.0; assert against `frames.lastHandle`
|
|
77
|
+
or the value `requestAnimationFrame` returned. The frame page, `agent-docs/doubles.md`, the README
|
|
78
|
+
and the skill say so.
|
|
79
|
+
- **`SubjectOf` from `/angular` is still rxjs's `Subject` only with `vitest-auto-spy/rxjs` in the
|
|
80
|
+
program.** `agent-docs/return-helpers.md` and the API page recommended the `/angular` export for a
|
|
81
|
+
variable holding `returnSubject()` without repeating that; without the import it is `SubjectLike`,
|
|
82
|
+
so `next()` compiles and `pipe()` does not. `SubjectLike`'s member list in `agent-docs` gains
|
|
83
|
+
`unsubscribe`.
|
|
84
|
+
- **Asserting the errors `flushUnhandledObservableErrors()` returned, without `.map(({ error }) => error)`:**
|
|
85
|
+
`expect(flushUnhandledObservableErrors()).toMatchObject([{ error: new Error('502') }])` checks the
|
|
86
|
+
count and ignores `test`. The setup page and `agent-docs/setup.md` show it.
|
|
87
|
+
- **"Only these console channels" is `expect(Object.keys(consoleOutput())).toStrictEqual(['info'])`**,
|
|
88
|
+
keys in alphabetical order; `{ info: expect.any(Array) }` trips `no-unsafe-assignment`.
|
|
89
|
+
`agent-docs/setup.md` and the `consoleOutput` docs show it.
|
|
90
|
+
|
|
13
91
|
## [5.45.0] - 2026-09-27
|
|
14
92
|
|
|
15
93
|
### Added
|
|
@@ -5922,7 +6000,8 @@ moved under 1 %: `/angular` +192 B (+1.0 %) for the override queue and the HTTP
|
|
|
5922
6000
|
This copy ships with the package and carries 5.x only. Releases before 5.0.0 are in the
|
|
5923
6001
|
[full changelog](https://github.com/ASDAlexey/vitest-auto-spy/blob/master/CHANGELOG.md).
|
|
5924
6002
|
|
|
5925
|
-
[Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.
|
|
6003
|
+
[Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.46.0...HEAD
|
|
6004
|
+
[5.46.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.45.0...v5.46.0
|
|
5926
6005
|
[5.45.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.44.0...v5.45.0
|
|
5927
6006
|
[5.44.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.43.0...v5.44.0
|
|
5928
6007
|
[5.43.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.42.0...v5.43.0
|
package/README.md
CHANGED
|
@@ -1581,7 +1581,7 @@ Node / Bun / React / Vue project pulls **neither rxjs nor Angular into its runti
|
|
|
1581
1581
|
| `vitest-auto-spy/angular/diagnostics` | `enableAngularDiagnostics` and the whole TestBed timing family — a companion to `/angular`, no core re-export; moved off `/angular` in 5.21.0 | `@angular/core` | ✅ |
|
|
1582
1582
|
| `vitest-auto-spy/angular/doubles` | the `window` / `document` and Material-dialog doubles; registers the Vitest adapter, so its doubles spy out of the box; moved off `/angular` in 5.21.0 | `@angular/core` | ✅ |
|
|
1583
1583
|
| `vitest-auto-spy/angular/matchers` | `registerDirectiveMatchers`, `registerResourceMatchers`, `registerSignalMatchers` — a companion to `/angular`, no core re-export; moved off `/angular` in 5.21.0 | `@angular/core`, `@angular/platform-browser` | ✅ |
|
|
1584
|
-
| `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest`, `expectNoRequest`, `verifyNoPendingRequests` — the `httpResource()` / `HttpClient` recipe in two lines, settling included
|
|
1584
|
+
| `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest`, `expectNoRequest`, `verifyNoPendingRequests`, `injectHttpTesting` — the `httpResource()` / `HttpClient` recipe in two lines, settling included | `@angular/common` | ✅ |
|
|
1585
1585
|
| `vitest-auto-spy/angular-router` | `provideActivatedRoute`, `injectActivatedRoute`, `createActivatedRoute` — Angular's own `ActivatedRoute` over one record, streams and snapshot moved together; `provideRouterDouble` / `injectRouterDouble` for the `Router` beside it, with `collectRouterEvents()` asserting the events that follow; `provideLocationDouble` wrapping the `SpyLocation` Angular ships | `@angular/router`, `@angular/common` | ✅ |
|
|
1586
1586
|
| `vitest-auto-spy/signal-forms` | `createForm` — Angular's own `form()` built where it can inject — and `registerFormMatchers()` for `toHaveFieldErrors` | `@angular/forms` (>=22) | ✅ |
|
|
1587
1587
|
| `vitest-auto-spy/bun` | the same core, driven by Bun's `bun:test` mocks | `bun:test` | ✅ |
|
|
@@ -3139,7 +3139,9 @@ it('loads the products', async () => {
|
|
|
3139
3139
|
`expectRequest` ticks before it looks — an `httpResource()` has issued nothing until something does —
|
|
3140
3140
|
and `flush()` / `error()` settle before they resolve, which is why the value reads on the next line.
|
|
3141
3141
|
Match by URL (either `url` or `urlWithParams`), by `RegExp`, or by a predicate over the request, with
|
|
3142
|
-
`{ method }` to tell a read from a write. `
|
|
3142
|
+
`{ method }` to tell a read from a write. `error(0)` is a network failure, and
|
|
3143
|
+
`error(status, { error })` sets the payload the `HttpErrorResponse` carries. `{ tick: false }` skips
|
|
3144
|
+
the tick, for a module whose partial `DOCUMENT` double the tick cannot run against. `provideHttpTesting()` fails, by default, any test that
|
|
3143
3145
|
ends holding a request nothing answered; `provideHttpTesting({ verifyOnTeardown: false })` turns that
|
|
3144
3146
|
off, and `verifyNoPendingRequests()` runs the same check by hand. An interceptor under test goes in
|
|
3145
3147
|
the same spread — `provideHttpTesting({ interceptors: [authInterceptor] })`, or
|
|
@@ -3289,7 +3291,7 @@ single-purpose utility you can pick up independently — they all ride on the sa
|
|
|
3289
3291
|
| `stubAbortController()` | `/dom-stubs` | A realm-consistent `AbortController`, so `addEventListener(…, { signal })` works under jsdom + zone.js |
|
|
3290
3292
|
| `stubWebStorage(key?, opts?)` | `/dom-stubs` | An in-memory `localStorage` / `sessionStorage` for this test, with `snapshot()` to assert on ([details](#how-to-mock-localstorage-and-sessionstorage)) |
|
|
3291
3293
|
| `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)) |
|
|
3292
|
-
| `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
|
|
3294
|
+
| `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) |
|
|
3293
3295
|
| `stubElementRect(el, rect?)` | `/dom-stubs` | `getBoundingClientRect()` answering a real `DOMRect` built from a partial `DOMRectInit`; returns the undo, plus the installed spy as `.getBoundingClientRect` |
|
|
3294
3296
|
| `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 |
|
|
3295
3297
|
| `flushEventLoopUntil(isDone, opts?)` | core | Real turns until a condition holds — a `resource()` leaving `loading` — with a budget instead of a hang |
|
|
@@ -4317,7 +4319,7 @@ another, and the next one is in that same file.
|
|
|
4317
4319
|
| `stubAbortController()` | A realm-consistent `AbortController` / `AbortSignal` for jsdom + zone.js, statics (`abort`, `timeout`, `any`) included |
|
|
4318
4320
|
| `stubWebStorage(key?, opts?)` _(`/dom-stubs`)_ | An in-memory `localStorage` / `sessionStorage` a spec installs for itself, seeded or empty; `snapshot()` reads it back as a plain record |
|
|
4319
4321
|
| `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>` |
|
|
4320
|
-
| `stubAnimationFrame(opts?)` _(`/dom-stubs`)_ | Frames on the spec's schedule: `'immediate'` or `'queued'` + `flush()`, `pending`, both globals as spies; returns `AnimationFrameStub`
|
|
4322
|
+
| `stubAnimationFrame(opts?)` _(`/dom-stubs`)_ | Frames on the spec's schedule: `'immediate'` or `'queued'` + `flush()`, `pending`, `lastHandle`, both globals as spies; returns `AnimationFrameStub` |
|
|
4321
4323
|
| `stubElementRect(el, rect?)` _(`/dom-stubs`)_ | A box for `getBoundingClientRect()` — missing fields `0`, edges derived; returns `ElementRectRestore` (callable undo plus `.getBoundingClientRect`) |
|
|
4322
4324
|
| `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 |
|
|
4323
4325
|
| `flushEventLoop(turns?)` / `settleDynamicImport(load, turns?)` | Real event-loop turns under fake timers; wait for a dynamic `import()` |
|
|
@@ -4339,6 +4341,7 @@ another, and the next one is in that same file.
|
|
|
4339
4341
|
| `settleResource(resource, opts?)` _(Angular)_ | Tick until an `httpResource()` / `resource()` / `rxResource()` leaves `loading` |
|
|
4340
4342
|
| `provideHttpTesting(opts?)` / `expectRequest(matcher, opts?)` _(`/angular-http`)_ | `provideHttpClient(...features)` + `provideHttpClientTesting()` in one spread — `{ interceptors, features }` for the interceptors under test; find the one matching request and `flush` / `error` it with the settling included |
|
|
4341
4343
|
| `expectNoRequest(matcher?, opts?)` / `verifyNoPendingRequests(opts?)` _(`/angular-http`)_ | Assert that nothing was requested / that the test left nothing unanswered; `{ ignoreCancelled: true }` — also accepted by `provideHttpTesting({ verifyOnTeardown })` — forgives a request the code under test unsubscribed from |
|
|
4344
|
+
| `injectHttpTesting()` _(`/angular-http`)_ | The `HttpTestingController` `provideHttpTesting()` installed, for a synchronous `expectOne` / `match` / `expectNone` on an Observable service; names the missing providers when there are none |
|
|
4342
4345
|
| `provideActivatedRoute(init?)` / `injectActivatedRoute(injector?)` _(`/angular-router`)_ | An `ActivatedRoute` provider whose streams, `paramMap`s and snapshot share one record, and the handle whose setters move them together |
|
|
4343
4346
|
| `createActivatedRoute(init?)` _(`/angular-router`)_ | The same double without a `TestBed` — `.route` plus the setters |
|
|
4344
4347
|
| `provideRouterDouble(init?)` / `injectRouterDouble(injector?)` / `createRouterDouble(init?)` _(`/angular-router`)_ | A `Router` derived from one URL — `navigate` spies, `setUrl`, `emitNavigation`, `setCurrentNavigation`, and the router's own URL serialization |
|
package/agent-docs/angular.md
CHANGED
|
@@ -478,6 +478,7 @@ resource's **default** value and passes.
|
|
|
478
478
|
| `provideHttpTesting(opts?)` | `provideHttpClient(...features)` + `provideHttpClientTesting()`; `{ verifyOnTeardown }` defaults to `true`, and takes `{ ignoreCancelled }` instead of `false`; `{ interceptors: [fn] }` for functional interceptors under test, `{ features: [...] }` for any other `provideHttpClient()` feature |
|
|
479
479
|
| `expectRequest(matcher, opts?)` | tick, then the one match — `.request`, `.flush(body, opts?)`, `.error(status, opts?)` |
|
|
480
480
|
| `expectNoRequest(matcher?, opts?)` | tick, then assert nothing matched; no argument means "nothing at all was requested" |
|
|
481
|
+
| `injectHttpTesting()` | the `HttpTestingController` itself, for a synchronous `expectOne` / `match` / `expectNone` on an Observable service — no tick needed; throws the same missing-providers message |
|
|
481
482
|
| `verifyNoPendingRequests(opts?)` | the teardown check by hand, for mid-test use or a suite with `verifyOnTeardown: false`; `{ ignoreCancelled: true }` forgives a request the code under test unsubscribed from |
|
|
482
483
|
|
|
483
484
|
An interceptor under test goes into the same call, and the teardown check stays armed — do not
|
|
@@ -506,6 +507,10 @@ provided after it, so it wins over `withXhr()`.
|
|
|
506
507
|
|
|
507
508
|
`matcher` is a URL (matched against either `url` or `urlWithParams`), a `RegExp` over
|
|
508
509
|
`urlWithParams`, or a predicate `(request) => boolean`. `{ method }` is case-insensitive.
|
|
510
|
+
`.error(0)` is a network failure; `.error(status, { error })` sets the payload `HttpErrorResponse.error`
|
|
511
|
+
carries (default `new ProgressEvent('error')`). A module that provides a partial `DOCUMENT` double
|
|
512
|
+
fails Angular's tick (`inject(...).body?.querySelector is not a function`, reported as a doubled
|
|
513
|
+
`DOCUMENT`): pass `{ tick: false }` to `expectRequest` / `expectNoRequest` when the request is already out.
|
|
509
514
|
`flush()` and `error()` are `async` because settling requires letting a microtask run — `await` them,
|
|
510
515
|
and the next line reads the settled value.
|
|
511
516
|
|
package/agent-docs/doubles.md
CHANGED
|
@@ -156,7 +156,10 @@ running frame is queued in either mode, so a loop that requests its own next fra
|
|
|
156
156
|
per `flush()`; `frames.flushAll()` runs such a chain until nothing is pending (and throws after 1000
|
|
157
157
|
rounds, on a loop that never stops). A `cancelAnimationFrame` for a handle the stub did not issue — a
|
|
158
158
|
native frame requested before it was installed, such as zoneless Angular's scheduler frame — is
|
|
159
|
-
passed on to the real `cancelAnimationFrame`, so installing the stub after the render leaks no timer.
|
|
159
|
+
passed on to the real `cancelAnimationFrame`, so installing the stub after the render leaks no timer.
|
|
160
|
+
The stub's own handles start above 2^30 (since 5.40.0), so assert a cancel against the handle it
|
|
161
|
+
issued — `expect(frames.cancelAnimationFrame).toHaveBeenLastCalledWith(frames.lastHandle)`, or the
|
|
162
|
+
value `requestAnimationFrame` returned — never against a literal such as `1`. A callback that throws stops `flush()` (or propagates out of `requestAnimationFrame`
|
|
160
163
|
in `'immediate'` mode); pass `onError: (error) => { … }` to intercept it instead — rethrow from
|
|
161
164
|
inside to keep the default for an error you did not mean to swallow. `stubElementRect` returns a real
|
|
162
165
|
`DOMRect` per call, edges derived, plus the installed spy as `.getBoundingClientRect` for
|
package/agent-docs/errors.md
CHANGED
|
@@ -168,6 +168,7 @@ Part of the agent reference [`AGENTS.md`](../AGENTS.md), which maps every sectio
|
|
|
168
168
|
| `expectRequest: no GET …?page=2 — but GET …?page=3 was made; the query differs` | the same path, another query string | name the query the code sends, or match the path alone: `expectRequest('/api/products')` (§13) |
|
|
169
169
|
| `expectRequest: no request matched … — nothing was requested at all` | an `httpResource()` whose `request()` returned no URL yet, or an `HttpClient` call nothing subscribed to | set the signal the resource reads, or subscribe to the call (§13) |
|
|
170
170
|
| `this TestBed has no HttpTestingController` | `expectRequest` / `expectNoRequest` on a module that never got the testing backend | `TestBed.configureTestingModule({ providers: [...provideHttpTesting()] })` from `vitest-auto-spy/angular-http` (§13) |
|
|
171
|
+
| `TestBed.tick() failed, and this TestBed provides its own DOCUMENT` (cause: `inject(...).body?.querySelector is not a function`) | `expectRequest` / `expectNoRequest` ticked the app, and Angular's tick reads members a partial `DOCUMENT` double lacks | `expectRequest(url, { tick: false })` when the request is already out (an `HttpClient` call), or provide a real document |
|
|
171
172
|
| `X was never answered (end of "…")` / `N requests were never answered` | `provideHttpTesting({ verifyOnTeardown })` or `enableAngularDiagnostics({ pendingRequests })` — the test left a request open; the message names its URL and the test | answer it with the line the message prints (`await expectRequest(url).flush(body)`, `controller.expectOne(url).flush(body)` under diagnostics in a module built on `provideHttpClientTesting()`), or assert its absence (`expectNoRequest(url)`); a cancelled one is forgiven with the `ignoreCancelled` opt-in the message names (§13) |
|
|
172
173
|
| `injectActivatedRoute(): nothing provides ActivatedRoute here` | no `ActivatedRoute` provider in the injector it read | `provideActivatedRoute({ … })` from `vitest-auto-spy/angular-router` in `providers`; `injectActivatedRoute(fixture.debugElement.injector)` for a component-level one (§13) |
|
|
173
174
|
| `the ActivatedRoute here is … not one provideActivatedRoute() built` | a later provider of `ActivatedRoute` won — `provideRouter()`, `RouterModule`, a `useValue`, `provideAutoSpy(ActivatedRoute)` | list `provideActivatedRoute()` last, or drop the other provider (§13) |
|
|
@@ -29,7 +29,7 @@ produce `nextWith is not a function` with nothing pointing at the duplicate. `Pr
|
|
|
29
29
|
|
|
30
30
|
`returnSubject()` and `nextWithPerCall()` return `SubjectOf<T>` — rxjs's own `Subject<T>` wherever
|
|
31
31
|
`import 'vitest-auto-spy/rxjs'` is in the TypeScript program, the structural `SubjectLike<T>`
|
|
32
|
-
(`next` / `error` / `complete` / `asObservable` / `closed`) where it is not. The switch is one
|
|
32
|
+
(`next` / `error` / `complete` / `unsubscribe` / `asObservable` / `closed`) where it is not. The switch is one
|
|
33
33
|
augmentable interface, `AutoSpyRxjsTypes<T>`, which `/rxjs` fills in with `subject: Subject<T>`;
|
|
34
34
|
augment it yourself only to plug in a different subject type. If
|
|
35
35
|
`const s: Subject<T> = spy.m.returnSubject()` fails to compile, the import is missing from the
|
|
@@ -48,6 +48,10 @@ the Subject it returns is dropped. Keep it when the spec pushes through it later
|
|
|
48
48
|
`void spy.items$.returnSubject();` when it never does — the rule's default `ignoreVoid: true` accepts
|
|
49
49
|
that.
|
|
50
50
|
|
|
51
|
+
`SubjectOf` is rxjs's `Subject` only where `import 'vitest-auto-spy/rxjs'` is in the program — the
|
|
52
|
+
`/angular` export does not add it. Without it the variable is `SubjectLike`: `next` / `error` /
|
|
53
|
+
`complete` compile, `pipe()` or an argument typed `Subject<T>` does not.
|
|
54
|
+
|
|
51
55
|
```ts
|
|
52
56
|
// argument dispatch — other arguments return undefined
|
|
53
57
|
users.getName.calledWith(1).mockReturnValue('Ada');
|
package/agent-docs/setup.md
CHANGED
|
@@ -92,7 +92,9 @@ error as `cause`; one scheduled outside a test fails the file at the sweep. The
|
|
|
92
92
|
it is still installed (`globalFakeTimers` is); a rethrow `config.onUnhandledError` takes reports
|
|
93
93
|
nothing. Fix it where the stream is subscribed — an error callback or `catchError` — or assert the
|
|
94
94
|
error. `flushUnhandledObservableErrors()` is the same check by hand: it returns `{ error, test }` (or
|
|
95
|
-
`{ error, outsideTest }`) for each rethrow that threw
|
|
95
|
+
`{ error, outsideTest }`) for each rethrow that threw; assert the errors alone with
|
|
96
|
+
`expect(flushUnhandledObservableErrors()).toMatchObject([{ error: new Error('502') }])` — it checks the
|
|
97
|
+
count and ignores `test`, with no `.map(({ error }) => error)`.
|
|
96
98
|
|
|
97
99
|
**`countStrayTimers()` cannot see a timer scheduled under fake timers, and that does not compose
|
|
98
100
|
away.** `vi.useFakeTimers()` assigns its own `setTimeout` over the tracking wrapper, so everything
|
|
@@ -768,6 +770,8 @@ Docs: https://asdalexey.github.io/vitest-auto-spy/utilities/setup#_16-console-ou
|
|
|
768
770
|
- **Pin everything a test logged in one assertion:** `consoleOutput()` from `/console` returns
|
|
769
771
|
`{ error?, warn?, info?, log?, debug?, trace? }`, each the argument lists of its calls, only the
|
|
770
772
|
channels written to — `expect(consoleOutput()).toStrictEqual({ info: [['done']] })`; `{}` is silence.
|
|
773
|
+
"Only these channels, whatever they said" is `expect(Object.keys(consoleOutput())).toStrictEqual(['info'])`
|
|
774
|
+
(keys in alphabetical order), which `no-unsafe-assignment` accepts where `{ info: expect.any(Array) }` does not.
|
|
771
775
|
It throws while no spy is on `console`, so it never reports silence it did not hear.
|
|
772
776
|
- The wrapper forwards every call unchanged, so Vitest's attribution and `onConsoleLog` still work.
|
|
773
777
|
`guardStrayConsole(reaction)` registers the same guard on its own.
|
package/dist/angular-http.d.ts
CHANGED
|
@@ -1,45 +1,18 @@
|
|
|
1
1
|
import { HttpInterceptorFn, HttpFeature, HttpFeatureKind, HttpRequest } from '@angular/common/http';
|
|
2
|
-
import { TestRequest } from '@angular/common/http/testing';
|
|
2
|
+
import { TestRequest, HttpTestingController } from '@angular/common/http/testing';
|
|
3
3
|
import { EnvironmentProviders, Provider } from '@angular/core';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* `provideHttpTesting()` + `expectRequest()` — the `httpResource()` and `HttpClient` dance in two
|
|
7
|
-
* lines.
|
|
8
|
-
*
|
|
9
|
-
* Answering one request from a zoneless spec takes six steps today, and the order of them is not
|
|
10
|
-
* guessable: tick (an `httpResource()` issues *nothing* until something does), inject the
|
|
11
|
-
* `HttpTestingController`, `expectOne`, `flush`, let one microtask run so the response reaches the
|
|
12
|
-
* resource, tick again so what read it is up to date. Miss the first tick and `expectOne` finds no
|
|
13
|
-
* request; miss the microtask and the assertion reads the resource's *default* value — a green test
|
|
14
|
-
* that asserts nothing until the day the default changes.
|
|
15
|
-
*
|
|
16
|
-
* ```ts
|
|
17
|
-
* TestBed.configureTestingModule({ providers: [...provideHttpTesting()] });
|
|
18
|
-
*
|
|
19
|
-
* const products = TestBed.runInInjectionContext(() => httpResource<Product[]>(() => '/api/products'));
|
|
20
|
-
*
|
|
21
|
-
* await expectRequest('/api/products').flush([product]);
|
|
22
|
-
*
|
|
23
|
-
* expect(products.value()).toEqual([product]); // no tick, no microtask, no detectChanges
|
|
24
|
-
* ```
|
|
25
|
-
*
|
|
26
|
-
* **This module is why `vitest-auto-spy/angular-http` is its own entry.** It is the only file in
|
|
27
|
-
* the package that imports `@angular/common`, and `vitest-auto-spy/angular` has to keep loading in
|
|
28
|
-
* a project that has `@angular/core` and not `@angular/common` — the same invariant that keeps
|
|
29
|
-
* rxjs behind `vitest-auto-spy/rxjs`. The optional peer is paid for by the suites that import this
|
|
30
|
-
* entry and by nobody else.
|
|
31
|
-
*
|
|
32
|
-
* It cooperates with `enableAngularDiagnostics({ pendingRequests })` rather than competing: both
|
|
33
|
-
* take the open requests with `match(() => true)`, which is one-shot, so whichever looks first
|
|
34
|
-
* owns them and one unanswered request is never reported twice.
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
5
|
/** How a request is named: a URL, a pattern, or a question asked of the request itself. */
|
|
38
6
|
type RequestMatcher = RegExp | string | ((request: HttpRequest<unknown>) => boolean);
|
|
39
7
|
/** Options for {@link expectRequest} and {@link expectNoRequest}. */
|
|
40
8
|
interface ExpectRequestOptions {
|
|
41
9
|
/** Narrow to one verb, for a URL that is both read and written in the same test. Case-insensitive. */
|
|
42
10
|
method?: string;
|
|
11
|
+
/**
|
|
12
|
+
* `false` skips the `TestBed.tick()` before the lookup and after the answer, for a module whose
|
|
13
|
+
* doubled `DOCUMENT` the app's tick cannot run against. The request must already be out. Default `true`.
|
|
14
|
+
*/
|
|
15
|
+
tick?: boolean;
|
|
43
16
|
}
|
|
44
17
|
/** Options for {@link provideHttpTesting}. */
|
|
45
18
|
interface HttpTestingOptions {
|
|
@@ -73,8 +46,13 @@ interface HttpTestingOptions {
|
|
|
73
46
|
type ResponseBody = Parameters<TestRequest['flush']>[0];
|
|
74
47
|
/** `headers` / `status` / `statusText`, exactly as `TestRequest#flush` takes them. */
|
|
75
48
|
type FlushOptions = NonNullable<Parameters<TestRequest['flush']>[1]>;
|
|
76
|
-
/**
|
|
77
|
-
|
|
49
|
+
/**
|
|
50
|
+
* `headers` / `statusText` for {@link RequestExpectation.error} — the status is its first argument —
|
|
51
|
+
* and `error`, the payload `HttpErrorResponse.error` carries. Default `new ProgressEvent('error')`.
|
|
52
|
+
*/
|
|
53
|
+
type RequestErrorOptions = Omit<NonNullable<Parameters<TestRequest['error']>[1]>, 'status'> & {
|
|
54
|
+
error?: Parameters<TestRequest['error']>[0];
|
|
55
|
+
};
|
|
78
56
|
/** The one request {@link expectRequest} found, and the two ways to answer it. */
|
|
79
57
|
interface RequestExpectation {
|
|
80
58
|
/** The request as the code under test sent it — URL, method, headers, body. */
|
|
@@ -84,7 +62,10 @@ interface RequestExpectation {
|
|
|
84
62
|
* reads it has been ticked.
|
|
85
63
|
*/
|
|
86
64
|
flush(body: ResponseBody, options?: FlushOptions): Promise<void>;
|
|
87
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Fail the request with an HTTP status, then settle the same way {@link RequestExpectation.flush} does.
|
|
67
|
+
* Status `0` is a network failure: no response reached the client.
|
|
68
|
+
*/
|
|
88
69
|
error(status: number, options?: RequestErrorOptions): Promise<void>;
|
|
89
70
|
}
|
|
90
71
|
/**
|
|
@@ -148,5 +129,10 @@ declare function expectNoRequest(matcher?: RequestMatcher, options?: ExpectReque
|
|
|
148
129
|
declare function verifyNoPendingRequests(options?: {
|
|
149
130
|
ignoreCancelled?: boolean;
|
|
150
131
|
}): void;
|
|
132
|
+
/**
|
|
133
|
+
* The `HttpTestingController` `provideHttpTesting()` installed, for a synchronous `expectOne` / `match` /
|
|
134
|
+
* `expectNone` on an Observable service. Throws with the missing providers named when there is none.
|
|
135
|
+
*/
|
|
136
|
+
declare function injectHttpTesting(): HttpTestingController;
|
|
151
137
|
|
|
152
|
-
export { type ExpectRequestOptions, type FlushOptions, type HttpTestingOptions, type RequestErrorOptions, type RequestExpectation, type RequestMatcher, type ResponseBody, expectNoRequest, expectRequest, provideHttpTesting, verifyNoPendingRequests };
|
|
138
|
+
export { type ExpectRequestOptions, type FlushOptions, type HttpTestingOptions, type RequestErrorOptions, type RequestExpectation, type RequestMatcher, type ResponseBody, expectNoRequest, expectRequest, injectHttpTesting, provideHttpTesting, verifyNoPendingRequests };
|
package/dist/angular-http.js
CHANGED
|
@@ -5,6 +5,7 @@ import './chunk-HVNKTPKG.js';
|
|
|
5
5
|
import { count, taskName } from './chunk-DYAS4OK4.js';
|
|
6
6
|
import { angularHttpNoRequest, angularHttpPending, angularHttpController, angularHttpExpectRequest } from './chunk-5FQZGJLJ.js';
|
|
7
7
|
import { withDocs } from './chunk-ENCEEH23.js';
|
|
8
|
+
import { DOCUMENT } from '@angular/common';
|
|
8
9
|
import { withInterceptors, provideHttpClient } from '@angular/common/http';
|
|
9
10
|
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
|
|
10
11
|
import { InjectionToken, provideEnvironmentInitializer } from '@angular/core';
|
|
@@ -166,15 +167,37 @@ Narrow it: \`expectRequest(url, { method: "POST" })\`, name the full \`urlWithPa
|
|
|
166
167
|
)
|
|
167
168
|
);
|
|
168
169
|
}
|
|
169
|
-
async function settle() {
|
|
170
|
+
async function settle(tick) {
|
|
170
171
|
for (let round = 0; round < SETTLE_ROUNDS; round += 1) {
|
|
171
172
|
await Promise.resolve();
|
|
173
|
+
if (tick) {
|
|
174
|
+
tickApp("expectRequest");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function tickApp(caller) {
|
|
179
|
+
try {
|
|
172
180
|
flushEffects();
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (TestBed.inject(DOCUMENT) === globalThis.document) {
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
throw new Error(
|
|
186
|
+
withDocs(
|
|
187
|
+
`[vitest-auto-spy] ${caller}: TestBed.tick() failed, and this TestBed provides its own DOCUMENT \u2014 Angular reads the real document's members while it ticks, which a partial double does not have.
|
|
188
|
+
Pass { tick: false } to answer without ticking (the request must already be out), or provide a DOCUMENT that is a real document.`,
|
|
189
|
+
angularHttpExpectRequest
|
|
190
|
+
),
|
|
191
|
+
{ cause: error }
|
|
192
|
+
);
|
|
173
193
|
}
|
|
174
194
|
}
|
|
175
195
|
function expectRequest(matcher, options = {}) {
|
|
176
196
|
const controller = readController("expectRequest");
|
|
177
|
-
|
|
197
|
+
const tick = options.tick ?? true;
|
|
198
|
+
if (tick) {
|
|
199
|
+
tickApp("expectRequest");
|
|
200
|
+
}
|
|
178
201
|
const [first, ...rest] = controller.match(toPredicate(matcher, options));
|
|
179
202
|
if (first === void 0) {
|
|
180
203
|
throw noMatch(controller, matcher, options);
|
|
@@ -186,17 +209,19 @@ function expectRequest(matcher, options = {}) {
|
|
|
186
209
|
request: first.request,
|
|
187
210
|
flush: async (body, flushOptions) => {
|
|
188
211
|
first.flush(body, flushOptions);
|
|
189
|
-
await settle();
|
|
212
|
+
await settle(tick);
|
|
190
213
|
},
|
|
191
|
-
error: async (status, errorOptions) => {
|
|
192
|
-
first.error(
|
|
193
|
-
await settle();
|
|
214
|
+
error: async (status, { error = new ProgressEvent("error"), ...errorOptions } = {}) => {
|
|
215
|
+
first.error(error, { ...errorOptions, status });
|
|
216
|
+
await settle(tick);
|
|
194
217
|
}
|
|
195
218
|
};
|
|
196
219
|
}
|
|
197
220
|
function expectNoRequest(matcher = () => true, options = {}) {
|
|
198
221
|
const controller = readController("expectNoRequest");
|
|
199
|
-
|
|
222
|
+
if (options.tick ?? true) {
|
|
223
|
+
tickApp("expectNoRequest");
|
|
224
|
+
}
|
|
200
225
|
const matched = controller.match(toPredicate(matcher, options));
|
|
201
226
|
if (matched.length === 0) {
|
|
202
227
|
return;
|
|
@@ -212,6 +237,9 @@ Something the test did asked for data it was asserted not to need \u2014 a resou
|
|
|
212
237
|
function verifyNoPendingRequests(options = {}) {
|
|
213
238
|
verifyPending(options.ignoreCancelled === true);
|
|
214
239
|
}
|
|
240
|
+
function injectHttpTesting() {
|
|
241
|
+
return readController("injectHttpTesting");
|
|
242
|
+
}
|
|
215
243
|
function pendingOf(request) {
|
|
216
244
|
return { method: request.request.method, urlWithParams: request.request.urlWithParams, cancelled: request.cancelled };
|
|
217
245
|
}
|
|
@@ -228,4 +256,4 @@ function verifyPending(ignoreCancelled, test) {
|
|
|
228
256
|
}
|
|
229
257
|
}
|
|
230
258
|
|
|
231
|
-
export { expectNoRequest, expectRequest, provideHttpTesting, verifyNoPendingRequests };
|
|
259
|
+
export { expectNoRequest, expectRequest, injectHttpTesting, provideHttpTesting, verifyNoPendingRequests };
|
package/dist/angular-matchers.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { strictEquals, looseEquals } from './chunk-B6JUBFBA.js';
|
|
1
2
|
import { sourceClassName, count } from './chunk-DYAS4OK4.js';
|
|
2
3
|
import { angularDirectiveApplied } from './chunk-5FQZGJLJ.js';
|
|
3
4
|
import { withDocs } from './chunk-ENCEEH23.js';
|
|
@@ -205,7 +206,7 @@ function toHaveResourceValue(received, expected) {
|
|
|
205
206
|
};
|
|
206
207
|
}
|
|
207
208
|
const actual = received.value();
|
|
208
|
-
const pass = this
|
|
209
|
+
const pass = looseEquals(this, actual, expected);
|
|
209
210
|
return {
|
|
210
211
|
pass,
|
|
211
212
|
actual,
|
|
@@ -243,12 +244,6 @@ function registerResourceMatchers() {
|
|
|
243
244
|
function isSpy(received) {
|
|
244
245
|
return "mock" in received || "calls" in received;
|
|
245
246
|
}
|
|
246
|
-
function sameClass(a, b) {
|
|
247
|
-
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null || Array.isArray(a) && Array.isArray(b)) {
|
|
248
|
-
return void 0;
|
|
249
|
-
}
|
|
250
|
-
return Object.getPrototypeOf(a) === Object.getPrototypeOf(b) ? void 0 : false;
|
|
251
|
-
}
|
|
252
247
|
function registerSignalMatchers() {
|
|
253
248
|
expect.extend({
|
|
254
249
|
toHaveSignalValue(received, expected, { strict = false } = {}) {
|
|
@@ -261,7 +256,7 @@ function registerSignalMatchers() {
|
|
|
261
256
|
);
|
|
262
257
|
}
|
|
263
258
|
const actual = received();
|
|
264
|
-
const pass = strict ?
|
|
259
|
+
const pass = strict ? strictEquals(this, actual, expected) : looseEquals(this, actual, expected);
|
|
265
260
|
return {
|
|
266
261
|
pass,
|
|
267
262
|
actual,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/lib/matcher-equality.ts
|
|
2
|
+
function sameClass(a, b) {
|
|
3
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null || Array.isArray(a) && Array.isArray(b)) {
|
|
4
|
+
return void 0;
|
|
5
|
+
}
|
|
6
|
+
return Object.getPrototypeOf(a) === Object.getPrototypeOf(b) ? void 0 : false;
|
|
7
|
+
}
|
|
8
|
+
function arrayBufferEquality(a, b) {
|
|
9
|
+
if (!(a instanceof ArrayBuffer) || !(b instanceof ArrayBuffer)) {
|
|
10
|
+
return void 0;
|
|
11
|
+
}
|
|
12
|
+
const left = new Uint8Array(a);
|
|
13
|
+
const right = new Uint8Array(b);
|
|
14
|
+
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
15
|
+
}
|
|
16
|
+
function looseEquals(context, actual, expected) {
|
|
17
|
+
return context.equals(actual, expected, [...context.customTesters, context.utils.iterableEquality]);
|
|
18
|
+
}
|
|
19
|
+
function strictEquals(context, actual, expected) {
|
|
20
|
+
const testers = [...context.customTesters, context.utils.iterableEquality, sameClass, arrayBufferEquality];
|
|
21
|
+
const sparseArrayEquality = (a, b) => Array.isArray(a) && Array.isArray(b) ? context.equals(a, b, testers, true) && context.equals(Object.keys(a), Object.keys(b)) : void 0;
|
|
22
|
+
return context.equals(actual, expected, [...testers, sparseArrayEquality], true);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { looseEquals, strictEquals };
|
package/dist/cli.js
CHANGED
|
@@ -3865,7 +3865,7 @@ function checkForeignPragma(graph) {
|
|
|
3865
3865
|
}
|
|
3866
3866
|
|
|
3867
3867
|
// src/cli/checks/export-map.generated.ts
|
|
3868
|
-
var EXPORT_MAP_VERSION = "5.
|
|
3868
|
+
var EXPORT_MAP_VERSION = "5.45.0";
|
|
3869
3869
|
var ENTRY_SPECIFIERS = "vitest-auto-spy vitest-auto-spy/bun vitest-auto-spy/bun-angular vitest-auto-spy/node vitest-auto-spy/rstest vitest-auto-spy/rxjs vitest-auto-spy/console vitest-auto-spy/dom-stubs vitest-auto-spy/diagnostics vitest-auto-spy/jasmine vitest-auto-spy/jasmine-compat vitest-auto-spy/observer-spy vitest-auto-spy/angular vitest-auto-spy/angular/diagnostics vitest-auto-spy/angular/doubles vitest-auto-spy/angular/matchers vitest-auto-spy/angular-http vitest-auto-spy/angular-router vitest-auto-spy/signal-forms vitest-auto-spy/nestjs vitest-auto-spy/react vitest-auto-spy/vue vitest-auto-spy/svelte vitest-auto-spy/setup vitest-auto-spy/zone vitest-auto-spy/eslint-plugin vitest-auto-spy/perf-reporter";
|
|
3870
3870
|
var EXPORTED_BY = {
|
|
3871
3871
|
AccessorImplementations: "0 1 2 3 4 12 20 21 22",
|
|
@@ -3925,6 +3925,7 @@ var EXPORTED_BY = {
|
|
|
3925
3925
|
BlockNetworkOptions: "23",
|
|
3926
3926
|
CallbackSubscribable: "0 1 2 3 4 12 20 21 22",
|
|
3927
3927
|
CallLog: "0 1 2 3 4 20 21 22",
|
|
3928
|
+
CancelAnimationFrameFn: "7",
|
|
3928
3929
|
cancelStrayTimers: "23",
|
|
3929
3930
|
captureArg: "0 1 2 3 4 20 21 22",
|
|
3930
3931
|
CaptureArgOptions: "0 1 2 3 4 20 21 22",
|
|
@@ -4058,6 +4059,7 @@ var EXPORTED_BY = {
|
|
|
4058
4059
|
hostElement: "2 12",
|
|
4059
4060
|
HttpTestingOptions: "16",
|
|
4060
4061
|
injectActivatedRoute: "17",
|
|
4062
|
+
injectHttpTesting: "16",
|
|
4061
4063
|
InjectionLog: "12 19",
|
|
4062
4064
|
injectLocationDouble: "17",
|
|
4063
4065
|
injectMatDialogRef: "14",
|
|
@@ -4182,6 +4184,7 @@ var EXPORTED_BY = {
|
|
|
4182
4184
|
RenderShallowOptions: "2 12",
|
|
4183
4185
|
reportPropsOutsideHooks: "0 1 2 3 4 20 21 22",
|
|
4184
4186
|
reportSpecTiming: "13",
|
|
4187
|
+
RequestAnimationFrameFn: "7",
|
|
4185
4188
|
RequestErrorOptions: "16",
|
|
4186
4189
|
RequestExpectation: "16",
|
|
4187
4190
|
RequestMatcher: "16",
|
package/dist/console.d.ts
CHANGED
|
@@ -50,7 +50,7 @@ declare global {
|
|
|
50
50
|
declare function installConsoleSpies(): ConsoleSpies;
|
|
51
51
|
/** A console method that writes, as `consoleOutput()` keys it. */
|
|
52
52
|
type ConsoleChannel = 'debug' | 'error' | 'info' | 'log' | 'trace' | 'warn';
|
|
53
|
-
/** What a test wrote, per channel: the arguments of each call. A channel nothing wrote to is absent. */
|
|
53
|
+
/** What a test wrote, per channel: the arguments of each call. A channel nothing wrote to is absent; keys come in alphabetical order. */
|
|
54
54
|
type ConsoleOutput = Partial<Record<ConsoleChannel, unknown[][]>>;
|
|
55
55
|
/**
|
|
56
56
|
* Everything the console spies recorded, as one value. Only the channels written to appear, so an
|
|
@@ -61,6 +61,7 @@ type ConsoleOutput = Partial<Record<ConsoleChannel, unknown[][]>>;
|
|
|
61
61
|
* cli.run(['--dry-run']);
|
|
62
62
|
* expect(consoleOutput()).toStrictEqual({ info: [['done']] });
|
|
63
63
|
* expect(consoleOutput()).toStrictEqual({}); // wrote nothing at all
|
|
64
|
+
* expect(Object.keys(consoleOutput())).toStrictEqual(['info']); // only info, whatever it said
|
|
64
65
|
* ```
|
|
65
66
|
*/
|
|
66
67
|
declare function consoleOutput(): ConsoleOutput;
|
package/dist/dom-stubs.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { F as Func } from './types-Dxw-oWfr.js';
|
|
2
2
|
export { W as WebStorageKey, a as WebStorageStub, b as WebStorageStubOptions, s as stubWebStorage } from './web-storage-dwQ9E7Lm.js';
|
|
3
|
+
import { Mock } from 'vitest';
|
|
3
4
|
import { R as RestoreProp } from './prop-mock-WOpRQPUc.js';
|
|
4
|
-
import 'vitest';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* A host-runner mock function: a callable spy (Vitest `vi.fn()`, Bun `mock()`,
|
|
@@ -196,14 +196,23 @@ interface AnimationFrameStubOptions {
|
|
|
196
196
|
*/
|
|
197
197
|
onError?: (error: unknown) => void;
|
|
198
198
|
}
|
|
199
|
+
/** `requestAnimationFrame` as the stub installs it, spelled without the DOM lib. */
|
|
200
|
+
type RequestAnimationFrameFn = (callback: (timestamp: number) => void) => number;
|
|
201
|
+
/** `cancelAnimationFrame` as the stub installs it. */
|
|
202
|
+
type CancelAnimationFrameFn = (handle: number) => void;
|
|
199
203
|
/** The handle {@link stubAnimationFrame} returns. */
|
|
200
204
|
interface AnimationFrameStub {
|
|
201
205
|
/** How many requested frames have not run yet. */
|
|
202
206
|
readonly pending: number;
|
|
207
|
+
/**
|
|
208
|
+
* The handle the latest `requestAnimationFrame` call returned, or `undefined` before the first one.
|
|
209
|
+
* Handles start above 2^30, so assert against this rather than a literal such as `1`.
|
|
210
|
+
*/
|
|
211
|
+
readonly lastHandle: number | undefined;
|
|
203
212
|
/** The spy installed as `requestAnimationFrame`. */
|
|
204
|
-
readonly requestAnimationFrame:
|
|
213
|
+
readonly requestAnimationFrame: Mock<RequestAnimationFrameFn>;
|
|
205
214
|
/** The spy installed as `cancelAnimationFrame`. */
|
|
206
|
-
readonly cancelAnimationFrame:
|
|
215
|
+
readonly cancelAnimationFrame: Mock<CancelAnimationFrameFn>;
|
|
207
216
|
/**
|
|
208
217
|
* Run every frame requested so far, as one browser frame. A frame requested from inside one of
|
|
209
218
|
* them waits for the next `flush()`, as it waits for the next frame in a browser.
|
|
@@ -458,4 +467,4 @@ interface ResizeEntryRect {
|
|
|
458
467
|
*/
|
|
459
468
|
declare function resizeEntry(target: Element, rect?: ResizeEntryRect): ResizeObserverEntry;
|
|
460
469
|
|
|
461
|
-
export { type AnimationFrameMode, type AnimationFrameStub, type AnimationFrameStubOptions, type ElementRectRestore, type IntersectionEntryOverrides, type IntersectionObserverStubOptions, type MediaElementState, type MediaElementStub, type MediaElementStubOptions, type MutationRecordInit, type ObserverGlobal, type ObserverInstance, type ObserverStub, type ObserverStubOptions, type ResizeEntryRect, type WorkerInstance, type WorkerScript, type WorkerStub, type WorkerStubOptions, intersectionEntry, mutationRecord, resizeEntry, stubAbortController, stubAnimationFrame, stubElementRect, stubIntersectionObserver, stubMediaElement, stubMutationObserver, stubObserver, stubResizeObserver, stubWorker };
|
|
470
|
+
export { type AnimationFrameMode, type AnimationFrameStub, type AnimationFrameStubOptions, type CancelAnimationFrameFn, type ElementRectRestore, type IntersectionEntryOverrides, type IntersectionObserverStubOptions, type MediaElementState, type MediaElementStub, type MediaElementStubOptions, type MutationRecordInit, type ObserverGlobal, type ObserverInstance, type ObserverStub, type ObserverStubOptions, type RequestAnimationFrameFn, type ResizeEntryRect, type WorkerInstance, type WorkerScript, type WorkerStub, type WorkerStubOptions, intersectionEntry, mutationRecord, resizeEntry, stubAbortController, stubAnimationFrame, stubElementRect, stubIntersectionObserver, stubMediaElement, stubMutationObserver, stubObserver, stubResizeObserver, stubWorker };
|
package/dist/dom-stubs.js
CHANGED
|
@@ -1279,18 +1279,22 @@ function stubAnimationFrame(options = {}) {
|
|
|
1279
1279
|
get pending() {
|
|
1280
1280
|
return queue.size;
|
|
1281
1281
|
},
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
flush,
|
|
1285
|
-
flushAll: (timestamp) => {
|
|
1286
|
-
flushUntilIdle(queue, () => flush(timestamp));
|
|
1282
|
+
get lastHandle() {
|
|
1283
|
+
return lastId === FIRST_HANDLE ? void 0 : lastId;
|
|
1287
1284
|
},
|
|
1285
|
+
requestAnimationFrame: asRunnerMock2(request),
|
|
1286
|
+
cancelAnimationFrame: asRunnerMock2(cancel),
|
|
1287
|
+
flush,
|
|
1288
|
+
flushAll: (timestamp) => flushUntilIdle(queue, () => flush(timestamp)),
|
|
1288
1289
|
restore() {
|
|
1289
1290
|
[...restores].reverse().forEach((undo) => undo());
|
|
1290
1291
|
queue.clear();
|
|
1291
1292
|
}
|
|
1292
1293
|
};
|
|
1293
1294
|
}
|
|
1295
|
+
function asRunnerMock2(mock) {
|
|
1296
|
+
return mock;
|
|
1297
|
+
}
|
|
1294
1298
|
function runQueued(queue, run) {
|
|
1295
1299
|
for (const handle of [...queue.keys()]) {
|
|
1296
1300
|
const callback = queue.get(handle);
|
package/dist/jasmine.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { enableJasmineCompat } from './chunk-3ZBRBPOL.js';
|
|
2
2
|
export { enableJasmineCompat } from './chunk-3ZBRBPOL.js';
|
|
3
|
+
import { looseEquals } from './chunk-B6JUBFBA.js';
|
|
3
4
|
import { createSpyFromClass } from './chunk-I6CB4CNT.js';
|
|
4
5
|
import { useVitestAdapter } from './chunk-IB72SVKT.js';
|
|
5
6
|
import './chunk-QGLUX55Q.js';
|
|
@@ -174,7 +175,7 @@ function registerValueMatchers() {
|
|
|
174
175
|
function registerCollectionMatchers() {
|
|
175
176
|
expect.extend({
|
|
176
177
|
jasmineMapContaining(received, sample) {
|
|
177
|
-
const missing = received instanceof Map ? mapMissing(received, sample,
|
|
178
|
+
const missing = received instanceof Map ? mapMissing(received, sample, (a, b) => looseEquals(this, a, b)) : [];
|
|
178
179
|
const pass = received instanceof Map && missing.length === 0;
|
|
179
180
|
return {
|
|
180
181
|
pass,
|
|
@@ -182,7 +183,7 @@ function registerCollectionMatchers() {
|
|
|
182
183
|
};
|
|
183
184
|
},
|
|
184
185
|
jasmineSetContaining(received, sample) {
|
|
185
|
-
const missing = received instanceof Set ? setMissing(received, sample,
|
|
186
|
+
const missing = received instanceof Set ? setMissing(received, sample, (a, b) => looseEquals(this, a, b)) : [];
|
|
186
187
|
const pass = received instanceof Set && missing.length === 0;
|
|
187
188
|
return {
|
|
188
189
|
pass,
|
|
@@ -190,7 +191,7 @@ function registerCollectionMatchers() {
|
|
|
190
191
|
};
|
|
191
192
|
},
|
|
192
193
|
jasmineArrayWithExactContents(received, sample) {
|
|
193
|
-
const diff = Array.isArray(received) ? exactContentsDiff(received, sample,
|
|
194
|
+
const diff = Array.isArray(received) ? exactContentsDiff(received, sample, (a, b) => looseEquals(this, a, b)) : { missing: [], extra: [] };
|
|
194
195
|
const pass = Array.isArray(received) && diff.missing.length === 0 && diff.extra.length === 0;
|
|
195
196
|
return {
|
|
196
197
|
pass,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-auto-spy",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.46.0",
|
|
4
4
|
"description": "Auto-generate fully-typed test spies from a class — across Vitest, Bun, node:test and Rstest, with Angular/NestJS/React/Vue/Svelte recipes. Drop-in replacement for jest-auto-spies and jasmine-auto-spies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auto-mock",
|
|
@@ -187,7 +187,7 @@ it('loads', async () => {
|
|
|
187
187
|
| ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
188
188
|
| a `signal()` / `computed()` field on the class under test | `mockSignalProp(obj, prop, initial)` — writes through a writable one, `asReadonly()` included; an `input()` is refused |
|
|
189
189
|
| a resource field, when the HTTP round trip is not the point | `mockResourceProp(obj, prop, initial)` — `set` / `fail` / `loading`; `value()` after `fail()` throws |
|
|
190
|
-
| the HTTP round trip _is_ the point | `expectRequest(url).flush(body)` — `/angular-http`, settling included
|
|
190
|
+
| the HTTP round trip _is_ the point | `expectRequest(url).flush(body)` — `/angular-http`, settling included; a synchronous `expectOne` on an Observable service: `injectHttpTesting()` |
|
|
191
191
|
| an interceptor under test: `provideHttpClient(withInterceptors([...]))` + `provideHttpClientTesting()` | `provideHttpTesting({ interceptors: [fn], features: [withInterceptorsFromDi()] })` — keeps the end-of-test check |
|
|
192
192
|
| a component or service that reads `ActivatedRoute` | `provideActivatedRoute({ params })`, then `injectActivatedRoute().setParams(…)` — `/angular-router` |
|
|
193
193
|
| a guard or a class built with `new` that takes the route | `createActivatedRoute({ params })` — the same handle, no `TestBed` |
|
|
@@ -296,7 +296,7 @@ it('loads', async () => {
|
|
|
296
296
|
| a hand-written `class MockChartComponent` restating a child's selector | `createComponentStub(ChartComponent)` (`/angular`) — selector, inputs, outputs read from `ɵcmp` |
|
|
297
297
|
| a `TestingStorage` class for `localStorage` / `sessionStorage` | `stubWebStorage('localStorage', { items })` from `/dom-stubs` — `snapshot()` to assert |
|
|
298
298
|
| a hand-written `MockWorker` whose `addEventListener` sets `onmessage` | `stubWorker({ respond })` from `/dom-stubs` — `last.emit(data)`, `last.messages` |
|
|
299
|
-
| `window.requestAnimationFrame = (cb) => { cb(0); return 0; }`, or an array of callbacks looped over | `stubAnimationFrame()` / `stubAnimationFrame({ mode: 'queued' })` from `/dom-stubs` — `flush()`, `flushAll()` for nested frames, `pending`; a foreign handle's cancel reaches the real one
|
|
299
|
+
| `window.requestAnimationFrame = (cb) => { cb(0); return 0; }`, or an array of callbacks looped over | `stubAnimationFrame()` / `stubAnimationFrame({ mode: 'queued' })` from `/dom-stubs` — `flush()`, `flushAll()` for nested frames, `pending`; a foreign handle's cancel reaches the real one; assert a cancel with `frames.lastHandle`, not `1` |
|
|
300
300
|
| `mockValueProp(el, 'getBoundingClientRect', () => new DOMRect(…))`, or a `({ height })` literal | `stubElementRect(el, { width, height })` from `/dom-stubs` — missing fields are `0`, edges derived |
|
|
301
301
|
| `'x' does not exist in type 'MethodReturns<{ y: any; }>'` on a generic class | spell out the type argument — `createSpyFromClass<Config>(Config, …)`; `provideAutoSpy` infers it |
|
|
302
302
|
| `injectSpy(ModalRef)` reads a generic class at its constraint, not its default | `injectSpy<ModalRef<Data>>(ModalRef)` — a constructor taking `T` hides the default |
|