vitest-auto-spy 3.11.0 → 3.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +54 -1
- package/README.md +75 -8
- package/dist/angular.d.ts +40 -1
- package/dist/angular.js +77 -4
- package/dist/bun-angular.d.ts +1 -1
- package/dist/bun-angular.js +4 -4
- package/dist/bun.d.ts +83 -1
- package/dist/bun.js +2 -2
- package/dist/{chunk-B7PDIHEG.js → chunk-7YTPHYP7.js} +16 -4
- package/dist/{chunk-LVLSMH6T.js → chunk-JV5XRAS4.js} +1 -1
- package/dist/{chunk-2U3J65A4.js → chunk-ZBVBJFNW.js} +51 -3
- package/dist/eslint-plugin.cjs +80 -78
- package/dist/eslint-plugin.js +80 -78
- package/dist/index.d.ts +1 -1
- package/dist/index.js +66 -5
- package/dist/nestjs.js +2 -2
- package/dist/node.cjs +67 -4
- package/dist/node.d.cts +2 -0
- package/dist/node.d.ts +1 -1
- package/dist/node.js +2 -2
- package/dist/react.d.ts +1 -1
- package/dist/react.js +2 -2
- package/dist/svelte.d.ts +1 -1
- package/dist/svelte.js +2 -2
- package/dist/vue.d.ts +1 -1
- package/dist/vue.js +3 -3
- package/package.json +1 -1
- package/skills/vitest-auto-spy/SKILL.md +4 -1
package/AGENTS.md
CHANGED
|
@@ -77,7 +77,8 @@ Do you have a real class at runtime?
|
|
|
77
77
|
├── yes, and reads chain (a.b.c()) → mockDeep<T>(overrides?) → DeepMockProxy<T>
|
|
78
78
|
├── yes, and CALLS chain (a.b().c()) → mockDeep<T>({}, { selfReturning: true })
|
|
79
79
|
└── no, it is only READ (DTO, config, route snapshot)
|
|
80
|
-
|
|
80
|
+
├── one spec, a couple of fields → createMock<T>(partial?) → T (no spies)
|
|
81
|
+
└── many specs, one shared model → createFixtureFactory<T>(defaults) → (overrides?) => T
|
|
81
82
|
|
|
82
83
|
One standalone function? → createFunctionSpy<Fn>('name')
|
|
83
84
|
Code under test does `new Foo()`? → a real class? createSpyClass(Foo)
|
|
@@ -97,6 +98,16 @@ noise scales with the number of them.
|
|
|
97
98
|
`createMock<T>()` is the one to reach for on data shapes — it returns a plain `T`, so it satisfies a
|
|
98
99
|
`no-type-assertion` lint rule without an `eslint-disable` on every fixture.
|
|
99
100
|
|
|
101
|
+
**`createFixture<T>(defaults, overrides?)` / `createFixtureFactory<T>(defaults)` are for the model
|
|
102
|
+
that more than one spec builds.** The difference from `createMock` is the `defaults` argument: it is
|
|
103
|
+
a **complete** `T`, checked in full, in one place — so a field the model dropped fails there instead
|
|
104
|
+
of in eight copies of a hundred-line literal. Overrides are deep-partial-checked and merge leaf by
|
|
105
|
+
leaf; an overridden array replaces the default one. Every call hands back a fresh object and the
|
|
106
|
+
defaults are copied at build time, which is what keeps one test's mutation out of the next test —
|
|
107
|
+
across files, under `isolate: false`. The copy is deep through plain objects and arrays only: a
|
|
108
|
+
`Date`, a `Map` or a class instance travels by reference, because rebuilding it would strip its
|
|
109
|
+
prototype. Defaults that are a class instance with getters go through `withOverrides()` first.
|
|
110
|
+
|
|
100
111
|
**`mockDeep` builds depth on property access, not on calls** — the distinction the tree now spells
|
|
101
112
|
out, and the one that costs an afternoon otherwise. `mock.repo.user.find()` chains because every hop
|
|
102
113
|
but the last is a _read_. A node that is **called** returns what it was configured to return, and by
|
|
@@ -657,6 +668,14 @@ sub-tree). The key is non-enumerable, so a spread does not copy it, and there is
|
|
|
657
668
|
`[Symbol.dispose]` on a host mock restores the original implementation instead — call
|
|
658
669
|
`resetAutoSpy(spy)` there. If the project does not transpile `using`, call `spy[Symbol.dispose]()`.
|
|
659
670
|
|
|
671
|
+
`src/lib/dispose-symbol.ts` installs `Symbol.dispose` when the realm has none, and `DISPOSE` — not
|
|
672
|
+
`Symbol.dispose` — is what library code compares against. Node 22 has no explicit resource
|
|
673
|
+
management in V8: it patches the symbol in itself, as `Symbol.for('nodejs.dispose')`, onto the main
|
|
674
|
+
realm only, so under `jsdom` / `happy-dom` (a bare `vm` context) it is absent, the downlevelled
|
|
675
|
+
`using` throws out of `tslib.__addDisposableResource`, and `spy[Symbol.dispose]` degrades into a
|
|
676
|
+
property named `"undefined"`. The shim is the same registry symbol, so it is identical to Node's
|
|
677
|
+
across realms.
|
|
678
|
+
|
|
660
679
|
---
|
|
661
680
|
|
|
662
681
|
## 8. Observable assertions (core entry — no rxjs needed)
|
|
@@ -1539,6 +1558,37 @@ assertNgModuleScopes(DirectivesModule, PipesModule); // throws, naming the modul
|
|
|
1539
1558
|
Then declare what the spec needs in the TestBed module directly. Pass only modules you expect to
|
|
1540
1559
|
bring declarations — a providers-only module is legitimately empty.
|
|
1541
1560
|
|
|
1561
|
+
### A component whose own definition has a hole in it
|
|
1562
|
+
|
|
1563
|
+
The same bundle, one level down. A component's `providers`, `viewProviders` and compiled scope are
|
|
1564
|
+
**baked into `ɵcmp` when its module executes**, not read at `createComponent` time — so a barrel
|
|
1565
|
+
split into a chunk that has not run yet produces a definition with `undefined` in those lists.
|
|
1566
|
+
Angular finds out much later, from inside itself, with a stack that names neither the barrel nor the
|
|
1567
|
+
component:
|
|
1568
|
+
|
|
1569
|
+
```
|
|
1570
|
+
TypeError: Cannot read properties of undefined (reading 'provide')
|
|
1571
|
+
❯ resolveProvider render3/di_setup.ts:95
|
|
1572
|
+
```
|
|
1573
|
+
|
|
1574
|
+
Both obvious cures fail: `await import('@scope/lib')` at the top of `beforeEach` is already too late,
|
|
1575
|
+
and a static import at the top of the spec does not fix the order this bundler emits. Worse, the spec
|
|
1576
|
+
that breaks is one nobody touched — chunk boundaries move with file *contents*, so editing a type
|
|
1577
|
+
next door is enough.
|
|
1578
|
+
|
|
1579
|
+
```ts
|
|
1580
|
+
import { assertComponentDefIntact } from 'vitest-auto-spy/angular';
|
|
1581
|
+
|
|
1582
|
+
assertComponentDefIntact(HoverMenuComponent); // HoverMenuComponent.ɵcmp.providers[0] is undefined
|
|
1583
|
+
const fixture = TestBed.createComponent(HoverMenuComponent);
|
|
1584
|
+
```
|
|
1585
|
+
|
|
1586
|
+
It walks the three lists, nested arrays and forward-reference thunks included, and answers the
|
|
1587
|
+
related `Cannot read properties of undefined (reading 'ɵcmp')` from `imports: [Cmp]` as well — there
|
|
1588
|
+
the class reference itself never arrived, and the message names the argument position. Directives
|
|
1589
|
+
(`ɵdir`) are checked the same way. Neither this nor `assertNgModuleScopes` fixes the build; both
|
|
1590
|
+
replace a stack inside `@angular/core` with a line naming what is missing.
|
|
1591
|
+
|
|
1542
1592
|
### Four silent failures, as one setup line
|
|
1543
1593
|
|
|
1544
1594
|
```ts
|
|
@@ -2015,6 +2065,9 @@ packages, which a subpath export can never be.
|
|
|
2015
2065
|
| `overrideComponentProvider(…): the override did not apply` | the component injects a different token, or a later `overrideProvider` won | pass the token the component actually injects (a base class, an `InjectionToken`), and override after nothing else re-configures |
|
|
2016
2066
|
| `the test ended with N unflushed HttpTestingController request(s)` | `enableAngularDiagnostics({ pendingRequests })` — nothing answered them | flush each (`controller.expectOne(url).flush(body)`), or `controller.verify()` where absence is the assertion (§13) |
|
|
2017
2067
|
| `NgModule(s) with an empty runtime scope: …` | `ngModuleScopes` (or `assertNgModuleScopes`) — an AOT test bundle stripped `ɵɵsetNgModuleScope`, so the import contributes nothing | declare what the spec needs in the TestBed module directly; pass only modules expected to bring declarations (§13) |
|
|
2068
|
+
| `Cannot read properties of undefined (reading 'provide')`, stack inside `render3/di_setup` | a barrel chunk had not run when the component's definition was built, so a provider slot is `undefined` | `assertComponentDefIntact(Cmp)` before `createComponent` — it names the list and the index (§13) |
|
|
2069
|
+
| `assertComponentDefIntact(): argument N is undefined, which carries no ɵcmp or ɵdir` | the class reference itself never arrived — the `Cannot read properties of undefined (reading 'ɵcmp')` case | the same split barrel; import the component from its own module, not the barrel (§13) |
|
|
2070
|
+
| `TS1117: An object literal cannot have multiple properties with the same name`, in a fixture | a hundred-line model literal copied into eight specs and edited independently | one `createFixtureFactory<T>(defaults)`; note the runtime keeps the **second** key, so do not auto-fix by dropping one (§12) |
|
|
2018
2071
|
| `configureTestingModule was given N schema(s) that can never apply` | `enableAngularDiagnostics({ deadSchemas })` — `schemas` next to a standalone component, with `declarations` empty | drop the `schemas` entry and put the missing directive/pipe in the component's own `imports`, or use `createDirectiveHost` (§13) |
|
|
2019
2072
|
| `injectSpy(X): the injector returned a plain instance, not an auto-spy` | the token is provided for real; a `console.warn` on its own, a **throw** under `enableAngularDiagnostics({ unspiedProviders })` | `provideAutoSpy(X)` / `provideAutoSpyForToken(TOKEN)`, or `TestBed.inject(X)` if the real thing is what the spec wants |
|
|
2020
2073
|
| `trackInjections(...).get(X): that token is not tracked by this log` | `get` only answers for tokens whose providers the log created | add the token to the `trackInjections([...])` list, or read it from the injector directly (§13) |
|
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)
|
|
@@ -49,7 +49,7 @@ identical API, with **RxJS** spies and **Angular / NestJS / React / Vue·Pinia /
|
|
|
49
49
|
- ⚙️ Getter / setter spies via `accessorSpies`
|
|
50
50
|
- 🧰 DI & mocking utilities — `provideAutoSpy` / `injectSpy` (Angular, NestJS, Vue), `createFunctionSpy`, `mockReadonlyProp` for signals
|
|
51
51
|
- ⚡ Angular speed & zoneless helpers — `renderShallow` (**1.7×** on real component specs), `createWithAutoSpies`, `stable` / `flushEffects`, `settleResource` for `httpResource()`, `toHaveSignalValue`, per-file `TestBed` timings
|
|
52
|
-
- 🧱 The providers a testing module cannot reach — `overrideComponentProvider` (which verifies the override actually applied), `provideAutoSpyForToken`, `assertNgModuleScopes`, `createDirectiveHost`
|
|
52
|
+
- 🧱 The providers a testing module cannot reach — `overrideComponentProvider` (which verifies the override actually applied), `provideAutoSpyForToken`, `assertNgModuleScopes`, `assertComponentDefIntact`, `createDirectiveHost`
|
|
53
53
|
- 🚨 Failures that used to be silence — `enableAngularDiagnostics()` for dead NgModule imports, dead `schemas`, an unspied provider and unflushed HTTP requests; `trackInjections` for which collaborators the code actually asked for
|
|
54
54
|
- 🔒 [Strict doubles](#strict-doubles--fail-on-a-method-nobody-configured) — `strict: true` / `onUnstubbedCall` fail on a method nobody configured, naming the class, the method and the arguments instead of answering `undefined`
|
|
55
55
|
- ♻️ `using spy = createSpyFromClass(X)` — every double carries `[Symbol.dispose]`, so the `afterEach` that only reset one spy can go
|
|
@@ -58,7 +58,7 @@ identical API, with **RxJS** spies and **Angular / NestJS / React / Vue·Pinia /
|
|
|
58
58
|
- ⏳ Waiting that is not a guess — `flushEventLoop`, `settleDynamicImport`, `flushEventLoopUntil`, and a clock that survives fake timers (`mockSystemTime`, `useCountingClock`)
|
|
59
59
|
- 🌀 `fakeAsync` / `waitForAsync` on Vitest — one import of `vitest-auto-spy/zone`; zone.js stays out of every other entry
|
|
60
60
|
- 🧩 Module mocks that prove they applied — `assertMocked`, `moduleNamespace`, for a `vi.mock()` a bundler quietly ignored
|
|
61
|
-
- 🧾 Fixtures without casts — deep-partial `createMock`, `narrow()`, `withOverrides()`, `asInstances()`, `captureArg()`
|
|
61
|
+
- 🧾 Fixtures without casts — deep-partial `createMock`, `createFixture` / `createFixtureFactory`, `narrow()`, `withOverrides()`, `asInstances()`, `captureArg()`
|
|
62
62
|
- 🚚 A migration you can verify — `compareTestRuns` on the two JSON reports, `diffByField` for the assertion the reporter collapses
|
|
63
63
|
- 📏 Lint rules and one-line test-run hygiene — fourteen rules in `vitest-auto-spy/eslint-plugin` (two `--fix`, six suggestions), `setupAutoSpy()`
|
|
64
64
|
- 🩺 [Editor diagnostics](#editor-diagnostics--webstorm--vs-code) — the same anti-patterns underlined while you type: native ESLint inspections in **WebStorm** and the other JetBrains IDEs, the ESLint extension in **VS Code**, no extra plugin either way
|
|
@@ -1171,6 +1171,40 @@ Rule of thumb: `createAutoMock` for a collaborator you **call** and assert on, `
|
|
|
1171
1171
|
data shape you **read**. `createMock` is also the one place the `as` lives, so a suite under a
|
|
1172
1172
|
`no-type-assertion` lint rule stops sprinkling `eslint-disable` over its fixtures.
|
|
1173
1173
|
|
|
1174
|
+
### A model many specs build — `createFixture` / `createFixtureFactory`
|
|
1175
|
+
|
|
1176
|
+
`createMock` answers "this spec reads two fields of a big shape". The other habit is more expensive:
|
|
1177
|
+
a model with seventeen required fields, each with its own nested interface, copied into every spec
|
|
1178
|
+
that needs one. Measured on one migration shard, those copies alone produced **28 `TS1117`**
|
|
1179
|
+
diagnostics — a duplicate key in a literal, where the runtime keeps the *second* one.
|
|
1180
|
+
|
|
1181
|
+
```ts
|
|
1182
|
+
import { createFixtureFactory } from 'vitest-auto-spy';
|
|
1183
|
+
|
|
1184
|
+
// article.fixture.ts — the model, written out once and checked in full
|
|
1185
|
+
export const anArticle = createFixtureFactory<Article>({
|
|
1186
|
+
id: '1',
|
|
1187
|
+
header: { title: '', subtitle: 'none' },
|
|
1188
|
+
tags: [],
|
|
1189
|
+
publishedAt: new Date(0),
|
|
1190
|
+
});
|
|
1191
|
+
|
|
1192
|
+
// in a spec — name only what this test is about
|
|
1193
|
+
const draft = anArticle({ header: { title: 'Draft' } }); // header.subtitle survives
|
|
1194
|
+
```
|
|
1195
|
+
|
|
1196
|
+
`defaults` is a **complete** `T`, and that is the point rather than a chore: a field the model
|
|
1197
|
+
dropped six months ago fails in one place instead of in eight copies nobody re-checks. `Partial<T>`
|
|
1198
|
+
and `as T` both delete that diagnostic. Overrides are deep-partial-checked and merge leaf by leaf; an
|
|
1199
|
+
overridden array replaces the default one outright.
|
|
1200
|
+
|
|
1201
|
+
Every call returns a **new object**, and the defaults are copied when the factory is built — a
|
|
1202
|
+
fixture shared by reference is the most common way one test's mutation decides another's outcome, and
|
|
1203
|
+
under `isolate: false` that reaches across files. The copy is deep through plain objects and arrays
|
|
1204
|
+
and stops there: a `Date`, a `Map` or a class instance is carried across by reference rather than
|
|
1205
|
+
rebuilt without its prototype. For defaults that *are* a class instance with getters, snapshot them
|
|
1206
|
+
with `withOverrides()` first.
|
|
1207
|
+
|
|
1174
1208
|
## Synchronous methods
|
|
1175
1209
|
|
|
1176
1210
|
```ts
|
|
@@ -1302,10 +1336,14 @@ non-enumerable, so `{ ...spy }` does not carry it into a snapshot, and there is
|
|
|
1302
1336
|
`[Symbol.asyncDispose]` — `resetAutoSpy` is synchronous, and `await using` falls back to `@@dispose`
|
|
1303
1337
|
anyway.
|
|
1304
1338
|
|
|
1305
|
-
**The method is ours; the `using` declaration is your toolchain's.** `
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1339
|
+
**The method is ours; the `using` declaration is your toolchain's.** esbuild and `tsc` both downlevel
|
|
1340
|
+
the declaration. If your setup does not transpile, call `spy[Symbol.dispose]()` or
|
|
1341
|
+
`resetAutoSpy(spy)` directly. On **Node 22** the package installs the missing `Symbol.dispose` when
|
|
1342
|
+
it loads: the downlevelled form reads the symbol off the global `Symbol`, and Node 22 patches it in
|
|
1343
|
+
only on its main realm — under Vitest's `jsdom` environment, whose globals come from a `vm` context,
|
|
1344
|
+
it is absent and `using` throws `TypeError: Symbol.dispose is not defined.`. The shim is
|
|
1345
|
+
`Symbol.for('nodejs.dispose')`, the same registry symbol Node uses, so the key is identical across
|
|
1346
|
+
realms; a runtime that already has the symbol (Node 24+, Bun) is left untouched. A standalone `createFunctionSpy` is **not** covered: it is a host-runner mock, and Vitest
|
|
1309
1347
|
puts its own `[Symbol.dispose]` on those, which restores the original implementation rather than
|
|
1310
1348
|
reverting this library's configuration.
|
|
1311
1349
|
|
|
@@ -1813,6 +1851,8 @@ single-purpose utility you can pick up independently — they all ride on the sa
|
|
|
1813
1851
|
| `createFunctionSpy(name)` | core | A single standalone function spy with the full helper set (`calledWith`, `resolveWith`, `nextWith`, …) — no class needed |
|
|
1814
1852
|
| `createAutoMock<T>(overrides?)` | core | Proxy-based spy from a **type/interface** alone ([details](#auto-mock-by-type-no-class-needed)) |
|
|
1815
1853
|
| `createMock<T>(partial?)` | core | A plain, spy-free `T` built from the fields a test seeds — for data shapes, not collaborators |
|
|
1854
|
+
| `createFixture<T>(defaults, overrides?)` | core | One `T` from a complete, checked default plus what this test changes — a fresh copy every call |
|
|
1855
|
+
| `createFixtureFactory<T>(defaults)` | core | Somewhere to put that default: returns `(overrides?) => T`, with the defaults pinned at build time |
|
|
1816
1856
|
| `createObservableWithValues(configs, opts?)` | `/rxjs` | Build a fake `Observable` emitting a precise sequence of values / errors / completion |
|
|
1817
1857
|
| `consoleInfoSpy` / `consoleWarnSpy` / … | `/console` | Silent typed spies over the global `console`, installed on import ([details](#console-spies--vitest-auto-spyconsole)) |
|
|
1818
1858
|
| `mockReadonlyProp(obj, prop, value)` | `/angular` | Overwrite a `readonly` property (incl. Angular signals) with a static value |
|
|
@@ -1850,6 +1890,7 @@ single-purpose utility you can pick up independently — they all ride on the sa
|
|
|
1850
1890
|
| `registerFocusMatchers()` | `/setup` | Adds `expect(el).toHaveFocus()`, which names _why_ focus is elsewhere |
|
|
1851
1891
|
| `overrideAutoSpy(Token, config?)` / `overrideComponentProvider(Cmp, Token, config?)` | `/angular` | Replace a dependency a component declares in its own `providers` |
|
|
1852
1892
|
| `assertNgModuleScopes(...modules)` | `/angular` | Fail early when an AOT test bundle left an NgModule with no runtime declarations |
|
|
1893
|
+
| `assertComponentDefIntact(...components)` | `/angular` | Fail before rendering when a half-loaded barrel chunk left a hole in a component's own `providers` or scope |
|
|
1853
1894
|
| `enableAngularDiagnostics(opts?)` / `assertNoPendingRequests()` | `/angular` | Dead NgModule imports, dead `schemas`, an unspied provider and unflushed HTTP requests, as failures ([details](#diagnostics--four-silent-failures-made-loud)) |
|
|
1854
1895
|
| `trackInjections(tokens, opts?)` | `/angular`, `/nestjs` | Which collaborators DI actually constructed, recorded through provider factories — with the doubles attached |
|
|
1855
1896
|
| `mockSignalProp(obj, prop, initial)` | `/angular` | Replace a signal-valued property with a real `WritableSignal`, and hand the writable handle back |
|
|
@@ -2342,6 +2383,31 @@ asSpy(TestBed.inject(CartService)); // CartService → Spy<CartService>, for the
|
|
|
2342
2383
|
|
|
2343
2384
|
Both are the same object at runtime; only the view changes.
|
|
2344
2385
|
|
|
2386
|
+
### Error → cure
|
|
2387
|
+
|
|
2388
|
+
Keyed by what the compiler prints, because that is what you have when you get there. The helper
|
|
2389
|
+
names are unfindable from these messages otherwise — no `TS2739` text contains the word `asInstance`.
|
|
2390
|
+
|
|
2391
|
+
| Message | What actually happened | Cure |
|
|
2392
|
+
| ------------------------------------------------------------------------------- | ----------------------------------------- | -------------------------------------------------- |
|
|
2393
|
+
| `TS2352 … 'Spy<X>' … Property 'accessorSpies' is missing in type 'X'` | `x as Spy<X>`, written by hand | `asSpy(x)` — never a double assertion |
|
|
2394
|
+
| `TS2739` / `TS2740`: `'Spy<X>' is missing … ` + a list of **private** fields | a spy handed to an API typed against `X` | `asInstance(spy)` |
|
|
2395
|
+
| `TS2345: Argument of type 'Spy<X>' is not assignable to parameter of type 'X'` | the same, in an argument | `asInstance(spy)`, or `asInstances(a, b, c)` |
|
|
2396
|
+
| `TS2322: Type 'Spy<X>' is not assignable to type 'Mocked<X>' …` | the variable was declared `Mocked<T>` | declare it `Spy<T>` |
|
|
2397
|
+
| `'AddPromiseSpyMethods<unknown>' is missing … from type 'WithMockReturnValue<…>'` | a generic class inferred as `Service<any>` | `asSpy<Service>(…)` / `injectSpy<Service>(…)` |
|
|
2398
|
+
| `TS2739 … 'Spy<X>' is missing …` **on a line with `injectSpy`** | the provider handed back the real object | `provideAutoSpy(X)`, or an honest `TestBed.inject` |
|
|
2399
|
+
|
|
2400
|
+
Two notes that cost real time when they are missing.
|
|
2401
|
+
|
|
2402
|
+
**The last row is word for word the second one**, and that is a property of the language rather than
|
|
2403
|
+
a flaw in the table: one message serves two different mistakes. The tell is the line it lands on.
|
|
2404
|
+
|
|
2405
|
+
**The error count does not go down monotonically.** TypeScript stops checking a call at the first bad
|
|
2406
|
+
argument, so "one error left" means "as many as there are unfixed arguments" — one file counted
|
|
2407
|
+
40 → 1 → 1 → 1 → 0. Excess-property checking stops at the first unknown key of a literal for the same
|
|
2408
|
+
reason. The useful heuristic: if a file already contains one `asInstance`, it almost certainly needs
|
|
2409
|
+
another, and the next one is in that same file.
|
|
2410
|
+
|
|
2345
2411
|
## API reference
|
|
2346
2412
|
|
|
2347
2413
|
| Export | Description |
|
|
@@ -2349,6 +2415,7 @@ Both are the same object at runtime; only the view changes.
|
|
|
2349
2415
|
| `createSpyFromClass(Class, methodsOrConfig?)` | Build a fully-typed `Spy<T>` from a class |
|
|
2350
2416
|
| `createAutoMock<T>(overrides?, config?)` | Build a `Spy<T>` from a **type/interface** alone (Proxy, no class); `{ returns, observablePropsToSpyOn }` configure it |
|
|
2351
2417
|
| `createMock<T>(partial?)` | Build a plain, spy-free `T` from the fields a test seeds — for data shapes the code under test reads |
|
|
2418
|
+
| `createFixture<T>(defaults, overrides?)` / `createFixtureFactory<T>(defaults)` | A model written out and checked once, stamped into a fresh copy per test — for the fixture eight specs would otherwise each keep a copy of |
|
|
2352
2419
|
| `mockDeep<T>(overrides?, options?)` | Build a **recursive** auto-mock — `mock.repo.user.find()` chains without seeding; `{ selfReturning: true }` chains through calls too |
|
|
2353
2420
|
| `resetAutoSpy(spy)` / `clearAutoSpy(spy)` | Reset every spy in an auto-spy at once — `reset` also reverts return-value config (`calledWith` **and** a bare `mockReturnValue`); `clear` keeps it |
|
|
2354
2421
|
| `spy[Symbol.dispose]()` | What `using spy = createSpyFromClass(X)` runs at the end of the block — `resetAutoSpy(this)`, on every double including each `mockDeep` node |
|
|
@@ -2371,7 +2438,7 @@ Both are the same object at runtime; only the view changes.
|
|
|
2371
2438
|
| `autoMocked<T>(overrides?)` | `createAutoMock` typed as `T & Spy<T>` |
|
|
2372
2439
|
| `mockSystemTime` / `withSystemTime` / `mockNow` / `useCountingClock` _(`/setup`)_ | Clock control that survives fake timers being re-installed per test |
|
|
2373
2440
|
| `registerFocusMatchers()` _(`/setup`)_ | Adds `expect(el).toHaveFocus()` |
|
|
2374
|
-
| `overrideAutoSpy` / `overrideComponentProvider` / `assertNgModuleScopes` _(Angular)_ | Override a component-level provider — and verify on the next fixture that the override applied; diagnose an NgModule with an empty runtime scope |
|
|
2441
|
+
| `overrideAutoSpy` / `overrideComponentProvider` / `assertNgModuleScopes` / `assertComponentDefIntact` _(Angular)_ | Override a component-level provider — and verify on the next fixture that the override applied; diagnose an NgModule with an empty runtime scope |
|
|
2375
2442
|
| `enableAngularDiagnostics(opts?)` / `disableAngularDiagnostics()` / `assertNoPendingRequests()` _(Angular)_ | Dead NgModule imports, dead `schemas`, an unspied provider and unflushed HTTP requests, as failures |
|
|
2376
2443
|
| `trackInjections(tokens, opts?)` _(Angular, NestJS)_ | Providers that record which collaborators DI constructed, with an auto-spy behind each token |
|
|
2377
2444
|
| `setupAutoSpy(opts?)` _(`/setup`)_ | Property restore + duplicate-copy detection + mock-registry hygiene, in one call |
|
package/dist/angular.d.ts
CHANGED
|
@@ -35,6 +35,12 @@ import 'vitest';
|
|
|
35
35
|
* landed. So {@link overrideComponentProvider} also checks, on the next `TestBed.createComponent`,
|
|
36
36
|
* that the component's own injector really answers with the spy — see {@link verifyOnNextCreate}
|
|
37
37
|
* for why that check is always on rather than an opt-in diagnostic.
|
|
38
|
+
*
|
|
39
|
+
* The same AOT bundle is behind the two assertions at the end of the file.
|
|
40
|
+
* {@link assertNgModuleScopes} covers the module whose scope the bundler stripped;
|
|
41
|
+
* {@link assertComponentDefIntact} covers the component whose own definition was built while the
|
|
42
|
+
* chunk holding one of its providers had not run yet. Neither fixes a build — both replace a stack
|
|
43
|
+
* inside `@angular/core` with a line naming the thing that is missing.
|
|
38
44
|
*/
|
|
39
45
|
|
|
40
46
|
/** The `{ useValue }` shape `TestBed.overrideProvider` expects, carrying an auto-spy. */
|
|
@@ -75,6 +81,39 @@ declare function overrideAutoSpy<T>(ObjectClass: ClassType<T>, methodsToSpyOnOrC
|
|
|
75
81
|
* runtime scope the bundler has stripped — leaving the component with none of them.
|
|
76
82
|
*/
|
|
77
83
|
declare function overrideComponentProvider<T>(component: Type<unknown>, ObjectClass: ClassType<T>, methodsToSpyOnOrConfig?: ClassSpyConfiguration<T> | OnlyMethodKeysOf<T>[]): Spy<T>;
|
|
84
|
+
/**
|
|
85
|
+
* Fail before rendering when a component's own definition has holes in it.
|
|
86
|
+
*
|
|
87
|
+
* Providers are **baked into `ɵcmp` when the component's module executes**, not read at
|
|
88
|
+
* `createComponent` time. So when a bundler splits a barrel into a chunk that has not run yet, the
|
|
89
|
+
* definition is built with `undefined` where a provider or a scope dependency should be, and Angular
|
|
90
|
+
* discovers it much later, from inside itself:
|
|
91
|
+
*
|
|
92
|
+
* ```
|
|
93
|
+
* TypeError: Cannot read properties of undefined (reading 'provide')
|
|
94
|
+
* ❯ resolveProvider render3/di_setup.ts:95
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* The stack names neither the barrel, nor the symbol, nor the component — and the spec it breaks is
|
|
98
|
+
* usually one nobody touched, because chunk boundaries move with file *contents*: editing a type in
|
|
99
|
+
* a neighbouring file is enough. Both documented cures fail, too, and for the same reason: an
|
|
100
|
+
* `await import()` in `beforeEach` is already too late, and a static import at the top of the spec
|
|
101
|
+
* does not fix the order this bundler emits.
|
|
102
|
+
*
|
|
103
|
+
* ```ts
|
|
104
|
+
* assertComponentDefIntact(HoverMenuComponent);
|
|
105
|
+
* const fixture = TestBed.createComponent(HoverMenuComponent);
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* The same call answers the related `Cannot read properties of undefined (reading 'ɵcmp')` from
|
|
109
|
+
* `imports: [Cmp]`, where the class reference itself is the thing that never arrived.
|
|
110
|
+
*
|
|
111
|
+
* This does not fix the build — that is a bundler configuration question — but it turns a
|
|
112
|
+
* half-hour investigation into one line, and points it away from the spec.
|
|
113
|
+
*
|
|
114
|
+
* @param components The component (or directive) classes a spec is about to render or import.
|
|
115
|
+
*/
|
|
116
|
+
declare function assertComponentDefIntact(...components: unknown[]): void;
|
|
78
117
|
/**
|
|
79
118
|
* Fail early when an NgModule imported into the TestBed contributes nothing at runtime.
|
|
80
119
|
*
|
|
@@ -576,4 +615,4 @@ declare function reportSpecTiming(timing: SpecTiming): void;
|
|
|
576
615
|
*/
|
|
577
616
|
declare function enableTestBedDiagnostics(options?: TestBedDiagnosticsOptions): void;
|
|
578
617
|
|
|
579
|
-
export { type AngularDiagnosticsOptions, type AngularTestEnvMode, type AngularTestEnvOptions, type AutoSpyOverride, type DirectiveHostOptions, type MockedResource, type ResourceDouble, type ResourceDoubleStatus, type ResourceLike, type SignalLike, type SpecTiming, type TestBedDiagnosticsOptions, assertNgModuleScopes, assertNoPendingRequests, createDirectiveHost, disableAngularDiagnostics, disableTestBedDiagnostics, enableAngularDiagnostics, enableTestBedDiagnostics, formatSpecTiming, getTestBedTiming, instrumentTestBed, mockResourceProp, mockSignalProp, overrideAutoSpy, overrideComponentProvider, registerDirectiveMatchers, registerResourceMatchers, registerSignalMatchers, reportSpecTiming, setupAngularTestEnv };
|
|
618
|
+
export { type AngularDiagnosticsOptions, type AngularTestEnvMode, type AngularTestEnvOptions, type AutoSpyOverride, type DirectiveHostOptions, type MockedResource, type ResourceDouble, type ResourceDoubleStatus, type ResourceLike, type SignalLike, type SpecTiming, type TestBedDiagnosticsOptions, assertComponentDefIntact, assertNgModuleScopes, assertNoPendingRequests, createDirectiveHost, disableAngularDiagnostics, disableTestBedDiagnostics, enableAngularDiagnostics, enableTestBedDiagnostics, formatSpecTiming, getTestBedTiming, instrumentTestBed, mockResourceProp, mockSignalProp, overrideAutoSpy, overrideComponentProvider, registerDirectiveMatchers, registerResourceMatchers, registerSignalMatchers, reportSpecTiming, setupAngularTestEnv };
|
package/dist/angular.js
CHANGED
|
@@ -42,6 +42,19 @@ function useVitestAdapter() {
|
|
|
42
42
|
registerMockAdapter(vitestMockAdapter);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// src/lib/dispose-symbol.ts
|
|
46
|
+
var NODE_DISPOSE_KEY = "nodejs.dispose";
|
|
47
|
+
function installDisposeSymbol(host) {
|
|
48
|
+
const existing = Reflect.get(host, "dispose");
|
|
49
|
+
if (typeof existing === "symbol") {
|
|
50
|
+
return existing;
|
|
51
|
+
}
|
|
52
|
+
const shim = Symbol.for(NODE_DISPOSE_KEY);
|
|
53
|
+
Object.defineProperty(host, "dispose", { value: shim, enumerable: false, configurable: true, writable: false });
|
|
54
|
+
return shim;
|
|
55
|
+
}
|
|
56
|
+
var DISPOSE = installDisposeSymbol(Symbol);
|
|
57
|
+
|
|
45
58
|
// src/lib/docs-links.ts
|
|
46
59
|
var DOCS = "https://asdalexey.github.io/vitest-auto-spy";
|
|
47
60
|
var DOCS_LINKS = {
|
|
@@ -692,7 +705,7 @@ function disposeAutoSpy() {
|
|
|
692
705
|
resetAutoSpy(this);
|
|
693
706
|
}
|
|
694
707
|
function attachDispose(spy) {
|
|
695
|
-
Object.defineProperty(spy,
|
|
708
|
+
Object.defineProperty(spy, DISPOSE, { value: disposeAutoSpy, enumerable: false, configurable: true, writable: true });
|
|
696
709
|
}
|
|
697
710
|
|
|
698
711
|
// src/lib/auto-mock.ts
|
|
@@ -750,7 +763,7 @@ function createAutoMockHandler(seed, unstubbed) {
|
|
|
750
763
|
defineProperty: (_target, key, descriptor) => storeDefinedProp(store, key, descriptor),
|
|
751
764
|
deleteProperty: (_target, key) => dropStoredProp(store, key),
|
|
752
765
|
has(_target, key) {
|
|
753
|
-
return key === AUTO_SPY_MARK || key ===
|
|
766
|
+
return key === AUTO_SPY_MARK || key === DISPOSE || hasStoredProp(store, key);
|
|
754
767
|
},
|
|
755
768
|
ownKeys() {
|
|
756
769
|
return [.../* @__PURE__ */ new Set([...store.values.keys(), ...store.accessors.keys()])];
|
|
@@ -769,7 +782,7 @@ function readKey(store, key, receiver, unstubbed) {
|
|
|
769
782
|
if (key === AUTO_SPY_MARK) {
|
|
770
783
|
return true;
|
|
771
784
|
}
|
|
772
|
-
if (key ===
|
|
785
|
+
if (key === DISPOSE) {
|
|
773
786
|
return disposeAutoSpy;
|
|
774
787
|
}
|
|
775
788
|
if (typeof key === "symbol" || key === "then" || key === "constructor" || isProtocolKey(key) || isDeletedProp(store, key)) {
|
|
@@ -1402,6 +1415,66 @@ function moduleName(module) {
|
|
|
1402
1415
|
const name = readProperty(module, "name");
|
|
1403
1416
|
return typeof name === "string" ? name : String(module);
|
|
1404
1417
|
}
|
|
1418
|
+
var DEFINITION_LISTS = ["providers", "viewProviders", "dependencies"];
|
|
1419
|
+
function resolveList(list) {
|
|
1420
|
+
if (typeof list !== "function") {
|
|
1421
|
+
return list;
|
|
1422
|
+
}
|
|
1423
|
+
try {
|
|
1424
|
+
const produced = Reflect.apply(list, void 0, []);
|
|
1425
|
+
return produced;
|
|
1426
|
+
} catch {
|
|
1427
|
+
return void 0;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
function collectHoles(list, path, holes) {
|
|
1431
|
+
const resolved = resolveList(list);
|
|
1432
|
+
if (!Array.isArray(resolved)) {
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
resolved.forEach((entry, index) => {
|
|
1436
|
+
const at = `${path}[${index}]`;
|
|
1437
|
+
if (entry === void 0 || entry === null) {
|
|
1438
|
+
holes.push(at);
|
|
1439
|
+
} else if (Array.isArray(entry)) {
|
|
1440
|
+
collectHoles(entry, at, holes);
|
|
1441
|
+
}
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
function definitionOf(type) {
|
|
1445
|
+
const component = readProperty(type, "\u0275cmp");
|
|
1446
|
+
if (component !== void 0) {
|
|
1447
|
+
return { definition: component, key: "\u0275cmp" };
|
|
1448
|
+
}
|
|
1449
|
+
const directive = readProperty(type, "\u0275dir");
|
|
1450
|
+
return directive === void 0 ? void 0 : { definition: directive, key: "\u0275dir" };
|
|
1451
|
+
}
|
|
1452
|
+
function assertComponentDefIntact(...components) {
|
|
1453
|
+
components.forEach((component, position) => {
|
|
1454
|
+
const named = moduleName(component);
|
|
1455
|
+
const found = definitionOf(component);
|
|
1456
|
+
if (!found) {
|
|
1457
|
+
throw new Error(
|
|
1458
|
+
withDocs(
|
|
1459
|
+
`[vitest-auto-spy] assertComponentDefIntact(): argument ${position} is ${named}, which carries no \u0275cmp or \u0275dir.
|
|
1460
|
+
Either it is not a component or directive, or the chunk that defines it has not executed yet \u2014 the import resolved to nothing. A barrel split across chunks is the usual cause.`,
|
|
1461
|
+
DOCS_LINKS.angularOverrides
|
|
1462
|
+
)
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
const holes = [];
|
|
1466
|
+
DEFINITION_LISTS.forEach((list) => collectHoles(readProperty(found.definition, list), `${named}.${found.key}.${list}`, holes));
|
|
1467
|
+
if (holes.length > 0) {
|
|
1468
|
+
throw new Error(
|
|
1469
|
+
withDocs(
|
|
1470
|
+
`[vitest-auto-spy] ${holes.join(", ")} ${holes.length === 1 ? "is" : "are"} undefined.
|
|
1471
|
+
A component bakes its providers and its scope into the definition when its module executes, so a hole there means the chunk holding that symbol had not run at that moment \u2014 an uninitialised barrel chunk. Angular reports this much later as "Cannot read properties of undefined (reading 'provide')", from inside its own provider resolution.`,
|
|
1472
|
+
DOCS_LINKS.angularOverrides
|
|
1473
|
+
)
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1405
1478
|
function assertNgModuleScopes(...modules) {
|
|
1406
1479
|
const empty = modules.filter(hasEmptyRuntimeScope).map(moduleName);
|
|
1407
1480
|
if (empty.length === 0) {
|
|
@@ -2009,4 +2082,4 @@ function registerSignalMatchers() {
|
|
|
2009
2082
|
}
|
|
2010
2083
|
useVitestAdapter();
|
|
2011
2084
|
|
|
2012
|
-
export { assertNgModuleScopes, assertNoPendingRequests, countMockedProps, createDirectiveHost, createWithAutoSpies, disableAngularDiagnostics, disableTestBedDiagnostics, enableAngularDiagnostics, enableTestBedDiagnostics, flushEffects, formatSpecTiming, getTestBedTiming, injectSpy, instrumentTestBed, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockResourceProp, mockSignalProp, mockValueProp, overrideAutoSpy, overrideComponentProvider, provideAutoSpy, provideAutoSpyForToken, registerDirectiveMatchers, registerResourceMatchers, registerSignalMatchers, renderShallow, reportSpecTiming, restoreMockedProps, runEffect, settleResource, setupAngularTestEnv, stable, trackInjections };
|
|
2085
|
+
export { assertComponentDefIntact, assertNgModuleScopes, assertNoPendingRequests, countMockedProps, createDirectiveHost, createWithAutoSpies, disableAngularDiagnostics, disableTestBedDiagnostics, enableAngularDiagnostics, enableTestBedDiagnostics, flushEffects, formatSpecTiming, getTestBedTiming, injectSpy, instrumentTestBed, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockResourceProp, mockSignalProp, mockValueProp, overrideAutoSpy, overrideComponentProvider, provideAutoSpy, provideAutoSpyForToken, registerDirectiveMatchers, registerResourceMatchers, registerSignalMatchers, renderShallow, reportSpecTiming, restoreMockedProps, runEffect, settleResource, setupAngularTestEnv, stable, trackInjections };
|
package/dist/bun-angular.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { d as AccessorKeysOf, e as AddAccessorsSpies, f as AddCalledWithObservable, g as AddCalledWithPromise, h as AddCalledWithSpyMethods, A as AddObservableSpyMethods, i as AddPromiseSpyMethods, j as AddSpyMethodsByReturnTypes, k as AddVoidReturnHelpers, a as ClassSpyConfiguration, C as ClassType, b as CompleteValueConfig, D as DeepMockProxy, l as DeepPartial, E as ErrorValueConfig, F as Func, M as MethodReturns, m as Mutable, N as NextValueConfig, O as OnlyMethodKeysOf, n as OnlyObservablePropsOf, o as OnlyPropsOf, p as Overload, q as Overloads, P as PropStubValue, S as Spy, r as SpyDisposable, s as SpyOptions, t as StrictSpyConfiguration, U as UnstubbedCall, u as UnstubbedCallHandler, V as ValueConfig, c as ValueConfigPerCall, W as WithMockReturnValue } from './types-it6CnDkI.js';
|
|
2
|
-
export { ArgCaptor, AsInstances, AssertMockedOptions, ConstructorMock, ConstructorSpy, FlushUntilOptions, IntersectionObserverStubOptions, MediaElementState, MediaElementStub, MediaElementStubOptions, MockDeepOptions, ModuleNamespace, ModuleNamespaceOptions, MutationRecordInit, ObserverGlobal, ObserverInstance, ObserverStub, ObserverStubOptions, ResizeEntryRect, TestRunComparison, TestRunReport, TestRunSummary, asInstance, asInstances, asSpy, assertMocked, captureArg, clearAutoSpy, compareTestRuns, createFunctionSpy, createMock, createSpyClass, createSpyFromClass, diffByField, errorHandler, flushEventLoop, flushEventLoopUntil, formatTestRunComparison, intersectionEntry, mockConstructor, mockDeep, moduleNamespace, mutationRecord, narrow, resetAutoSpy, resizeEntry, settleDynamicImport, stubAbortController, stubConstructor, stubIntersectionObserver, stubMediaElement, stubMutationObserver, stubObserver, stubResizeObserver, summarizeTestRun, withOverrides } from './bun.js';
|
|
2
|
+
export { ArgCaptor, AsInstances, AssertMockedOptions, ConstructorMock, ConstructorSpy, FixtureFactory, FlushUntilOptions, IntersectionObserverStubOptions, MediaElementState, MediaElementStub, MediaElementStubOptions, MockDeepOptions, ModuleNamespace, ModuleNamespaceOptions, MutationRecordInit, ObserverGlobal, ObserverInstance, ObserverStub, ObserverStubOptions, ResizeEntryRect, TestRunComparison, TestRunReport, TestRunSummary, asInstance, asInstances, asSpy, assertMocked, captureArg, clearAutoSpy, compareTestRuns, createFixture, createFixtureFactory, createFunctionSpy, createMock, createSpyClass, createSpyFromClass, diffByField, errorHandler, flushEventLoop, flushEventLoopUntil, formatTestRunComparison, intersectionEntry, mockConstructor, mockDeep, moduleNamespace, mutationRecord, narrow, resetAutoSpy, resizeEntry, settleDynamicImport, stubAbortController, stubConstructor, stubIntersectionObserver, stubMediaElement, stubMutationObserver, stubObserver, stubResizeObserver, summarizeTestRun, withOverrides } from './bun.js';
|
|
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-CuGjE0YR.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-IgCwv_40.js';
|
|
5
5
|
export { d as describeDuplicateCopies, g as getPackageCopies } from './package-identity-B1pqa-Sh.js';
|
package/dist/bun-angular.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import './chunk-Y4YG7UOK.js';
|
|
2
|
-
import { createSpyForToken } from './chunk-
|
|
2
|
+
import { createSpyForToken } from './chunk-JV5XRAS4.js';
|
|
3
3
|
import './chunk-NQXXUJPS.js';
|
|
4
|
-
export { asInstance, asInstances, asSpy, assertMocked, captureArg, compareTestRuns, createMock, createSpyClass, describeDuplicateCopies, diffByField, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, formatTestRunComparison, getPackageCopies, intersectionEntry, mockConstructor, mockDeep, moduleNamespace, mutationRecord, narrow, resizeEntry, setEmissionTimeout, settleDynamicImport, stubAbortController, stubConstructor, stubIntersectionObserver, stubMediaElement, stubMutationObserver, stubObserver, stubResizeObserver, summarizeTestRun, withOverrides } from './chunk-
|
|
4
|
+
export { asInstance, asInstances, asSpy, assertMocked, captureArg, compareTestRuns, createFixture, createFixtureFactory, createMock, createSpyClass, describeDuplicateCopies, diffByField, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, formatTestRunComparison, getPackageCopies, intersectionEntry, mockConstructor, mockDeep, moduleNamespace, mutationRecord, narrow, resizeEntry, setEmissionTimeout, settleDynamicImport, stubAbortController, stubConstructor, stubIntersectionObserver, stubMediaElement, stubMutationObserver, stubObserver, stubResizeObserver, summarizeTestRun, withOverrides } from './chunk-ZBVBJFNW.js';
|
|
5
5
|
export { countMockedProps, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockValueProp, restoreMockedProps } from './chunk-R4EWAG7Y.js';
|
|
6
|
-
import { createSpyFromClass } from './chunk-
|
|
7
|
-
export { autoMocked, clearAutoSpy, createAutoMock, createSpyFromClass, resetAutoSpy } from './chunk-
|
|
6
|
+
import { createSpyFromClass } from './chunk-7YTPHYP7.js';
|
|
7
|
+
export { autoMocked, clearAutoSpy, createAutoMock, createSpyFromClass, resetAutoSpy } from './chunk-7YTPHYP7.js';
|
|
8
8
|
import { withDocs, DOCS_LINKS, isAutoSpyLike } from './chunk-3AWKPZCE.js';
|
|
9
9
|
export { createFunctionSpy, errorHandler } from './chunk-3AWKPZCE.js';
|
|
10
10
|
import './chunk-J5O72UHX.js';
|
package/dist/bun.d.ts
CHANGED
|
@@ -76,6 +76,88 @@ declare function createSpyFromClass<T, Options extends SpyOptions = SpyOptions>(
|
|
|
76
76
|
*/
|
|
77
77
|
declare function createMock<T>(partial?: DeepPartial<T>): T;
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* `createFixture` / `createFixtureFactory` — one checked copy of a data model, handed out fresh.
|
|
81
|
+
*
|
|
82
|
+
* Specs do not only spy; they *build data*, and that is where a suite meets the type checker for
|
|
83
|
+
* the first time. A content model with seventeen required fields, each with its own nested
|
|
84
|
+
* interface, gets copied into every spec that needs it: eight files, a hundred-line literal apiece.
|
|
85
|
+
* The copies then rot independently. Measured on one migration shard, that single habit produced
|
|
86
|
+
* **28 `TS1117`** diagnostics (a duplicate key in a literal — the runtime keeps the *second*, so an
|
|
87
|
+
* automated "drop one" fix silently changes values) and half of the shard's `TS2741`.
|
|
88
|
+
*
|
|
89
|
+
* The two obvious escapes are worse than the problem:
|
|
90
|
+
*
|
|
91
|
+
* - `as T` and `Partial<T>` both delete the diagnostic that made the migration worth doing —
|
|
92
|
+
* `TS2353`, "this fixture still sets a field the model dropped six months ago". A fixture that
|
|
93
|
+
* cannot be wrong about the model is a fixture that never reports the model changed.
|
|
94
|
+
* - {@link createAutoMock} builds an object from a type, but only its **methods**. A data model has
|
|
95
|
+
* none, so the machine walks straight past the place the errors actually are.
|
|
96
|
+
*
|
|
97
|
+
* So the answer is not another way to skip the fields. It is somewhere to *put* them once:
|
|
98
|
+
* {@link createFixtureFactory} takes a complete, fully checked `T` — every required field, every
|
|
99
|
+
* removed field rejected, one place for the compiler to point at — and returns a function that
|
|
100
|
+
* stamps out copies with per-test changes applied. The check happens once; the fifty call sites
|
|
101
|
+
* that follow name only what they care about.
|
|
102
|
+
*
|
|
103
|
+
* **Each call returns a new object.** That is not a performance footnote — a fixture shared by
|
|
104
|
+
* reference across a file is the most common way one test's mutation decides another test's
|
|
105
|
+
* outcome, and under `isolate: false` the sharing reaches across files. The copy is deep through
|
|
106
|
+
* plain objects and arrays and stops there: a `Date`, a `Map`, a DOM node or a class instance is
|
|
107
|
+
* carried across by reference, because rebuilding one would strip its prototype — accessors
|
|
108
|
+
* included — and hand back something that is no longer the model it claims to be. When the defaults
|
|
109
|
+
* *are* a class instance with getters, snapshot it with {@link withOverrides} first and hand the
|
|
110
|
+
* result here.
|
|
111
|
+
*
|
|
112
|
+
* | | `createMock<T>()` | `createFixture<T>()` |
|
|
113
|
+
* | --- | --- | --- |
|
|
114
|
+
* | Input | the fields this test reads | a complete `T`, plus what this test changes |
|
|
115
|
+
* | Unnamed fields | `undefined` at runtime | the default's value |
|
|
116
|
+
* | Use it for | a shape read shallowly, once | a model many specs build, read deeply, and mutate |
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
/** Stamps out one fixture per call. What {@link createFixtureFactory} returns. */
|
|
120
|
+
type FixtureFactory<T> = (overrides?: DeepPartial<T>) => T;
|
|
121
|
+
/**
|
|
122
|
+
* Build one `T` from a complete default and the fields this test changes.
|
|
123
|
+
*
|
|
124
|
+
* ```ts
|
|
125
|
+
* const article = createFixture<Article>(ARTICLE_DEFAULTS, { header: { title: 'Draft' } });
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* `defaults` is a whole `T`, so a field the model removed is a compile error here rather than a
|
|
129
|
+
* value nothing reads; `overrides` is checked at every depth against the same type. Sibling fields
|
|
130
|
+
* of an overridden leaf are kept — `header.subtitle` above survives — while an overridden array
|
|
131
|
+
* replaces the default one entirely.
|
|
132
|
+
*
|
|
133
|
+
* Reach for {@link createFixtureFactory} as soon as a second spec needs the same defaults; reach
|
|
134
|
+
* for {@link createMock} when there are no meaningful defaults to begin with and the test reads two
|
|
135
|
+
* fields of a large shape.
|
|
136
|
+
*
|
|
137
|
+
* @param defaults A complete `T`. Checked in full: missing and removed fields both fail here.
|
|
138
|
+
* @param overrides The fields this fixture changes, checked against `T` at every depth.
|
|
139
|
+
*/
|
|
140
|
+
declare function createFixture<T>(defaults: T, overrides?: DeepPartial<T>): T;
|
|
141
|
+
/**
|
|
142
|
+
* Somewhere to put a fixture, so that the model is written out and checked exactly once.
|
|
143
|
+
*
|
|
144
|
+
* ```ts
|
|
145
|
+
* // article.fixture.ts — one file, one checked literal
|
|
146
|
+
* export const anArticle = createFixtureFactory<Article>({ id: '1', header: { title: '', subtitle: '' }, tags: [], … });
|
|
147
|
+
*
|
|
148
|
+
* // in a spec
|
|
149
|
+
* const draft = anArticle({ header: { title: 'Draft' } });
|
|
150
|
+
* const tagged = anArticle({ tags: ['news'] });
|
|
151
|
+
* ```
|
|
152
|
+
*
|
|
153
|
+
* The defaults are copied when the factory is built, so a later edit to the object that was passed
|
|
154
|
+
* in cannot reach a fixture already handed out — and every call returns a fresh copy, so one test
|
|
155
|
+
* mutating what it was given cannot decide what the next one sees.
|
|
156
|
+
*
|
|
157
|
+
* @param defaults A complete `T`, checked once, here.
|
|
158
|
+
*/
|
|
159
|
+
declare function createFixtureFactory<T>(defaults: T): FixtureFactory<T>;
|
|
160
|
+
|
|
79
161
|
/**
|
|
80
162
|
* Behaviour switches for {@link mockDeep}.
|
|
81
163
|
*
|
|
@@ -1105,4 +1187,4 @@ interface ResizeEntryRect {
|
|
|
1105
1187
|
*/
|
|
1106
1188
|
declare function resizeEntry(target: Element, rect?: ResizeEntryRect): ResizeObserverEntry;
|
|
1107
1189
|
|
|
1108
|
-
export { AddSpyMethodsByReturnTypes, type ArgCaptor, type AsInstances, type AssertMockedOptions, ClassSpyConfiguration, ClassType, type ConstructorMock, type ConstructorSpy, DeepMockProxy, DeepPartial, type FlushUntilOptions, Func, type IntersectionObserverStubOptions, type MediaElementState, type MediaElementStub, type MediaElementStubOptions, type MockDeepOptions, type ModuleNamespace, type ModuleNamespaceOptions, type MutationRecordInit, type ObserverGlobal, type ObserverInstance, type ObserverStub, type ObserverStubOptions, OnlyMethodKeysOf, type ResizeEntryRect, Spy, SpyOptions, type TestRunComparison, type TestRunReport, type TestRunSummary, UnstubbedCallHandler, asInstance, asInstances, asSpy, assertMocked, captureArg, clearAutoSpy, compareTestRuns, createFunctionSpy, createMock, createSpyClass, createSpyFromClass, diffByField, errorHandler, flushEventLoop, flushEventLoopUntil, formatTestRunComparison, intersectionEntry, mockConstructor, mockDeep, moduleNamespace, mutationRecord, narrow, resetAutoSpy, resizeEntry, settleDynamicImport, stubAbortController, stubConstructor, stubIntersectionObserver, stubMediaElement, stubMutationObserver, stubObserver, stubResizeObserver, summarizeTestRun, withOverrides };
|
|
1190
|
+
export { AddSpyMethodsByReturnTypes, type ArgCaptor, type AsInstances, type AssertMockedOptions, ClassSpyConfiguration, ClassType, type ConstructorMock, type ConstructorSpy, DeepMockProxy, DeepPartial, type FixtureFactory, type FlushUntilOptions, Func, type IntersectionObserverStubOptions, type MediaElementState, type MediaElementStub, type MediaElementStubOptions, type MockDeepOptions, type ModuleNamespace, type ModuleNamespaceOptions, type MutationRecordInit, type ObserverGlobal, type ObserverInstance, type ObserverStub, type ObserverStubOptions, OnlyMethodKeysOf, type ResizeEntryRect, Spy, SpyOptions, type TestRunComparison, type TestRunReport, type TestRunSummary, UnstubbedCallHandler, asInstance, asInstances, asSpy, assertMocked, captureArg, clearAutoSpy, compareTestRuns, createFixture, createFixtureFactory, createFunctionSpy, createMock, createSpyClass, createSpyFromClass, diffByField, errorHandler, flushEventLoop, flushEventLoopUntil, formatTestRunComparison, intersectionEntry, mockConstructor, mockDeep, moduleNamespace, mutationRecord, narrow, resetAutoSpy, resizeEntry, settleDynamicImport, stubAbortController, stubConstructor, stubIntersectionObserver, stubMediaElement, stubMutationObserver, stubObserver, stubResizeObserver, summarizeTestRun, withOverrides };
|
package/dist/bun.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import './chunk-Y4YG7UOK.js';
|
|
2
2
|
import './chunk-NQXXUJPS.js';
|
|
3
|
-
export { asInstance, asInstances, asSpy, assertMocked, captureArg, compareTestRuns, createMock, createSpyClass, describeDuplicateCopies, diffByField, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, formatTestRunComparison, getPackageCopies, intersectionEntry, mockConstructor, mockDeep, moduleNamespace, mutationRecord, narrow, resizeEntry, setEmissionTimeout, settleDynamicImport, stubAbortController, stubConstructor, stubIntersectionObserver, stubMediaElement, stubMutationObserver, stubObserver, stubResizeObserver, summarizeTestRun, withOverrides } from './chunk-
|
|
3
|
+
export { asInstance, asInstances, asSpy, assertMocked, captureArg, compareTestRuns, createFixture, createFixtureFactory, createMock, createSpyClass, describeDuplicateCopies, diffByField, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, formatTestRunComparison, getPackageCopies, intersectionEntry, mockConstructor, mockDeep, moduleNamespace, mutationRecord, narrow, resizeEntry, setEmissionTimeout, settleDynamicImport, stubAbortController, stubConstructor, stubIntersectionObserver, stubMediaElement, stubMutationObserver, stubObserver, stubResizeObserver, summarizeTestRun, withOverrides } from './chunk-ZBVBJFNW.js';
|
|
4
4
|
export { countMockedProps, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockValueProp, restoreMockedProps } from './chunk-R4EWAG7Y.js';
|
|
5
|
-
export { autoMocked, clearAutoSpy, createAutoMock, createSpyFromClass, resetAutoSpy } from './chunk-
|
|
5
|
+
export { autoMocked, clearAutoSpy, createAutoMock, createSpyFromClass, resetAutoSpy } from './chunk-7YTPHYP7.js';
|
|
6
6
|
export { createFunctionSpy, errorHandler } from './chunk-3AWKPZCE.js';
|
|
7
7
|
import './chunk-J5O72UHX.js';
|