vitest-auto-spy 3.6.0 → 3.7.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.
Potentially problematic release.
This version of vitest-auto-spy might be problematic. Click here for more details.
- package/AGENTS.md +25 -5
- package/README.md +35 -4
- package/dist/angular.d.ts +1 -1
- package/dist/angular.js +4 -4
- package/dist/bun-angular.d.ts +1 -1
- package/dist/bun-angular.js +5 -5
- package/dist/bun.d.ts +17 -10
- package/dist/bun.js +4 -4
- package/dist/{chunk-QGZRH5XG.js → chunk-2PFOBMTZ.js} +1 -1
- package/dist/{chunk-RJJJTLQ3.js → chunk-DNHLQG45.js} +1 -1
- package/dist/{chunk-LSDPJMNS.js → chunk-R5MFTE64.js} +55 -28
- package/dist/{chunk-F3NMY5KU.js → chunk-TMO2UFLD.js} +95 -49
- package/dist/{chunk-OEQTH7RA.js → chunk-WT75WGQZ.js} +72 -31
- package/dist/console.js +1 -1
- package/dist/index.js +4 -4
- package/dist/nestjs.js +2 -2
- package/dist/node.cjs +124 -56
- package/dist/node.d.cts +17 -10
- package/dist/node.js +4 -4
- package/dist/react.js +4 -4
- package/dist/svelte.js +4 -4
- package/dist/vue.js +5 -5
- package/dist/{run-effect-C_7mFldc.d.ts → zoneless-DbVZ96iR.d.ts} +142 -75
- package/package.json +2 -2
- package/skills/vitest-auto-spy/SKILL.md +5 -2
package/AGENTS.md
CHANGED
|
@@ -1067,6 +1067,7 @@ mechanisms, and a test that waits on the wrong one fails with a message that nam
|
|
|
1067
1067
|
| effects + `afterNextRender` + CD | `await stable(fixture)` (`…/angular`) | `detectChanges()` alone |
|
|
1068
1068
|
| timers, debounces, polling | `await advanceTimers(ms)` (`…/setup`) | `await Promise.resolve()` |
|
|
1069
1069
|
| a dynamic `import()`, native `async` in a dep | `await flushEventLoop()` / `settleDynamicImport()` | `tick()`, `flushMicrotasks()`, microtasks |
|
|
1070
|
+
| an `httpResource()` / `resource()` / `rxResource` | `await settleResource(r)` (`…/angular`) | `flushEventLoopUntil` — it never ticks |
|
|
1070
1071
|
|
|
1071
1072
|
```ts
|
|
1072
1073
|
import { flushEventLoop, settleDynamicImport } from 'vitest-auto-spy';
|
|
@@ -1088,9 +1089,14 @@ Three rules worth stating outright, because each of them cost a day somewhere:
|
|
|
1088
1089
|
and a non-zero exit code.
|
|
1089
1090
|
|
|
1090
1091
|
`flushEventLoopUntil(isDone, { turns, label })` is the same thing with a condition and a budget —
|
|
1091
|
-
for a
|
|
1092
|
-
|
|
1093
|
-
|
|
1092
|
+
for a chunk becoming reachable, an SDK reporting itself ready, a queue draining. Use it instead of a
|
|
1093
|
+
hand-tuned turn count: the count depends on the dependency, not on the spec, and a condition that
|
|
1094
|
+
never holds fails naming the `label` rather than hanging until the runner's timeout.
|
|
1095
|
+
|
|
1096
|
+
**Not for an Angular `resource()` / `httpResource()`.** Those need a change-detection _tick_, and
|
|
1097
|
+
this helper only takes event-loop turns — a resource awaited through it finishes the whole budget
|
|
1098
|
+
having issued zero requests. `settleResource(resource, { turns, label })` from
|
|
1099
|
+
`vitest-auto-spy/angular` is that wait.
|
|
1094
1100
|
|
|
1095
1101
|
`flushEventLoop(turns?)` takes real event-loop turns even while the timers are faked, without
|
|
1096
1102
|
touching the clock. It is the honest name for the `await vi.advanceTimersByTimeAsync(0)` trick,
|
|
@@ -1474,9 +1480,15 @@ spies.get(PricingService).total.mockReturnValue(100);
|
|
|
1474
1480
|
// NOTE: Injector.create() — it does NOT accept EnvironmentProviders (provideHttpClient() etc.)
|
|
1475
1481
|
|
|
1476
1482
|
// zoneless waiting
|
|
1477
|
-
await stable(fixture); // flush effects, then await the fixture
|
|
1483
|
+
await stable(fixture); // flush effects, then await the fixture; fails at 2000 ms naming the cause
|
|
1484
|
+
await stable(fixture, { timeout: 5000, label: 'the products fixture' });
|
|
1478
1485
|
flushEffects(); // the no-fixture half: services, stores, runInInjectionContext
|
|
1479
1486
|
|
|
1487
|
+
// resources — one wait for httpResource(), resource() and rxResource()
|
|
1488
|
+
flushEffects(); // an httpResource issues NO request until something ticks
|
|
1489
|
+
httpTesting.expectOne('/api/products').flush([product]);
|
|
1490
|
+
await settleResource(products, { label: 'the product resource' });
|
|
1491
|
+
|
|
1480
1492
|
// signal assertions
|
|
1481
1493
|
registerSignalMatchers(); // once, in the setup file
|
|
1482
1494
|
expect(component.total).toHaveSignalValue(3);
|
|
@@ -1489,6 +1501,12 @@ Two zoneless traps:
|
|
|
1489
1501
|
- `expect(someSignal).toBeTruthy()` passes for **every** signal ever created — a signal is a
|
|
1490
1502
|
function. Use `toHaveSignalValue`, which also rejects the missing-parentheses mistake.
|
|
1491
1503
|
|
|
1504
|
+
And one resource trap, which is the same shape one level up: an `httpResource()` reports `loading`
|
|
1505
|
+
with its **default** value until a tick _and_ a microtask after its response is flushed, so a spec
|
|
1506
|
+
that asserts too early asserts the default and passes. `settleResource` fails instead of passing
|
|
1507
|
+
emptily. Note the order — `flushEffects()` first (the request is issued there, not on creation),
|
|
1508
|
+
then the flush, then the wait.
|
|
1509
|
+
|
|
1492
1510
|
Per-file timing, to find which specs actually pay for `TestBed`:
|
|
1493
1511
|
|
|
1494
1512
|
```ts
|
|
@@ -1802,7 +1820,9 @@ packages, which a subpath export can never be.
|
|
|
1802
1820
|
| a stub that works in the first test of the file and in no other | installed at `describe` level or in `beforeAll`, then restored away | install it in `beforeEach`, or `installPerTest(() => stub…())` |
|
|
1803
1821
|
| a third-party library failing every other run, no test named | a test sealed a global with `Object.defineProperty` (non-configurable) | `setupAutoSpy({ guardGlobals: 'throw' })` names the file; then `mockValueProp` |
|
|
1804
1822
|
| `expected [ { at: 1, …(5) }, …(8) ] to deeply equal [ { …(6) }, … ]` | one field moved in every element — usually a frozen clock or an id | `expect(diffByField(actual, expected)).toBeUndefined()` |
|
|
1805
|
-
| a hand-tuned number of turns waiting for a `resource()` to load
|
|
1823
|
+
| a hand-tuned number of turns waiting for a `resource()` to load | a resource needs a change-detection **tick**, not event-loop turns; `flushEventLoopUntil` never ticks and the resource never even issues its request | `flushEffects()`, flush the request, then `await settleResource(r, { label })` |
|
|
1824
|
+
| a `resource()` assertion that passes but reads the **default** value | the spec asserted before the resource left `loading` | `await settleResource(r, { label })` — it fails loudly instead |
|
|
1825
|
+
| a spec dying on the runner's 5 s file timeout right after `await stable(fixture)` | the fixture never stabilised — an unflushed request, a real `setInterval` | `stable` now fails at 2000 ms naming the cause; raise `{ timeout }` only once neither is true |
|
|
1806
1826
|
| `flushEventLoopUntil` timing out on the **first** such test only, the rest green | a cold dynamic `import()` outran the turn budget; later tests hit the module cache | `await settleDynamicImport(() => import('…'))` — await the module, do not count turns |
|
|
1807
1827
|
| a template error in a `describe` that never patched anything | a spec `afterEach` threw and skipped `setupAutoSpy`'s, so a `mock*Prop` patch travelled | upgrade — an `onTestFinished` net restores it and names the cause (§10) |
|
|
1808
1828
|
| `Property 'mockReturnValue' does not exist on type 'never'` | a generic method with a conditional return type; the spy collapsed | upgrade — fixed in the types; the member now keeps its sync helper bundle |
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ identical API, with **RxJS** spies and **Angular / NestJS / React / Vue·Pinia /
|
|
|
16
16
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
17
17
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
18
18
|
[](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
|
|
19
|
-
[](#install)
|
|
20
20
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
21
21
|
[](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
|
|
22
22
|
[](./LICENSE)
|
|
@@ -48,7 +48,7 @@ identical API, with **RxJS** spies and **Angular / NestJS / React / Vue·Pinia /
|
|
|
48
48
|
- 📡 First-class RxJS `Observable` spying (`nextWith`, `nextWithValues`, `throwWith`, …)
|
|
49
49
|
- ⚙️ Getter / setter spies via `accessorSpies`
|
|
50
50
|
- 🧰 DI & mocking utilities — `provideAutoSpy` / `injectSpy` (Angular, NestJS, Vue), `createFunctionSpy`, `mockReadonlyProp` for signals
|
|
51
|
-
- ⚡ Angular speed & zoneless helpers — `renderShallow` (**1.7×** on real component specs), `createWithAutoSpies`, `stable` / `flushEffects`, `toHaveSignalValue`, per-file `TestBed` timings
|
|
51
|
+
- ⚡ Angular speed & zoneless helpers — `renderShallow` (**1.7×** on real component specs), `createWithAutoSpies`, `stable` / `flushEffects`, `settleResource` for `httpResource()`, `toHaveSignalValue`, per-file `TestBed` timings
|
|
52
52
|
- 🧱 The providers a testing module cannot reach — `overrideComponentProvider`, `provideAutoSpyForToken`, `assertNgModuleScopes`, `createDirectiveHost`
|
|
53
53
|
- 📡 Observable assertions that fail on silence — `expectEmission` / `expectEmissions` / `expectNoEmission` / `expectCompletion` / `expectError`, no rxjs required, Angular `output()` included
|
|
54
54
|
- 🏗️ Doubles for what the code builds itself — `mockConstructor` / `stubConstructor` for `new`, plus `stubMediaElement`, `stubAbortController` and the observer stubs
|
|
@@ -112,6 +112,7 @@ identical API, with **RxJS** spies and **Angular / NestJS / React / Vue·Pinia /
|
|
|
112
112
|
- [Shallow component rendering](#shallow-component-rendering)
|
|
113
113
|
- [Building a class with auto-spied dependencies](#building-a-class-with-auto-spied-dependencies)
|
|
114
114
|
- [Zoneless waiting](#zoneless-waiting)
|
|
115
|
+
- [Settling a `resource()` or `httpResource()`](#settling-a-resource-or-httpresource)
|
|
115
116
|
- [Asserting a signal's value](#asserting-a-signals-value)
|
|
116
117
|
- [Where a spec spends its time](#where-a-spec-spends-its-time)
|
|
117
118
|
- [NestJS](#nestjs)
|
|
@@ -711,7 +712,7 @@ Node / Bun / React / Vue project pulls **neither rxjs nor Angular into its runti
|
|
|
711
712
|
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | :----: |
|
|
712
713
|
| `vitest-auto-spy` | `createSpyFromClass`, `createAutoMock`, `createFunctionSpy`, sync + promise + accessor spies, `errorHandler`, types | `vitest` | ✅ |
|
|
713
714
|
| `vitest-auto-spy/rxjs` | observable spies (`nextWith`, `nextWithValues`, `observablePropsToSpyOn`, …) + `createObservableWithValues` | `rxjs` | ✅ |
|
|
714
|
-
| `vitest-auto-spy/angular` | `provideAutoSpy`, `injectSpy`, `renderShallow`, `createWithAutoSpies`, `stable`/`flushEffects`, the `mock*Prop` helpers, signal matchers, TestBed diagnostics | `@angular/core` | ✅ |
|
|
715
|
+
| `vitest-auto-spy/angular` | `provideAutoSpy`, `injectSpy`, `renderShallow`, `createWithAutoSpies`, `stable`/`flushEffects`, `settleResource`, the `mock*Prop` helpers, signal matchers, TestBed diagnostics | `@angular/core` | ✅ |
|
|
715
716
|
| `vitest-auto-spy/bun` | the same core, driven by Bun's `bun:test` mocks | `bun:test` | ✅ |
|
|
716
717
|
| `vitest-auto-spy/bun-angular` | Angular's `TestBed` under `bun test` — DOM, JIT `templateUrl` resolution and a zoneless environment, from one preload | `bun:test`, `@angular/core` | ✅ |
|
|
717
718
|
| `vitest-auto-spy/node` | the same core, driven by `node:test`'s `mock.fn()` | `node:test` | ✅ |
|
|
@@ -1255,6 +1256,35 @@ app the state that matters is signal-derived and effects are what move it forwar
|
|
|
1255
1256
|
both, in the right order; `flushEffects` prefers `TestBed.tick()` (Angular ≥ 20) and falls back to
|
|
1256
1257
|
`ApplicationRef.tick()`.
|
|
1257
1258
|
|
|
1259
|
+
The wait is bounded: `stable` gives the fixture **2000 ms** and then throws the cause, instead of
|
|
1260
|
+
letting the runner report a 5 s file-level timeout that names neither the helper nor the fixture.
|
|
1261
|
+
Pass `{ timeout, label }` to change either; `{ timeout: 0 }` waits indefinitely. The watchdog runs
|
|
1262
|
+
on a timer captured at import, so `vi.useFakeTimers()` cannot freeze it.
|
|
1263
|
+
|
|
1264
|
+
#### Settling a `resource()` or `httpResource()`
|
|
1265
|
+
|
|
1266
|
+
```ts
|
|
1267
|
+
import { flushEffects, settleResource } from 'vitest-auto-spy/angular';
|
|
1268
|
+
|
|
1269
|
+
const products = TestBed.runInInjectionContext(() => httpResource<Product[]>(() => '/api/products'));
|
|
1270
|
+
|
|
1271
|
+
flushEffects(); // the request is issued here — not when the resource was created
|
|
1272
|
+
TestBed.inject(HttpTestingController).expectOne('/api/products').flush([product]);
|
|
1273
|
+
await settleResource(products, { label: 'the product resource' });
|
|
1274
|
+
|
|
1275
|
+
expect(products.value()).toEqual([product]);
|
|
1276
|
+
```
|
|
1277
|
+
|
|
1278
|
+
Angular's resource primitives need a **different wait each** — measured on 21.2.17, an
|
|
1279
|
+
`httpResource` settles one tick + one microtask after its flush, a plain `resource()` takes two
|
|
1280
|
+
rounds of the same, and neither has made a request at all until something ticks. Getting it wrong
|
|
1281
|
+
asserts against the resource's _default_ value, which is a green test proving nothing.
|
|
1282
|
+
`settleResource` is the loop both converge under, with a turn budget and a failure that names the
|
|
1283
|
+
resource and the flush it is missing.
|
|
1284
|
+
|
|
1285
|
+
`flushEventLoopUntil` cannot do this: it takes real event-loop turns and never ticks, so a resource
|
|
1286
|
+
awaited through it finishes the budget having issued zero requests.
|
|
1287
|
+
|
|
1258
1288
|
#### Asserting a signal's value
|
|
1259
1289
|
|
|
1260
1290
|
```ts
|
|
@@ -1931,7 +1961,8 @@ Both are the same object at runtime; only the view changes.
|
|
|
1931
1961
|
| `describeDuplicateCopies()` / `getPackageCopies()` | The duplicate-install report, and the copies behind it |
|
|
1932
1962
|
| `renderShallow(Component, opts?)` _(Angular)_ | `TestBed` component, minus its children and (by default) its template |
|
|
1933
1963
|
| `createWithAutoSpies(Class, opts?)` _(Angular)_ | Build a class through Angular DI with every unprovided token auto-spied |
|
|
1934
|
-
| `stable(fixture)` / `flushEffects()` _(Angular)_
|
|
1964
|
+
| `stable(fixture, opts?)` / `flushEffects()` _(Angular)_ | Zoneless waiting: flush effects, then await the fixture, with a 2 s budget that names the cause |
|
|
1965
|
+
| `settleResource(resource, opts?)` _(Angular)_ | Tick until an `httpResource()` / `resource()` / `rxResource()` leaves `loading` |
|
|
1935
1966
|
| `registerSignalMatchers()` _(Angular)_ | Adds `expect(sig).toHaveSignalValue(value)` |
|
|
1936
1967
|
| `enableTestBedDiagnostics(opts?)` _(Angular)_ | Per-file report of how much of a spec's time went into `TestBed` |
|
|
1937
1968
|
| `setupAngularTestEnv(opts)` _(Angular)_ | Zone and zoneless spec files in one worker, switching platforms per file |
|
package/dist/angular.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { l as AngularTokenProvider, A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, R as RenderShallowOptions, c as ResourceStatusLike, S as SettleResourceOptions, d as ShallowRender, e as SpyRegistry, f as StableOptions, g as createWithAutoSpies, h as flushEffects, i as injectSpy, p as provideAutoSpy, m as provideAutoSpyForToken, r as renderShallow, j as runEffect, s as settleResource, k as stable } from './zoneless-DbVZ96iR.js';
|
|
2
2
|
import { Type, Signal, WritableSignal } from '@angular/core';
|
|
3
3
|
import { S as Spy, C as ClassType, a as ClassSpyConfiguration, O as OnlyMethodKeysOf } from './types-dZUFYsox.js';
|
|
4
4
|
export { A as AccessorImplementations, R as RestoreProp, c as countMockedProps, m as mockAccessorsProp, a as mockReadonlyProp, b as mockReadonlyPropGetter, d as mockValueProp, r as restoreMockedProps } from './prop-mock-CKFksCvA.js';
|
package/dist/angular.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useVitestAdapter } from './chunk-DMSELR3S.js';
|
|
2
|
-
export { createWithAutoSpies, flushEffects, injectSpy, provideAutoSpy, provideAutoSpyForToken, renderShallow, runEffect, stable } from './chunk-
|
|
2
|
+
export { createWithAutoSpies, flushEffects, injectSpy, provideAutoSpy, provideAutoSpyForToken, renderShallow, runEffect, settleResource, stable } from './chunk-TMO2UFLD.js';
|
|
3
3
|
import './chunk-OS2QFTIF.js';
|
|
4
|
-
export { expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, setEmissionTimeout } from './chunk-
|
|
4
|
+
export { expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, setEmissionTimeout } from './chunk-2PFOBMTZ.js';
|
|
5
5
|
import { mockReadonlyProp } from './chunk-QGBNXDKU.js';
|
|
6
6
|
export { countMockedProps, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockValueProp, restoreMockedProps } from './chunk-QGBNXDKU.js';
|
|
7
|
-
import { createSpyFromClass } from './chunk-
|
|
8
|
-
import './chunk-
|
|
7
|
+
import { createSpyFromClass } from './chunk-DNHLQG45.js';
|
|
8
|
+
import './chunk-WT75WGQZ.js';
|
|
9
9
|
import './chunk-TNB3Y3GI.js';
|
|
10
10
|
import './chunk-DA5E36HQ.js';
|
|
11
11
|
import { withDocs, DOCS_LINKS } from './chunk-VR5GJRBS.js';
|
package/dist/bun-angular.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { AsInstances, AssertMockedOptions, ConstructorMock, ConstructorSpy, Flus
|
|
|
3
3
|
export { A as AutoMockConfiguration, C as CallbackSubscribable, E as EmissionObserver, a as EmissionOptions, b as EmissionSource, S as SubscribableLike, c as autoMocked, d as createAutoMock, e as expectCompletion, f as expectEmission, g as expectEmissions, h as expectError, i as expectNoEmission, s as setEmissionTimeout } from './expect-emission-RtR1iYgI.js';
|
|
4
4
|
export { A as AccessorImplementations, R as RestoreProp, c as countMockedProps, m as mockAccessorsProp, a as mockReadonlyProp, b as mockReadonlyPropGetter, d as mockValueProp, r as restoreMockedProps } from './prop-mock-CKFksCvA.js';
|
|
5
5
|
export { d as describeDuplicateCopies, g as getPackageCopies } from './package-identity-B1pqa-Sh.js';
|
|
6
|
-
export { A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, R as RenderShallowOptions, S as ShallowRender,
|
|
6
|
+
export { A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, R as RenderShallowOptions, c as ResourceStatusLike, S as SettleResourceOptions, d as ShallowRender, e as SpyRegistry, f as StableOptions, g as createWithAutoSpies, h as flushEffects, i as injectSpy, p as provideAutoSpy, r as renderShallow, j as runEffect, s as settleResource, k as stable } from './zoneless-DbVZ96iR.js';
|
|
7
7
|
import 'rxjs';
|
|
8
8
|
import 'vitest';
|
|
9
9
|
import '@angular/core';
|
package/dist/bun-angular.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export { createWithAutoSpies, flushEffects, injectSpy, provideAutoSpy, renderShallow, runEffect, stable } from './chunk-
|
|
1
|
+
export { createWithAutoSpies, flushEffects, injectSpy, provideAutoSpy, renderShallow, runEffect, settleResource, stable } from './chunk-TMO2UFLD.js';
|
|
2
2
|
import './chunk-OWQR2YVQ.js';
|
|
3
3
|
import './chunk-NQXXUJPS.js';
|
|
4
|
-
export { asInstance, asInstances, asSpy, assertMocked, clearAutoSpy, compareTestRuns, createMock, createSpyClass, diffByField, flushEventLoop, flushEventLoopUntil, formatTestRunComparison, intersectionEntry, mockConstructor, mockDeep, moduleNamespace, mutationRecord, narrow, resetAutoSpy, resizeEntry, settleDynamicImport, stubAbortController, stubConstructor, stubIntersectionObserver, stubMediaElement, stubMutationObserver, stubObserver, stubResizeObserver, summarizeTestRun, withOverrides } from './chunk-
|
|
5
|
-
export { expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, setEmissionTimeout } from './chunk-
|
|
4
|
+
export { asInstance, asInstances, asSpy, assertMocked, clearAutoSpy, compareTestRuns, createMock, createSpyClass, diffByField, flushEventLoop, flushEventLoopUntil, formatTestRunComparison, intersectionEntry, mockConstructor, mockDeep, moduleNamespace, mutationRecord, narrow, resetAutoSpy, resizeEntry, settleDynamicImport, stubAbortController, stubConstructor, stubIntersectionObserver, stubMediaElement, stubMutationObserver, stubObserver, stubResizeObserver, summarizeTestRun, withOverrides } from './chunk-R5MFTE64.js';
|
|
5
|
+
export { expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, setEmissionTimeout } from './chunk-2PFOBMTZ.js';
|
|
6
6
|
export { countMockedProps, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockValueProp, restoreMockedProps } from './chunk-QGBNXDKU.js';
|
|
7
|
-
export { autoMocked, createAutoMock, createSpyFromClass } from './chunk-
|
|
8
|
-
export { createFunctionSpy, errorHandler } from './chunk-
|
|
7
|
+
export { autoMocked, createAutoMock, createSpyFromClass } from './chunk-DNHLQG45.js';
|
|
8
|
+
export { createFunctionSpy, errorHandler } from './chunk-WT75WGQZ.js';
|
|
9
9
|
import './chunk-TNB3Y3GI.js';
|
|
10
10
|
export { describeDuplicateCopies, getPackageCopies } from './chunk-DA5E36HQ.js';
|
|
11
11
|
import { withDocs, DOCS_LINKS } from './chunk-VR5GJRBS.js';
|
package/dist/bun.d.ts
CHANGED
|
@@ -444,8 +444,9 @@ declare function stubConstructor<T, TArgs extends unknown[] = unknown[]>(target:
|
|
|
444
444
|
* Give the runtime `turns` real event-loop turns, whatever the timers are doing.
|
|
445
445
|
*
|
|
446
446
|
* Reach for it when the thing being awaited crosses out of the zone / out of the test's own
|
|
447
|
-
* promise chain: a dynamic `import()` triggered by production code,
|
|
448
|
-
*
|
|
447
|
+
* promise chain: a dynamic `import()` triggered by production code, a native `async` function
|
|
448
|
+
* inside a dependency, a stub that resolves a turn later. Not an Angular `httpResource()` /
|
|
449
|
+
* `resource()` — those need a *tick*, which is `settleResource()`, not this.
|
|
449
450
|
*
|
|
450
451
|
* ```ts
|
|
451
452
|
* component.openModal(); // production code does `await import('./modal')`
|
|
@@ -472,19 +473,25 @@ interface FlushUntilOptions {
|
|
|
472
473
|
/**
|
|
473
474
|
* Take real event-loop turns until `isDone()` says so, then stop — or fail saying it never did.
|
|
474
475
|
*
|
|
475
|
-
* The shape behind every hand-rolled "settle" helper:
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
476
|
+
* The shape behind every hand-rolled "settle" helper: a lazily-loaded chunk becoming reachable, an
|
|
477
|
+
* SDK reporting itself ready, a queue draining. Written by hand it is a fixed number of turns, tuned
|
|
478
|
+
* by trial until the suite goes green — which is both slower than it needs to be (it always waits
|
|
479
|
+
* the maximum) and quietly fragile (one more hand-off in a dependency and the number is wrong
|
|
480
|
+
* again).
|
|
480
481
|
*
|
|
481
482
|
* ```ts
|
|
482
|
-
*
|
|
483
|
+
* client.warmUp();
|
|
483
484
|
*
|
|
484
|
-
* await flushEventLoopUntil(() =>
|
|
485
|
-
* expect(
|
|
485
|
+
* await flushEventLoopUntil(() => client.isReady(), { label: 'the SDK handshake' });
|
|
486
|
+
* expect(client.session()).toBeDefined();
|
|
486
487
|
* ```
|
|
487
488
|
*
|
|
489
|
+
* **Not for an Angular resource** — use `settleResource()` from `vitest-auto-spy/angular` for that.
|
|
490
|
+
* This helper takes real event-loop turns and never *ticks*, and an `httpResource()` issues no
|
|
491
|
+
* request at all until something does: measured, a resource awaited here finishes the whole budget
|
|
492
|
+
* having made zero requests, then fails saying the condition was never met. The docstring used to
|
|
493
|
+
* claim that use case and show it as the example; it never worked.
|
|
494
|
+
*
|
|
488
495
|
* The budget is what separates this from a `while (true)`: a condition that never becomes true is
|
|
489
496
|
* the normal way for this to be used wrongly — the request was never made, the stub never resolved
|
|
490
497
|
* — and a test that hangs until the runner's timeout reports the file, not the wait.
|
package/dist/bun.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import './chunk-OWQR2YVQ.js';
|
|
2
2
|
import './chunk-NQXXUJPS.js';
|
|
3
|
-
export { asInstance, asInstances, asSpy, assertMocked, clearAutoSpy, compareTestRuns, createMock, createSpyClass, diffByField, flushEventLoop, flushEventLoopUntil, formatTestRunComparison, intersectionEntry, mockConstructor, mockDeep, moduleNamespace, mutationRecord, narrow, resetAutoSpy, resizeEntry, settleDynamicImport, stubAbortController, stubConstructor, stubIntersectionObserver, stubMediaElement, stubMutationObserver, stubObserver, stubResizeObserver, summarizeTestRun, withOverrides } from './chunk-
|
|
4
|
-
export { expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, setEmissionTimeout } from './chunk-
|
|
3
|
+
export { asInstance, asInstances, asSpy, assertMocked, clearAutoSpy, compareTestRuns, createMock, createSpyClass, diffByField, flushEventLoop, flushEventLoopUntil, formatTestRunComparison, intersectionEntry, mockConstructor, mockDeep, moduleNamespace, mutationRecord, narrow, resetAutoSpy, resizeEntry, settleDynamicImport, stubAbortController, stubConstructor, stubIntersectionObserver, stubMediaElement, stubMutationObserver, stubObserver, stubResizeObserver, summarizeTestRun, withOverrides } from './chunk-R5MFTE64.js';
|
|
4
|
+
export { expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, setEmissionTimeout } from './chunk-2PFOBMTZ.js';
|
|
5
5
|
export { countMockedProps, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockValueProp, restoreMockedProps } from './chunk-QGBNXDKU.js';
|
|
6
|
-
export { autoMocked, createAutoMock, createSpyFromClass } from './chunk-
|
|
7
|
-
export { createFunctionSpy, errorHandler } from './chunk-
|
|
6
|
+
export { autoMocked, createAutoMock, createSpyFromClass } from './chunk-DNHLQG45.js';
|
|
7
|
+
export { createFunctionSpy, errorHandler } from './chunk-WT75WGQZ.js';
|
|
8
8
|
import './chunk-TNB3Y3GI.js';
|
|
9
9
|
export { describeDuplicateCopies, getPackageCopies } from './chunk-DA5E36HQ.js';
|
|
10
10
|
import './chunk-VR5GJRBS.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AUTO_SPY_MARK, createFunctionSpy, markAsMock } from './chunk-
|
|
1
|
+
import { AUTO_SPY_MARK, createFunctionSpy, markAsMock } from './chunk-WT75WGQZ.js';
|
|
2
2
|
import { requireObservableSupport } from './chunk-TNB3Y3GI.js';
|
|
3
3
|
import { getMockAdapter } from './chunk-DA5E36HQ.js';
|
|
4
4
|
import { withDocs, DOCS_LINKS } from './chunk-VR5GJRBS.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mockValueProp, mockReadonlyPropGetter } from './chunk-QGBNXDKU.js';
|
|
2
|
-
import { createSpyFromClass, createProxyPropStore, describeStoredProp, dropStoredProp, storeDefinedProp, writeStoredAccessor, writeStoredValue, readStoredAccessor, NOT_STORED, isProtocolKey, isDeletedProp } from './chunk-
|
|
3
|
-
import { runClearHook, runConfigReset, createFunctionSpy, isMarkedMock, serializeValue } from './chunk-
|
|
2
|
+
import { createSpyFromClass, createProxyPropStore, describeStoredProp, dropStoredProp, storeDefinedProp, writeStoredAccessor, writeStoredValue, readStoredAccessor, NOT_STORED, isProtocolKey, isDeletedProp } from './chunk-DNHLQG45.js';
|
|
3
|
+
import { runClearHook, runConfigReset, createFunctionSpy, isMarkedMock, readDeepChildren, serializeValue, DEEP_CHILDREN } from './chunk-WT75WGQZ.js';
|
|
4
4
|
import { getMockAdapter } from './chunk-DA5E36HQ.js';
|
|
5
5
|
import { withDocs, DOCS_LINKS } from './chunk-VR5GJRBS.js';
|
|
6
6
|
|
|
@@ -38,34 +38,43 @@ function readSpyMember(target, key, receiver, boundSpyMethods) {
|
|
|
38
38
|
boundSpyMethods.set(key, bound);
|
|
39
39
|
return bound;
|
|
40
40
|
}
|
|
41
|
+
function readNodeMember(state, target, key, receiver) {
|
|
42
|
+
const patched = readStoredAccessor(state.store, key, receiver);
|
|
43
|
+
if (patched !== NOT_STORED) {
|
|
44
|
+
return patched;
|
|
45
|
+
}
|
|
46
|
+
if (state.store.values.has(key)) {
|
|
47
|
+
return state.store.values.get(key);
|
|
48
|
+
}
|
|
49
|
+
if (key === DEEP_CHILDREN) {
|
|
50
|
+
return state.children;
|
|
51
|
+
}
|
|
52
|
+
if (key === "then" || isProtocolKey(key)) {
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
if (getSpySurfaceKeys().has(key)) {
|
|
56
|
+
return readSpyMember(target, key, receiver, state.boundSpyMethods);
|
|
57
|
+
}
|
|
58
|
+
if (typeof key === "symbol" || isDeletedProp(state.store, key)) {
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
if (!state.children.has(key)) {
|
|
62
|
+
state.children.set(key, createDeepNode(`${state.name}.${String(key)}`, {}, state.selfReturning));
|
|
63
|
+
}
|
|
64
|
+
return state.children.get(key);
|
|
65
|
+
}
|
|
41
66
|
function createDeepNode(name, overrides, selfReturning) {
|
|
42
67
|
const spy = createFunctionSpy(name);
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
68
|
+
const state = {
|
|
69
|
+
name,
|
|
70
|
+
selfReturning,
|
|
71
|
+
children: /* @__PURE__ */ new Map(),
|
|
72
|
+
boundSpyMethods: /* @__PURE__ */ new Map(),
|
|
73
|
+
store: createProxyPropStore(overrides)
|
|
74
|
+
};
|
|
75
|
+
const { store } = state;
|
|
46
76
|
const handler = {
|
|
47
|
-
get(target, key, receiver)
|
|
48
|
-
const patched = readStoredAccessor(store, key, receiver);
|
|
49
|
-
if (patched !== NOT_STORED) {
|
|
50
|
-
return patched;
|
|
51
|
-
}
|
|
52
|
-
if (store.values.has(key)) {
|
|
53
|
-
return store.values.get(key);
|
|
54
|
-
}
|
|
55
|
-
if (key === "then" || isProtocolKey(key)) {
|
|
56
|
-
return void 0;
|
|
57
|
-
}
|
|
58
|
-
if (getSpySurfaceKeys().has(key)) {
|
|
59
|
-
return readSpyMember(target, key, receiver, boundSpyMethods);
|
|
60
|
-
}
|
|
61
|
-
if (typeof key === "symbol" || isDeletedProp(store, key)) {
|
|
62
|
-
return void 0;
|
|
63
|
-
}
|
|
64
|
-
if (!children.has(key)) {
|
|
65
|
-
children.set(key, createDeepNode(`${name}.${String(key)}`, {}, selfReturning));
|
|
66
|
-
}
|
|
67
|
-
return children.get(key);
|
|
68
|
-
},
|
|
77
|
+
get: (target, key, receiver) => readNodeMember(state, target, key, receiver),
|
|
69
78
|
set(_target, key, value, receiver) {
|
|
70
79
|
if (!writeStoredAccessor(store, key, value, receiver)) {
|
|
71
80
|
writeStoredValue(store, key, value);
|
|
@@ -105,7 +114,7 @@ function collectAccessorMocks(spy) {
|
|
|
105
114
|
}
|
|
106
115
|
return [...Object.values(bag.getters), ...Object.values(bag.setters)].filter(isMarkedMock);
|
|
107
116
|
}
|
|
108
|
-
function
|
|
117
|
+
function collectOwnMocks(spy) {
|
|
109
118
|
const mocks = [];
|
|
110
119
|
Object.keys(spy).forEach((key) => {
|
|
111
120
|
const descriptor = Object.getOwnPropertyDescriptor(spy, key);
|
|
@@ -118,6 +127,24 @@ function collectMocks(spy) {
|
|
|
118
127
|
});
|
|
119
128
|
return [...mocks, ...collectAccessorMocks(spy)];
|
|
120
129
|
}
|
|
130
|
+
function collectMocks(spy) {
|
|
131
|
+
const mocks = [];
|
|
132
|
+
const seen = /* @__PURE__ */ new Set();
|
|
133
|
+
const visit = (value) => {
|
|
134
|
+
if (seen.has(value)) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
seen.add(value);
|
|
138
|
+
if (isMarkedMock(value)) {
|
|
139
|
+
mocks.push(value);
|
|
140
|
+
readDeepChildren(value).forEach(visit);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
collectOwnMocks(value).forEach(visit);
|
|
144
|
+
};
|
|
145
|
+
visit(spy);
|
|
146
|
+
return mocks;
|
|
147
|
+
}
|
|
121
148
|
function clearAutoSpy(spy) {
|
|
122
149
|
const adapter = getMockAdapter();
|
|
123
150
|
collectMocks(spy).forEach((mock) => {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { createSpyFromClass, createAutoMock } from './chunk-
|
|
2
|
-
import { isAutoSpyLike } from './chunk-
|
|
1
|
+
import { createSpyFromClass, createAutoMock } from './chunk-DNHLQG45.js';
|
|
2
|
+
import { isAutoSpyLike } from './chunk-WT75WGQZ.js';
|
|
3
3
|
import { withDocs, DOCS_LINKS } from './chunk-VR5GJRBS.js';
|
|
4
4
|
import { TestBed } from '@angular/core/testing';
|
|
5
|
-
import {
|
|
5
|
+
import { Injector, runInInjectionContext, NO_ERRORS_SCHEMA, ApplicationRef, ɵSIGNAL as _SIGNAL } from '@angular/core';
|
|
6
6
|
|
|
7
7
|
function provideAutoSpy(ObjectClass, methodsToSpyOnOrConfig) {
|
|
8
8
|
return {
|
|
@@ -32,42 +32,6 @@ function warnWhenNotASpy(token, injected) {
|
|
|
32
32
|
)
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
|
-
function isStandalone(component) {
|
|
36
|
-
const definition = Reflect.get(component, "\u0275cmp");
|
|
37
|
-
return typeof definition === "object" && definition !== null && Reflect.get(definition, "standalone") === true;
|
|
38
|
-
}
|
|
39
|
-
function buildOverride(component, options) {
|
|
40
|
-
const override = {};
|
|
41
|
-
if (isStandalone(component)) {
|
|
42
|
-
override.imports = options.keepChildren ?? [];
|
|
43
|
-
override.schemas = [NO_ERRORS_SCHEMA];
|
|
44
|
-
}
|
|
45
|
-
if (!options.keepTemplate) {
|
|
46
|
-
override.template = options.template ?? "";
|
|
47
|
-
override.styles = [];
|
|
48
|
-
}
|
|
49
|
-
return override;
|
|
50
|
-
}
|
|
51
|
-
function applyInputs(fixture, inputs) {
|
|
52
|
-
Object.entries(inputs ?? {}).forEach(([name, value]) => fixture.componentRef.setInput(name, value));
|
|
53
|
-
}
|
|
54
|
-
function renderShallow(component, options = {}) {
|
|
55
|
-
const standalone = isStandalone(component);
|
|
56
|
-
TestBed.configureTestingModule({
|
|
57
|
-
imports: standalone ? [component, ...options.imports ?? []] : options.imports ?? [],
|
|
58
|
-
declarations: standalone ? [] : [component],
|
|
59
|
-
providers: options.providers ?? [],
|
|
60
|
-
schemas: [NO_ERRORS_SCHEMA]
|
|
61
|
-
});
|
|
62
|
-
TestBed.overrideComponent(component, { set: buildOverride(component, options) });
|
|
63
|
-
options.beforeCreate?.();
|
|
64
|
-
const fixture = TestBed.createComponent(component);
|
|
65
|
-
applyInputs(fixture, options.inputs);
|
|
66
|
-
if (options.detectChanges ?? true) {
|
|
67
|
-
fixture.detectChanges();
|
|
68
|
-
}
|
|
69
|
-
return { fixture, component: fixture.componentInstance };
|
|
70
|
-
}
|
|
71
35
|
function createSpyForToken(token) {
|
|
72
36
|
if (typeof token === "function") {
|
|
73
37
|
return createSpyFromClass(token, { lazySpies: true });
|
|
@@ -109,17 +73,41 @@ function createWithAutoSpies(target, options = {}) {
|
|
|
109
73
|
}
|
|
110
74
|
};
|
|
111
75
|
}
|
|
112
|
-
function
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
76
|
+
function isStandalone(component) {
|
|
77
|
+
const definition = Reflect.get(component, "\u0275cmp");
|
|
78
|
+
return typeof definition === "object" && definition !== null && Reflect.get(definition, "standalone") === true;
|
|
79
|
+
}
|
|
80
|
+
function buildOverride(component, options) {
|
|
81
|
+
const override = {};
|
|
82
|
+
if (isStandalone(component)) {
|
|
83
|
+
override.imports = options.keepChildren ?? [];
|
|
84
|
+
override.schemas = [NO_ERRORS_SCHEMA];
|
|
117
85
|
}
|
|
118
|
-
|
|
86
|
+
if (!options.keepTemplate) {
|
|
87
|
+
override.template = options.template ?? "";
|
|
88
|
+
override.styles = [];
|
|
89
|
+
}
|
|
90
|
+
return override;
|
|
119
91
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
92
|
+
function applyInputs(fixture, inputs) {
|
|
93
|
+
Object.entries(inputs ?? {}).forEach(([name, value]) => fixture.componentRef.setInput(name, value));
|
|
94
|
+
}
|
|
95
|
+
function renderShallow(component, options = {}) {
|
|
96
|
+
const standalone = isStandalone(component);
|
|
97
|
+
TestBed.configureTestingModule({
|
|
98
|
+
imports: standalone ? [component, ...options.imports ?? []] : options.imports ?? [],
|
|
99
|
+
declarations: standalone ? [] : [component],
|
|
100
|
+
providers: options.providers ?? [],
|
|
101
|
+
schemas: [NO_ERRORS_SCHEMA]
|
|
102
|
+
});
|
|
103
|
+
TestBed.overrideComponent(component, { set: buildOverride(component, options) });
|
|
104
|
+
options.beforeCreate?.();
|
|
105
|
+
const fixture = TestBed.createComponent(component);
|
|
106
|
+
applyInputs(fixture, options.inputs);
|
|
107
|
+
if (options.detectChanges ?? true) {
|
|
108
|
+
fixture.detectChanges();
|
|
109
|
+
}
|
|
110
|
+
return { fixture, component: fixture.componentInstance };
|
|
123
111
|
}
|
|
124
112
|
function readReactiveNode(candidate) {
|
|
125
113
|
if (typeof candidate !== "object" || candidate === null) {
|
|
@@ -150,5 +138,63 @@ function runEffect(effectRef) {
|
|
|
150
138
|
}
|
|
151
139
|
node.fn();
|
|
152
140
|
}
|
|
141
|
+
var setTimer = globalThis.setTimeout.bind(globalThis);
|
|
142
|
+
var clearTimer = globalThis.clearTimeout.bind(globalThis);
|
|
143
|
+
function flushEffects() {
|
|
144
|
+
const testBed = TestBed;
|
|
145
|
+
if (typeof testBed.tick === "function") {
|
|
146
|
+
testBed.tick();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
TestBed.inject(ApplicationRef).tick();
|
|
150
|
+
}
|
|
151
|
+
async function stable(fixture, options = {}) {
|
|
152
|
+
flushEffects();
|
|
153
|
+
const timeout = options.timeout ?? 2e3;
|
|
154
|
+
if (timeout <= 0) {
|
|
155
|
+
await fixture.whenStable();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
let watchdog = void 0;
|
|
159
|
+
try {
|
|
160
|
+
await Promise.race([
|
|
161
|
+
fixture.whenStable(),
|
|
162
|
+
new Promise((_resolve, reject) => {
|
|
163
|
+
watchdog = setTimer(() => reject(unstableError(timeout, options.label)), timeout);
|
|
164
|
+
})
|
|
165
|
+
]);
|
|
166
|
+
} finally {
|
|
167
|
+
clearTimer(watchdog);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function unstableError(timeout, label) {
|
|
171
|
+
const what = label ?? "the fixture";
|
|
172
|
+
return new Error(
|
|
173
|
+
withDocs(
|
|
174
|
+
`[vitest-auto-spy] stable: ${what} was still unstable after ${timeout} ms. A pending HttpClient request keeps a fixture unstable, and under \`provideHttpClientTesting\` only the spec can complete one \u2014 flush it before awaiting (\`TestBed.inject(HttpTestingController).expectOne(url).flush(body)\`), or use \`settleResource()\` if what you are waiting for is a resource. The other cause is a real timer: a \`setInterval\` or a long \`setTimeout\` the component started keeps Angular busy for as long as it runs, and \`setupFakeTimers()\` plus \`advanceTimers()\` is how a spec gets past that. Raise \`{ timeout }\` only once neither is true.`,
|
|
175
|
+
DOCS_LINKS.angular
|
|
176
|
+
)
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/lib/settle-resource.ts
|
|
181
|
+
var PENDING_STATUSES = /* @__PURE__ */ new Set(["loading", "reloading"]);
|
|
182
|
+
async function settleResource(target, options = {}) {
|
|
183
|
+
const turns = options.turns ?? 20;
|
|
184
|
+
for (let turn = 0; turn <= turns; turn += 1) {
|
|
185
|
+
if (!PENDING_STATUSES.has(target.status())) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
flushEffects();
|
|
189
|
+
await Promise.resolve();
|
|
190
|
+
}
|
|
191
|
+
const what = options.label ?? "the resource";
|
|
192
|
+
throw new Error(
|
|
193
|
+
withDocs(
|
|
194
|
+
`[vitest-auto-spy] settleResource: ${what} was still '${target.status()}' after ${turns} rounds of tick + microtask. A resource stays loading until its request completes, and under \`provideHttpClientTesting\` nothing but the spec can complete one \u2014 flush it first (\`TestBed.inject(HttpTestingController).expectOne(url).flush(body)\`), then await this again. If there is no request to flush, the resource never started: its \`request()\` computation reads a signal the test never set, or the injection context it was created in was discarded. And if the loader resolves on a *timer* rather than a promise, no number of turns will do it \u2014 \`advanceTimers()\` is what moves those.`,
|
|
195
|
+
DOCS_LINKS.angular
|
|
196
|
+
)
|
|
197
|
+
);
|
|
198
|
+
}
|
|
153
199
|
|
|
154
|
-
export { createWithAutoSpies, flushEffects, injectSpy, provideAutoSpy, provideAutoSpyForToken, renderShallow, runEffect, stable };
|
|
200
|
+
export { createWithAutoSpies, flushEffects, injectSpy, provideAutoSpy, provideAutoSpyForToken, renderShallow, runEffect, settleResource, stable };
|