vitest-auto-spy 5.24.0 → 5.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +203 -22
- package/README.md +57 -33
- package/dist/angular-diagnostics.js +10 -10
- package/dist/angular-doubles.d.ts +1 -1
- package/dist/angular-doubles.js +7 -7
- package/dist/angular-http.js +4 -4
- package/dist/angular-router.d.ts +11 -1
- package/dist/angular-router.js +56 -34
- package/dist/angular.d.ts +10 -6
- package/dist/angular.js +115 -22
- package/dist/bun-angular.d.ts +6 -6
- package/dist/bun-angular.js +71 -25
- package/dist/bun.d.ts +46 -10
- package/dist/bun.js +9 -9
- package/dist/{chunk-V6TIK7XU.js → chunk-2ISMAFNN.js} +2 -2
- package/dist/{chunk-QO4BPR3V.js → chunk-2XXGISYD.js} +2 -1
- package/dist/{chunk-45W5LBPY.js → chunk-34S5ZMWU.js} +5 -5
- package/dist/{chunk-U5NJJ4ZN.js → chunk-3MRCVCRZ.js} +1 -1
- package/dist/{chunk-DQ3SPSCH.js → chunk-6FIKN2RJ.js} +27 -7
- package/dist/{chunk-YEQQIKQR.js → chunk-6RP37RWX.js} +1 -1
- package/dist/{chunk-MBSPAUUD.js → chunk-73FGLUVI.js} +3 -3
- package/dist/{chunk-BBPYNKGB.js → chunk-7TGT2436.js} +1 -1
- package/dist/{chunk-6UETTNQH.js → chunk-FZMWANZV.js} +1 -1
- package/dist/{chunk-ZCWAFGHX.js → chunk-IOBBADJP.js} +2 -2
- package/dist/{chunk-CPLUHRHD.js → chunk-L6VPMLRJ.js} +1 -1
- package/dist/{chunk-MUZZBRUP.js → chunk-R7DIFC2L.js} +2 -2
- package/dist/{chunk-RGRZDCM6.js → chunk-ROSQKG22.js} +1 -1
- package/dist/{chunk-LZVIH54N.js → chunk-V4XBUP4D.js} +24 -8
- package/dist/{chunk-JBV35SU3.js → chunk-V76LFJW4.js} +1 -1
- package/dist/{chunk-E5M7DBWB.js → chunk-YCWKPW3N.js} +55 -12
- package/dist/{chunk-MG7LKE4R.js → chunk-YZSBSAYZ.js} +1 -1
- package/dist/cli.js +438 -125
- package/dist/console.d.ts +1 -1
- package/dist/console.js +7 -7
- package/dist/dom-stubs.d.ts +86 -4
- package/dist/dom-stubs.js +167 -19
- package/dist/eslint-plugin.cjs +363 -118
- package/dist/eslint-plugin.d.ts +4 -2
- package/dist/{expect-emission-CD6thEE8.d.ts → expect-emission-DoGUvahc.d.ts} +15 -2
- package/dist/index.d.ts +5 -5
- package/dist/index.js +95 -16
- package/dist/jasmine-compat.d.ts +1 -1
- package/dist/jasmine-compat.js +2 -2
- package/dist/jasmine.d.ts +2 -2
- package/dist/jasmine.js +10 -10
- package/dist/nestjs.d.ts +2 -2
- package/dist/nestjs.js +10 -10
- package/dist/node.cjs +96 -15
- package/dist/node.d.cts +2 -0
- package/dist/node.d.ts +5 -5
- package/dist/node.js +95 -16
- package/dist/{package-identity-CqrrGVDm.d.ts → package-identity-BiGXcUoQ.d.ts} +1 -1
- package/dist/perf-reporter.d.ts +131 -0
- package/dist/perf-reporter.js +4 -1
- package/dist/{prop-mock-yHR11U7R.d.ts → prop-mock-C0VtO_pe.d.ts} +11 -8
- package/dist/react.d.ts +5 -5
- package/dist/react.js +95 -16
- package/dist/rstest.d.ts +5 -5
- package/dist/rstest.js +9 -9
- package/dist/rxjs.d.ts +2 -2
- package/dist/rxjs.js +2 -2
- package/dist/setup.d.ts +275 -5
- package/dist/setup.js +491 -75
- package/dist/signal-forms.js +1 -1
- package/dist/svelte.d.ts +5 -5
- package/dist/svelte.js +95 -16
- package/dist/{track-injections-L6IgAUL7.d.ts → track-injections-DLl6ra2D.d.ts} +1 -1
- package/dist/{track-signal-runs-DsBgqliM.d.ts → track-signal-runs-DS7_MCPC.d.ts} +18 -4
- package/dist/{types-BuQtoidb.d.ts → types-CUf4fME5.d.ts} +47 -7
- package/dist/vue.d.ts +6 -6
- package/dist/vue.js +95 -16
- package/package.json +7 -2
- package/skills/vitest-auto-spy/SKILL.md +7 -0
package/AGENTS.md
CHANGED
|
@@ -15,7 +15,8 @@ Working on the library's own source instead? Read `CONTRIBUTING.md` in the repos
|
|
|
15
15
|
Setting this up for a team? `npx vitest-auto-spy init` writes a pointer to this file into the
|
|
16
16
|
instruction files this repository's agents actually read — `AGENTS.md` (Codex, Cursor, Copilot and
|
|
17
17
|
most of the field), `CLAUDE.md` (Claude Code) and `GEMINI.md` (Gemini CLI), plus the glob-scoped
|
|
18
|
-
rule file of any tool whose own directory already exists. `--check` is the CI form
|
|
18
|
+
rule file of any tool whose own directory already exists. `--check` is the CI form; `--only
|
|
19
|
+
CLAUDE.md,.claude` limits it to the files a repository keeps untracked. Full table:
|
|
19
20
|
<https://asdalexey.github.io/vitest-auto-spy/agents>.
|
|
20
21
|
|
|
21
22
|
| Resource | Where |
|
|
@@ -131,7 +132,7 @@ Do you have a real class at runtime?
|
|
|
131
132
|
|
|
132
133
|
Object already built, other code already holds it?
|
|
133
134
|
→ createSpyFromInstance(obj, config?) → Spy<T>, patched IN PLACE
|
|
134
|
-
One standalone function? → createFunctionSpy<Fn>('name')
|
|
135
|
+
One standalone function? → createFunctionSpy<Fn>('name') → FunctionSpy<Fn>
|
|
135
136
|
Code under test does `new Foo()`? → a real class? createSpyClass(Foo)
|
|
136
137
|
→ only a shape? mockConstructor<T>(() => instance)
|
|
137
138
|
→ on a global? stubConstructor(globalThis, 'Image', factory)
|
|
@@ -154,6 +155,20 @@ with an optional `overrides` parameter relies on that. A fixture that means "no
|
|
|
154
155
|
`undefined` itself: `getters.profile.mockReturnValue(undefined)`, never
|
|
155
156
|
`mockReturnValue(createMock<Profile>(undefined))`.
|
|
156
157
|
|
|
158
|
+
A fixture **outside** its type on purpose — the `null` or array a backend sends where the type says
|
|
159
|
+
object, a payload that has to reach a runtime guard — is `outOfType<T>(value)`: nothing is checked,
|
|
160
|
+
the call site says so, and `prefer-create-mock` / `no-ts-expect-error-on-double` accept it where they
|
|
161
|
+
would report `as unknown as T` or a `@ts-expect-error`. It takes its type from the slot, so
|
|
162
|
+
`mockValueProp(job, 'status', outOfType('UNKNOWN'))` stays on the key-checked overload. Everything else
|
|
163
|
+
stays `createMock<T>()`.
|
|
164
|
+
On a strict double, `spy.m.mockReturnValue(outOfType(undefined))` reaches a defensive
|
|
165
|
+
`?? of(null)` branch of a method typed to return an `Observable`, and counts as configured.
|
|
166
|
+
|
|
167
|
+
Under `strict`, `returns: { open: undefined }` marks a method configured whose answer nobody reads
|
|
168
|
+
(an ngrx `rxMethod` ref, a snack-bar ref) — accepted for any return type. It is also how a
|
|
169
|
+
type-only double under `strict` takes its void calls, one key each:
|
|
170
|
+
`createAutoMock<CanvasRenderingContext2D>(undefined, { strict: true, returns: { save: undefined, restore: undefined } })`.
|
|
171
|
+
|
|
157
172
|
**`createSpyFromInstance(instance, config?)` is the one case where nothing is constructed.** Every
|
|
158
173
|
factory above builds the double, which is no help once the object exists and other code already
|
|
159
174
|
points at it — a service a factory built, a third-party client, a half-real `TestBed.inject(X)`. It
|
|
@@ -528,14 +543,17 @@ Four conventions that carry most of the value:
|
|
|
528
543
|
Every spied method is a real runner mock, so `mockReturnValue`, `mockImplementation`,
|
|
529
544
|
`toHaveBeenCalledWith` and the rest all work as usual. On top of that:
|
|
530
545
|
|
|
531
|
-
| Return type | Helpers added
|
|
532
|
-
| --------------- |
|
|
533
|
-
| anything | `calledWith(...args)` → `.mockReturnValue(v)` / `.returnValue(v)` / `.failWith(err)`, `mustBeCalledWith(...args)` → same, `failWith(err)`
|
|
534
|
-
| `Promise<T>` | `resolveWith(v)`, `rejectWith(v)`, `resolveWithPerCall([{ value }, …])`
|
|
535
|
-
| `Observable<T>` | `nextWith(v)`, `nextOneTimeWith(v)`, `nextWithValues(configs)`, `nextWithPerCall(configs)`, `throwWith(v)`, `complete()`, `returnSubject()`
|
|
546
|
+
| Return type | Helpers added |
|
|
547
|
+
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
548
|
+
| anything | `calledWith(...args)` → `.mockReturnValue(v)` / `.returnValue(v)` / `.failWith(err)`, `mustBeCalledWith(...args)` → same, `failWith(err)`. On a `Promise` or `Observable` method the handle carries that row's helpers instead — `calledWith(id).nextWith(v)`, `.resolveWith(v)`; `.mockReturnValue` there is a `TS2339` |
|
|
549
|
+
| `Promise<T>` | `resolveWith(v)`, `rejectWith(v)`, `resolveWithPerCall([{ value }, …])` |
|
|
550
|
+
| `Observable<T>` | `nextWith(v)`, `nextOneTimeWith(v)`, `nextWithValues(configs)`, `nextWithPerCall(configs)`, `throwWith(v)`, `complete()`, `returnSubject()` |
|
|
536
551
|
|
|
537
552
|
`Observable` **properties** (not just methods) get the same helpers — list them in
|
|
538
|
-
`observablePropsToSpyOn`.
|
|
553
|
+
`observablePropsToSpyOn`. The prop spy is a plain `Observable`: a `BehaviorSubject` member loses
|
|
554
|
+
`.value` / `getValue()`, and code that reads them gets `undefined`. Seed a real one instead —
|
|
555
|
+
`overrides: { isActive$: new BehaviorSubject(false) }` — and drive it with `.next()`; the same holds
|
|
556
|
+
for a stream the test pushes into itself.
|
|
539
557
|
|
|
540
558
|
**What counts as an `Observable` is structural, and no declaration names rxjs (4.0.0).** A member
|
|
541
559
|
earns the observable bundle when its type satisfies the exported `ObservableLike<T>` — `subscribe`
|
|
@@ -1154,13 +1172,14 @@ across realms.
|
|
|
1154
1172
|
emits, the callback never runs and nothing is asserted. Invert it — **the assertion is the `await`**:
|
|
1155
1173
|
|
|
1156
1174
|
```ts
|
|
1157
|
-
import { expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission } from 'vitest-auto-spy';
|
|
1175
|
+
import { expectAllEmissions, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission } from 'vitest-auto-spy';
|
|
1158
1176
|
|
|
1159
1177
|
await expect(expectEmission(component.visible$)).resolves.toBe(true); // the first VALUE, not a list
|
|
1160
1178
|
await expect(expectEmission(tasks$)).resolves.toEqual({ id: 1 }); // the task itself, not `[task]`
|
|
1161
1179
|
await expect(expectEmissions(source$, 3)).resolves.toEqual([1, 2, 3]); // the list is this one
|
|
1162
1180
|
await expectNoEmission(source$, { timeout: 50 });
|
|
1163
1181
|
await expectCompletion(service.purgeCache()); // "it finished" — the value is not the point
|
|
1182
|
+
await expect(expectAllEmissions(source$)).resolves.toEqual([1, 2]); // every value, and none after
|
|
1164
1183
|
```
|
|
1165
1184
|
|
|
1166
1185
|
Options: `{ timeout, label }`. `timeout` defaults to `1000` ms (`0` for `expectNoEmission`, whose
|
|
@@ -1350,8 +1369,9 @@ real getters plus a `createFunctionSpy` per method — this is usually why.
|
|
|
1350
1369
|
|
|
1351
1370
|
**The second overload is a normal tool, not a last resort.** Each helper has a checked overload
|
|
1352
1371
|
(`K extends keyof T`) and a `(object, property: PropertyKey, value: unknown)` one behind it, and
|
|
1353
|
-
the JSDoc calls the latter
|
|
1354
|
-
of the real calls, all of them legitimate
|
|
1372
|
+
the JSDoc calls the latter the one for members the public type does not describe. In practice it
|
|
1373
|
+
carries about half of the real calls, all of them legitimate. It cannot reach a JS `#private` field —
|
|
1374
|
+
no property key can — and `'x' as keyof T` over one patches a new, unrelated key that nothing reads:
|
|
1355
1375
|
|
|
1356
1376
|
```ts
|
|
1357
1377
|
mockValueProp(router, 'routerState', { snapshot: { url: '/home' } }); // a partial fixture of a fat type
|
|
@@ -1447,6 +1467,12 @@ every one after it reads the real member. `propsOutsideHooks` grades that — `'
|
|
|
1447
1467
|
`'throw'`, `'off'` — and `reportPropsOutsideHooks(reaction)` sets the same dial for a suite that
|
|
1448
1468
|
wires its own hooks. Move the call into `beforeEach`.
|
|
1449
1469
|
|
|
1470
|
+
**Adopting `setupAutoSpy()` in a suite that restored its patches once per file** — one
|
|
1471
|
+
`restoreMockedProps()` in an `afterAll` — changes a patch from "lives for the file" to "lives for one
|
|
1472
|
+
test". On 176 files of one such suite, 3 broke, each a `mock*Prop` in a `describe` body or
|
|
1473
|
+
`beforeAll`, and the `'warn'` report is easy to miss in a full run. Run the first time with
|
|
1474
|
+
`setupAutoSpy({ propsOutsideHooks: 'throw' })`, fix what it names, then drop the option.
|
|
1475
|
+
|
|
1450
1476
|
**The one that only bites at scale:** with `isolate: false`, a `setTimeout` or
|
|
1451
1477
|
`requestAnimationFrame` a component schedules and never clears keeps running after its file is done,
|
|
1452
1478
|
and fires while the **next** file is mid-test. It is reported against that innocent file, as
|
|
@@ -1566,6 +1592,72 @@ One entry is never pruned, and it is not one of yours: the Vitest adapter regist
|
|
|
1566
1592
|
`vi.clearAllMocks()` into a silent no-op for every double in the run, so it carries a mark the
|
|
1567
1593
|
pruner skips. Why the library needs a mock of its own for that is the next section.
|
|
1568
1594
|
|
|
1595
|
+
**The one that answers for every file that follows:** `vi.spyOn(localStorage, 'setItem')` puts the
|
|
1596
|
+
mock on the storage as an own property, and happy-dom's `Storage` hands each instance out wrapped
|
|
1597
|
+
in a Proxy whose `deleteProperty` trap only knows stored items. `mockRestore()` — and
|
|
1598
|
+
`vi.restoreAllMocks()` walking the same path — deletes through that trap, comes back green, and
|
|
1599
|
+
does nothing: the spy keeps recording, and a later spec's own `vi.spyOn` is handed the same mock
|
|
1600
|
+
with the previous file's calls already in it, so "not to have been called" fails every other run.
|
|
1601
|
+
Probing by running `vi.restoreAllMocks()` rules nothing out — it is the failing path itself. Writing
|
|
1602
|
+
`Storage.prototype`'s method back over the spy does go through the proxy; that asymmetry, define
|
|
1603
|
+
passes and delete does not, is the repair, and it is on by default:
|
|
1604
|
+
|
|
1605
|
+
```ts
|
|
1606
|
+
setupAutoSpy(); // restoreStorageSpies: true — the sweep runs at the file boundary only
|
|
1607
|
+
```
|
|
1608
|
+
|
|
1609
|
+
`restoreStorageSpies()` is the one-shot (it returns the storages it repaired); a suite that
|
|
1610
|
+
deliberately keeps a spy on a storage method for a whole worker turns the option off. jsdom breaks
|
|
1611
|
+
the other half of the same contract — its Storage proxy turns a `defineProperty` of a method into a
|
|
1612
|
+
stored item — which is why the gate is `vi.isMockFunction` rather than "differs from the
|
|
1613
|
+
prototype": a junk item is left alone, a mock is repaired.
|
|
1614
|
+
|
|
1615
|
+
**The one that outlives its component:** a listener on `window` or `document` is not the
|
|
1616
|
+
component's to take off. Overlays, portals and services register on the shared targets, and under
|
|
1617
|
+
`isolate: false` whatever a file leaves registered fires during the next one — the same wrong-file
|
|
1618
|
+
blame as a stray timer, with none of the visibility: nothing errors, the callback simply runs
|
|
1619
|
+
against mocks and a DOM it was never written for.
|
|
1620
|
+
|
|
1621
|
+
```ts
|
|
1622
|
+
setupAutoSpy({ strayListeners: true }); // wrap addEventListener, sweep what the file added
|
|
1623
|
+
```
|
|
1624
|
+
|
|
1625
|
+
The split is the one `pruneMockRegistry` uses: a `beforeAll` marks the listeners already on
|
|
1626
|
+
`window`/`document` — registered while the module graph was evaluated, a framework's one-time
|
|
1627
|
+
initialisation — and the `afterAll` takes off everything added since. The pieces are exported too:
|
|
1628
|
+
`trackStrayListeners()` (idempotent, returns the undo), `baselineStrayListeners()`,
|
|
1629
|
+
`removeStrayListeners()` (returns how many), `countStrayListeners()` (throws before
|
|
1630
|
+
`trackStrayListeners()` has run, like the timer counter) and `describeStrayListeners()` — each
|
|
1631
|
+
stray's target, type, spec file and up to five frames, the list `onStrayListeners({ removed, listeners })`
|
|
1632
|
+
receives when a file should fail instead: `onStrayListeners: ({ removed }) => expect(removed).toBe(0)`.
|
|
1633
|
+
Under jsdom a listener registered with `{ once: true }` that already fired stays counted until
|
|
1634
|
+
something removes it: the wrapper cannot observe the firing without breaking identity-based
|
|
1635
|
+
`removeEventListener` from the code under test, and removing an already-fired listener is a no-op
|
|
1636
|
+
anyway. happy-dom detaches it through the public `removeEventListener`, so there it leaves the count.
|
|
1637
|
+
|
|
1638
|
+
**The one no registry tracks:** `vi.stubGlobal` is undone by `unstubGlobals` and
|
|
1639
|
+
`vi.spyOn(globalThis, …)` by `restoreMocks`, but a plain assignment —
|
|
1640
|
+
`global.ResizeObserver = stub` — is written straight into the shared worker and read by every later
|
|
1641
|
+
file. `guardGlobals` names the _unrepairable_ case, a non-configurable redefine; this is the net
|
|
1642
|
+
for the repairable one:
|
|
1643
|
+
|
|
1644
|
+
```ts
|
|
1645
|
+
setupAutoSpy({ restoreGlobals: true }); // one snapshot per worker, restored at every file boundary
|
|
1646
|
+
```
|
|
1647
|
+
|
|
1648
|
+
`captureGlobalBaseline()` takes the snapshot — the first call is the worker's truth, a later one
|
|
1649
|
+
does nothing, so a re-capture can never launder a replacement into the baseline. `restoreGlobals()`
|
|
1650
|
+
is the sweep, and returns the keys it changed. Two traps it steps around, both load-bearing. A DOM
|
|
1651
|
+
environment installs window properties on `globalThis` as accessor pairs forwarding to an override
|
|
1652
|
+
map, so `global.ResizeObserver = stub` leaves the descriptor untouched and the restore has to write
|
|
1653
|
+
the captured value back through the same setter, not just re-define the descriptor. And the
|
|
1654
|
+
library's own wrappers — `setTimeout` under `strayTimers`, `addEventListener` under
|
|
1655
|
+
`strayListeners` — are marked as theirs, so the restore steps around them instead of uninstalling
|
|
1656
|
+
the tracking at the first boundary. Added keys are never deleted (a framework that installs a
|
|
1657
|
+
global at import time must not lose it), and the identity globals — `location`, `document`,
|
|
1658
|
+
`window` and their kin — are never written back at all. A leftover fake clock comes off first,
|
|
1659
|
+
because restoring timer descriptors under an installed fake breaks both.
|
|
1660
|
+
|
|
1569
1661
|
### What a method spy is, and the one thing that differs from `vi.fn()`
|
|
1570
1662
|
|
|
1571
1663
|
A method spy is **not** a `vi.fn()`. It is this library's own mock function: one shared prototype
|
|
@@ -1768,6 +1860,28 @@ allows `node:fs` in `dist/setup.js` for exactly that. Silent outside the builder
|
|
|
1768
1860
|
window, and on a Node before `getBuiltinModule`. `setupAutoSpy({ angularBuildHint: false })` turns
|
|
1769
1861
|
it off.
|
|
1770
1862
|
|
|
1863
|
+
**Strict for one run:** `VITEST_AUTO_SPY_STRICT=1 npx vitest run <slice>` makes `setupAutoSpy()` arm
|
|
1864
|
+
`strict` whatever the setup file passed (`0` turns it off, `survey` counts instead of throwing); a second `setupAutoSpy()` in an extra setup
|
|
1865
|
+
file also works now, without grading the first call's network stubs as patched outside a hook.
|
|
1866
|
+
|
|
1867
|
+
**Moving a suite to `strict`:** most of the churn is the same few methods configured file by file.
|
|
1868
|
+
Run the suite once in survey mode, which refuses nothing and ends each file with the list strict mode
|
|
1869
|
+
would have refused, most frequent first (`setupAutoSpy({ strict: 'survey' })` is the same in code):
|
|
1870
|
+
|
|
1871
|
+
```bash
|
|
1872
|
+
VITEST_AUTO_SPY_STRICT=survey npx vitest run
|
|
1873
|
+
```
|
|
1874
|
+
|
|
1875
|
+
The top of that list — `SvgIconService.getIcon`, `NotificationsService.open` — goes into the setup
|
|
1876
|
+
file once, `registerAutoSpyDefaults(NotificationsService, { returns: { open: undefined } })`, and
|
|
1877
|
+
every double of the class answers it; the tail is what each spec configures itself.
|
|
1878
|
+
|
|
1879
|
+
**One setup file for the builder and for plain Vitest:** the builder initialises `TestBed` before any
|
|
1880
|
+
setup file, so a second `initTestEnvironment()` throws "Cannot set base providers because it has
|
|
1881
|
+
already been called". Guard it with `isAngularUnitTestBuilder()` from `/setup` — the same marker the
|
|
1882
|
+
hint reads — and name the file in the target's `setupFiles`, which is the only way the builder runs
|
|
1883
|
+
it (`doctor` reports `builder-setup-unreached` otherwise).
|
|
1884
|
+
|
|
1771
1885
|
One more field of the same family differs quietly and changes only the report:
|
|
1772
1886
|
`slowTestThreshold` is `5` in Jest (**seconds**) and `300` in Vitest (**milliseconds**), so a
|
|
1773
1887
|
migrated suite starts marking most of its files slow. That is a unit change, not a regression.
|
|
@@ -2006,7 +2120,8 @@ frame outside `node_modules`:
|
|
|
2006
2120
|
before the guard armed are taken off.
|
|
2007
2121
|
- `strayConsole: { allow: ['…', /…/] }` is the last resort, for environment noise no spec can reach.
|
|
2008
2122
|
Output written to `process.stdout` / `process.stderr` directly, and jsdom's own virtual console, is
|
|
2009
|
-
not seen.
|
|
2123
|
+
not seen. An Angular suite that resets modules under `isolate: false` allows `/NG0912/`, Angular's import-time
|
|
2124
|
+
component-ID collision warning, which the guard otherwise blames on whichever file is importing.
|
|
2010
2125
|
- The wrapper forwards every call unchanged, so Vitest's attribution and `onConsoleLog` still work.
|
|
2011
2126
|
`guardStrayConsole(reaction)` registers the same guard on its own.
|
|
2012
2127
|
|
|
@@ -2117,7 +2232,9 @@ to a bare `TestBed.tick()`; `src/zone-tests/stable.zone-test.ts` is the guard.
|
|
|
2117
2232
|
`flushEventLoopUntil(isDone, { turns, label })` is the same thing with a condition and a budget —
|
|
2118
2233
|
for a chunk becoming reachable, an SDK reporting itself ready, a queue draining. Use it instead of a
|
|
2119
2234
|
hand-tuned turn count: the count depends on the dependency, not on the spec, and a condition that
|
|
2120
|
-
never holds fails naming the `label` rather than hanging until the runner's timeout.
|
|
2235
|
+
never holds fails naming the `label` rather than hanging until the runner's timeout. For real I/O —
|
|
2236
|
+
a socket round-trip, a child process — pass `{ timeoutMs, label }` instead of `turns`: it polls the
|
|
2237
|
+
real clock every 10 ms, unaffected by fake timers, and replaces a hand-rolled `waitFor(predicate, ms)`.
|
|
2121
2238
|
|
|
2122
2239
|
**Not for an Angular `resource()` / `httpResource()`.** Those need a change-detection _tick_, and
|
|
2123
2240
|
this helper only takes event-loop turns — a resource awaited through it finishes the whole budget
|
|
@@ -2180,7 +2297,10 @@ for that last reason: a class with a static named `calls` would otherwise shadow
|
|
|
2180
2297
|
log. **The statics are not typed** — the return type is still `ConstructorSpy<T>`, so reading one
|
|
2181
2298
|
needs a cast at the spec; type them and this note goes.
|
|
2182
2299
|
|
|
2183
|
-
For the three observers, prefer the purpose-built stubs (§13). For `
|
|
2300
|
+
For the three observers, prefer the purpose-built stubs (§13). For a `Worker` the code builds with
|
|
2301
|
+
`new Worker(new URL(…, import.meta.url))`, prefer `stubWorker({ respond })` from `/dom-stubs`: an
|
|
2302
|
+
`EventTarget` whose listeners all receive the reply, answered on a microtask after `postMessage`
|
|
2303
|
+
returns, with `last.messages`, `last.emit(data)` and `last.fail(error)` on the handle. For `AbortController` — which breaks
|
|
2184
2304
|
in a jsdom run for a reason involving none of the three parties in the stack trace — use
|
|
2185
2305
|
`stubAbortController()`.
|
|
2186
2306
|
|
|
@@ -2241,6 +2361,9 @@ it('forgets the token on logout', () => {
|
|
|
2241
2361
|
});
|
|
2242
2362
|
```
|
|
2243
2363
|
|
|
2364
|
+
Writes in the middle of a test go through `.storage`, the installed `Storage` itself:
|
|
2365
|
+
`local.storage.setItem('token', 'xyz')`. The stub is not a `Storage`; `local.setItem` is a `TS2339`.
|
|
2366
|
+
|
|
2244
2367
|
It goes through `mockValueProp`, so `restoreMockedProps()` (and `setupAutoSpy()` between tests) puts
|
|
2245
2368
|
the previous storage back; install it in `beforeEach`. It also lands on `document.defaultView` when
|
|
2246
2369
|
that is a separate object. It is not `restoreWebStorage()`: that one repairs a broken environment
|
|
@@ -2453,9 +2576,21 @@ selected.set(true); // every computed reading it updates
|
|
|
2453
2576
|
Use it whenever the value has to change during the test. `mockReadonlyProp` stays right when the
|
|
2454
2577
|
value is fixed for the whole test and you never need the handle.
|
|
2455
2578
|
|
|
2579
|
+
A `protected` or TS-`private` signal is reached too: the key is checked against the public type
|
|
2580
|
+
first, and a key that type does not describe takes the value's own type instead —
|
|
2581
|
+
`mockSignalProp(component, 'saving', true)`. `mockResourceProp` works the same way. A public key never
|
|
2582
|
+
falls through to that form, so its value is still checked.
|
|
2583
|
+
|
|
2456
2584
|
A `linkedSignal()` is a writable signal like any other, so `mockSignalProp` covers it and there is
|
|
2457
2585
|
no separate helper to go looking for.
|
|
2458
2586
|
|
|
2587
|
+
**An ngrx `signalStore()`'s state members go through `mockSignalProp` too**, on the store double —
|
|
2588
|
+
`mockSignalProp(store, 'currentAngle', null)`, then `.set(angle)` mid-test. A state slice is a
|
|
2589
|
+
signal the template reads during the first render; a method spy there answers `undefined`, and
|
|
2590
|
+
`mockImplementation(() => value())` is re-read by nothing downstream. A nullable object slice is
|
|
2591
|
+
typed `DeepSignal<A> | Signal<null>`: `overrides` accepts one `signal<A | null>()` for it, but only
|
|
2592
|
+
`mockSignalProp` hands back the handle.
|
|
2593
|
+
|
|
2459
2594
|
**It patches as little as it can, and the order rule follows from that.** A member that is already
|
|
2460
2595
|
writable — `signal()`, `model()`, `linkedSignal()` — is **written through**, not replaced: Angular
|
|
2461
2596
|
links a consumer to the signal it read, not to the property it read it through, so replacing one
|
|
@@ -2558,6 +2693,19 @@ TestBed.overrideComponent(ProfileComponent, { remove: { providers: [DeleteAccoun
|
|
|
2558
2693
|
the TestBed compiler, which `overrideProvider` alone does not do. Reach for the `remove` form when
|
|
2559
2694
|
the module already provides the spy and the component's declaration is simply in the way.
|
|
2560
2695
|
|
|
2696
|
+
Under `renderShallow` the override goes in `beforeCreate`, which runs after the testing module is
|
|
2697
|
+
configured and before the component is created:
|
|
2698
|
+
|
|
2699
|
+
```ts
|
|
2700
|
+
let menu: Spy<NavigationBuilderService>;
|
|
2701
|
+
|
|
2702
|
+
renderShallow(CatalogPageComponent, {
|
|
2703
|
+
beforeCreate: () => {
|
|
2704
|
+
menu = overrideComponentProvider(CatalogPageComponent, NavigationBuilderService);
|
|
2705
|
+
},
|
|
2706
|
+
});
|
|
2707
|
+
```
|
|
2708
|
+
|
|
2561
2709
|
### `injectSpy` cannot reach a component-level provider
|
|
2562
2710
|
|
|
2563
2711
|
`injectSpy(X)` reads the **global** `TestBed` injector. A provider declared on the component
|
|
@@ -3196,13 +3344,24 @@ component's cleanup throws:
|
|
|
3196
3344
|
const load = Object.assign(vi.fn(), { destroy: vi.fn() });
|
|
3197
3345
|
```
|
|
3198
3346
|
|
|
3347
|
+
Under `strict`, a call whose ref nobody reads is configured with `returns: { load: undefined }`. When
|
|
3348
|
+
the code does read the ref (it calls `destroy`), seed one from the method's own type — ngrx does not
|
|
3349
|
+
export `RxMethodRef`: `returns: { load: createAutoMock<ReturnType<Store['load']>>() }`.
|
|
3350
|
+
|
|
3351
|
+
A `signalStore()` class keeps its methods and `rxMethod`s on the instance, not the prototype, so
|
|
3352
|
+
discovery finds none of them. `provideAutoSpy(Store, { fillMissing: true, returns: { … } })` answers
|
|
3353
|
+
every such member with a spy — `returns` and `strict` apply to them as to any method — instead of a
|
|
3354
|
+
long `instanceMethodsToSpyOn` list.
|
|
3355
|
+
|
|
3199
3356
|
```ts
|
|
3200
3357
|
// shallow rendering — configureTestingModule + NO_ERRORS_SCHEMA + overrideComponent, in one call
|
|
3201
3358
|
const { fixture, component } = renderShallow(TaskListComponent, {
|
|
3202
3359
|
providers: [provideAutoSpy(TaskService)],
|
|
3203
3360
|
inputs: { projectId: 42 }, // signal inputs take the VALUE, not the signal
|
|
3204
3361
|
});
|
|
3205
|
-
// other options: imports, keepTemplate, keepChildren, template, beforeCreate, detectChanges
|
|
3362
|
+
// other options: imports, keepTemplate, keepModules, keepChildren, keepHostDirectives, template, beforeCreate, detectChanges
|
|
3363
|
+
// COVERAGE: the override recompiles the component under JIT for the rest of the file (keepTemplate too),
|
|
3364
|
+
// so its AOT template/host branches leave coverage — keep one real TestBed render first where they matter
|
|
3206
3365
|
|
|
3207
3366
|
// change an input after the first render — one setInput per key, then the wait, in one await
|
|
3208
3367
|
await setInputs(fixture, { projectId: 7 }); // a name the component does not declare is refused here
|
|
@@ -3675,6 +3834,8 @@ export default [
|
|
|
3675
3834
|
`autoSpy.configs.typeErrors` is a second flat config holding the subset whose findings are compile
|
|
3676
3835
|
errors — `prefer-as-spy` (`TS2352`) and `no-mocked-for-spy` (`TS2322`). Spread its `rules` after a
|
|
3677
3836
|
blanket downgrade so those keep their severity; do not copy the two names into a consumer's config.
|
|
3837
|
+
`autoSpy.configs.strict` is `recommended` with every rule at `error`, instead of mapping
|
|
3838
|
+
`Object.keys(autoSpy.rules)` by hand.
|
|
3678
3839
|
|
|
3679
3840
|
| Rule | Level | Fix | Flags |
|
|
3680
3841
|
| --------------------------------- | ------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -3720,7 +3881,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
|
|
|
3720
3881
|
| `no-mock-cast` | `error` | suggest | a cast to Vitest's `Mock` / `MockInstance` over a **member access** — `TestBed.inject(S).m as Mock` → `injectSpy(S).m`, which the suggestion writes whenever the token is in view (directly, or through a `const` the file settled with a `TestBed.inject`). `Mock` with no parameters is `Mock<any>`: `mockReturnValue` takes anything and `toHaveBeenCalledWith` stops comparing arguments. `(spy.m.mockReturnValue as Mock)(…)` gets its own message — the cast is on the member that installs the answer, so neither the value nor the method's return type is checked. The name must resolve to an import from `vitest` / `@rstest/core` / `bun:test` / `jest`, or to nothing at all, so a domain `Mock` is left alone |
|
|
3721
3882
|
| `no-redundant-mock-reset` | `error` | `--fix` / suggest | a mock reset the runner already performs between tests — `vi.clearAllMocks()` / `resetAllMocks()` / `restoreAllMocks()`, `x.mockClear()` / `mockReset()` / `mockRestore()` — standing where nothing the file wrote ran since the runner's own: the first statement of a `beforeEach` no other `beforeEach` precedes, or a clear as the last statement of an `afterEach` → delete it, and the hook with it where that is all it held. A restore or reset in `afterEach` / `afterAll` is never reported: after the last test it is what takes a spy off before the `afterAll` hooks. **Silent** until the flags are known: options `{ clearMocks, restoreMocks, mockReset }` (or `{ configFile }` naming the runner config) first, then a `vitest.config.*` / `vite.config.*` searched upwards from the file and read as text. Flag matches call, never family — `restoreMocks` walks only the spies `vi.spyOn` installed. `--fix` only in a file whose only before-hook it is; a suggestion otherwise; never inside a test body |
|
|
3722
3883
|
| `no-unasserted-argument` | `warn` | — | a bare `expect(spy).toHaveBeenCalled()` on evidence from the file: the same subject asserted with `toHaveBeenCalledWith(…)` in another test, or a title containing `with` over a body whose every assertion is a bare call → `toHaveBeenCalledWith(…)` / `mustBeCalledWith(…)`. `not.toHaveBeenCalled()` and the counting matchers are never reported |
|
|
3723
|
-
| `no-compile-components` | `error` | suggest | `compileComponents()` under a builder that inlines `templateUrl` / `styleUrls` — a promise already settled → delete it, and the `async` of a hook that awaits nothing else. **Silent until** `['error', { builder: 'inline-resources' }]`: under a JIT setup that loads resources at run time the call is load-bearing. **Keep the call for a component whose template holds a `@defer` block** — that ships async class metadata, which `TestBed` resolves in this very call whatever the builder did, and dropping it fails the test with `has unresolved metadata`; the rule cannot see another file's template, so
|
|
3884
|
+
| `no-compile-components` | `error` | suggest | `compileComponents()` under a builder that inlines `templateUrl` / `styleUrls` — a promise already settled → delete it, and the `async` of a hook that awaits nothing else. **Silent until** `['error', { builder: 'inline-resources' }]`: under a JIT setup that loads resources at run time the call is load-bearing. **Keep the call for a component whose template holds a `@defer` block** — that ships async class metadata, which `TestBed` resolves in this very call whatever the builder did, and dropping it fails the test with `has unresolved metadata`; the rule cannot see another file's template, so list those classes in `{ ignoreComponents: ['CardComponent'] }` (a spec naming one is left alone) or keep a `// eslint-disable-next-line vitest-auto-spy/no-compile-components -- @defer: async class metadata` |
|
|
3724
3885
|
| `no-sync-testbed-await` | `error` | suggest | `await` on a TestBed call that answers the TestBed or a fixture — `configureTestingModule`, `override*`, `resetTestingModule`, `createComponent`, `getLastFixture`, through `TestBed`, `getTestBed()`, a chain of those, or a name holding one → drop the `await`, and the `async` of a hook that then awaits nothing else; the suggestion does both. Reads **no types**, so it reports without `parserOptions.project`, where `@typescript-eslint/await-thenable` cannot. `inject` and `runInInjectionContext` are never reported: each answers whatever the token or the callback holds, which can be a promise |
|
|
3725
3886
|
| `jasmine-namespace-without-entry` | `error` | — | `.and` / `.calls` / `.withArgs` on a library spy in a file that installs the compat layer nowhere — option: `{ setupModules: […] }` |
|
|
3726
3887
|
| `no-jasmine-globals` | `error` | — | `jasmine.*`, bare `spyOn(` / `spyOnProperty(` / `spyOnAllFunctions(` / `fail(` / `pending(`, `.withContext(` |
|
|
@@ -3916,7 +4077,8 @@ packages, which a subpath export can never be.
|
|
|
3916
4077
|
|
|
3917
4078
|
| Message contains | Cause | Fix |
|
|
3918
4079
|
| --------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
3919
|
-
| `No mock adapter registered` | no runtime entry was imported, or the wrong one
|
|
4080
|
+
| `No mock adapter registered` | no runtime entry was imported, or the wrong one — or the setup file that imports it never ran: an `@angular/build:unit-test` / `@nx/angular:unit-test` target runs only the `setupFiles` it names (`doctor` reports `builder-setup-unreached`) | import `vitest-auto-spy` (Vitest) / `…/bun` / `…/node` once before creating spies; under the unit-test builder, name the setup file in the target's `setupFiles` |
|
|
4081
|
+
| `No "X" export is defined on the "<m>" mock`, in a spec whose `vi.mock('<m>')` has no factory | another spec file mocked `<m>` with a factory, and under `isolate: false` Vitest hands that factory to a later automock or `{ spy: true }` mock of the same module (`doctor` reports `module-mock-leak`) | mock it the same way in both files, or `vi.resetModules()` in this file before importing it |
|
|
3920
4082
|
| `Observable spies require rxjs` | the rxjs layer was never loaded | `import 'vitest-auto-spy/rxjs';` once, in the setup file |
|
|
3921
4083
|
| `… is not subscribable (…)` | the argument is not an observable — most often the value it emits, or a promise | pass the observable itself; `await` a promise directly (§8) |
|
|
3922
4084
|
| ``… the `advance` callback threw: …`` | the `{ advance }` callback failed; the original is on `cause` and the subscription is torn down | fix the callback — a `vi.runAllTimers()` with no fake timers installed is the usual one (§8) |
|
|
@@ -3976,6 +4138,9 @@ packages, which a subpath export can never be.
|
|
|
3976
4138
|
| an assertion error printed to stderr, every test green and the run exiting 0 | zone.js swallowed a rejection nobody handled | `setupAutoSpy({ strayRejections: true })` fails the test it surfaced in (§10) |
|
|
3977
4139
|
| an `expect()` inside a `.then()` that never seems to run | nothing awaits the chain, so the test ended first | `await` the promise and assert the settled value — `no-floating-assertion` (§16) |
|
|
3978
4140
|
| `trackStrayRejections() found no zone.js on the host` | `strayRejections` turned on where zone.js is not loaded | `import 'zone.js';` in the setup file, or drop the option |
|
|
4141
|
+
| a spy on a storage method still answering after `vi.restoreAllMocks()` probed green | happy-dom's Storage proxy deletes only stored items, so the restore behind `mockRestore()` never lands; a later spec's `vi.spyOn` gets the same mock with the old calls in it | on by default — `setupAutoSpy()` repairs it at the file boundary; `restoreStorageSpies()` on its own, or `restoreStorageSpies: false` for a worker-lifetime spy (§10) |
|
|
4142
|
+
| a listener callback running against mocks and a DOM it was never written for, in a file that never added it | a `window`/`document` listener a previous file left registered, under `isolate: false` | `setupAutoSpy({ strayListeners: true })` — module-graph registrations stay, per-file ones go at the boundary (§10) |
|
|
4143
|
+
| a global a previous file patched (`ResizeObserver` answering a stub) with no file named | a raw assignment no registry tracks — not `vi.stubGlobal`, not `vi.spyOn` | `setupAutoSpy({ restoreGlobals: true })` puts the worker's baseline back at every file boundary; `guardGlobals` for the non-configurable case (§10) |
|
|
3979
4144
|
| `… .destroy is not a function` | an ngrx `rxMethod` replaced with a bare mock | `Object.assign(vi.fn(), { destroy: vi.fn() })` |
|
|
3980
4145
|
| `NullInjectorError` for a service you did provide | it is a component-level provider, not a module one | `asSpy(fixture.debugElement.injector.get(X))`, not `injectSpy(X)` |
|
|
3981
4146
|
| `runEffect(): … not an EffectRef returned by effect()` | passed the callback, a signal, or an unassigned field | pass what `effect()` returned; a field may need its lifecycle hook to run first |
|
|
@@ -4028,6 +4193,13 @@ packages, which a subpath export can never be.
|
|
|
4028
4193
|
| `throwWith` reaching the success branch first, with the previous test's value | the spy's `ReplaySubject(1)` outlived the test that filled it | upgrade; and `resetAutoSpy(spy)` in `beforeEach` when the TestBed is built in `beforeAll` |
|
|
4029
4194
|
| `TS2540` on a `Spy<T>` / `createAutoMock` member, which the runtime writes fine | `Spy<T>` is homomorphic and keeps the `readonly` of the source member — deliberately: on a member replaced by a **spied accessor** the assignment reaches the setter spy and the getter still answers `undefined`, so the type error is the loud version of a silent no-op | `mockValueProp(spy, 'X', value)` — it defines rather than assigns, so it works in both cases; `Mutable<Spy<T>>` is the opt-in for plain data members, and `Reflect.set` is **not** a way round it |
|
|
4030
4195
|
| `TS2345` from `mockReturnValue` / `mockImplementation` / `mockResolvedValue` on a double that used to compile | the mock surface is `MockInstance<Method>`, so the stub is now checked against the method's own return type | stub with what the method returns; if the double is meant to answer something else, the method's type or the spec's expectation is wrong |
|
|
4196
|
+
| `TS2345: Argument of type 'Spy<X>' is not assignable to parameter of type 'X'` on `mockReturnValue(double)` | 5.1.0: the stub is checked against the method, and a double of the return type is a `Spy<X>`, not an `X` | `mockReturnValue(asInstance(double))` (§6) |
|
|
4197
|
+
| `with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties` on a stub | the stub's type spells an optional member `?: T` where the method's spells `?: T \| undefined` (or the reverse), or a fixture writes `key: undefined`; a `Subject` is invariant, so its type argument has to match exactly | `method.returnSubject()` for a subject typed from the method; omit the key instead of writing `undefined` |
|
|
4198
|
+
| `Types of parameters 'x' and 'y' are incompatible` on `mockImplementation(…)` | 5.1.0: the implementation is checked against the method's parameters, and an annotation in it disagrees | drop the parameter annotations and let the method type them; narrow inside the body |
|
|
4199
|
+
| `TS4111` / `TS2375` / `TS2322` inside a `mockImplementation` body that used to compile | before 5.1.0 the implementation's parameters were `any`, so nothing in its body was checked | fix the body — the error is ordinary once its parameters are typed |
|
|
4200
|
+
| `TS2349: This expression is not callable` on a value read from `spy.mock.calls` | 5.1.0 types `mock.calls` from the method; a listener taken from `addEventListener`'s calls is `EventListenerOrEventListenerObject` | narrow before calling: `if (typeof listener === 'function') listener(event)` |
|
|
4201
|
+
| `is not assignable to parameter of type 'RxMethod<…>'` (or another member type) on `mockReadonlyProp(x, 'm', vi.fn(impl))` | `vi.fn` infers its type from the member the helper replaces, so `impl` must return what the member returns | return the member's type — `Object.assign(vi.fn(), { destroy: vi.fn() })` for an `rxMethod` |
|
|
4202
|
+
| `Argument of type '() => A \| null' is not assignable to parameter of type '(() => A) \| (() => null)'` | the member is a union of call signatures — `@ngrx/signals`' `DeepSignal<A> \| Signal<null>` for a nullable slice — and 5.1.0–5.24.0 typed its stub as the union of both halves | upgrade past 5.24.0, which types the stub as one signature returning the union |
|
|
4031
4203
|
| `delete mock.optionalMethod` leaving the member present and truthy | the Proxy had no `deleteProperty` trap before 3.5.0 | upgrade; before that, `mock.optionalMethod = undefined` |
|
|
4032
4204
|
| half a double's methods reaching the real implementation after `Object.assign` | `ownKeys` on a type-driven Proxy lists only the keys already read | `createSpyFromClass(X)` — a real object, with enumerable method keys |
|
|
4033
4205
|
| `let s: MockInstance<() => unknown>` not matching anything | `MockInstance<F>` is invariant in `F`; Jest's `SpyInstance` was not | `MockInstance<T['method']>`, or better `injectSpy(X).method` |
|
|
@@ -4043,7 +4215,7 @@ packages, which a subpath export can never be.
|
|
|
4043
4215
|
| `createNestUnit(X).spies.get(Y): Y is in \`expose\`, so the unit got a real instance` | an exposed class is real, not a spy | read the spies of its own collaborators, or drop it from `expose` |
|
|
4044
4216
|
| `expect(component.ngOnInit).toHaveBeenCalled()` fails after `fixture.detectChanges()`, or a hook's `.mockImplementation` never runs | the spy was put on the **instance**; a view calls the hook it read off the class prototype when the component was created | `vi.spyOn(Cls.prototype, 'ngOnInit')` before `TestBed.createComponent`, or assert what the hook does — `no-instance-lifecycle-spy` (§16) |
|
|
4045
4217
|
| `TS2345: Argument of type 'InjectionToken<X>' is not assignable to parameter of type 'ClassType<unknown>'`, on `registerAutoSpyDefaults` | the core entry's `registerAutoSpyDefaults` takes a class only — it may not name Angular's token type | import `registerAutoSpyDefaults` (and `clearAutoSpyDefaults`) from `vitest-auto-spy/angular`, which also takes an `InjectionToken` (§5) |
|
|
4046
|
-
| `selfReturning names 'x', which is not a spied method` | the name is not a method of the double — a typo, or a restricting `onlyMethodsToSpyOn` left it out; on a type-driven double, `then` and `constructor` are never spies
|
|
4218
|
+
| `selfReturning names 'x', which is not a spied method` | the name is not a method of the double — a typo, or a restricting `onlyMethodsToSpyOn` left it out; on a type-driven double, `then` and `constructor` are never spies; a method the constructor assigns (an ngrx `rxMethod`) is not on the prototype | fix the name or the whitelist, or list a constructor-assigned method in `instanceMethodsToSpyOn`; the value is silently never returned otherwise (§5) |
|
|
4047
4219
|
| a spec green over a `useValue` fixture whose key the code never reads — `queryParams$` on an `ActivatedRoute` | `useValue` is `any`, so a renamed or misspelled key of an object literal compiles | the member's real name — `no-unknown-use-value-key` (§16), with type information; or `provideAutoSpy(X, { overrides })`, which the compiler checks |
|
|
4048
4220
|
| a spec runs down the "browser" (or any flag-on) branch whatever its `useValue` says | `{ provide: IS_BROWSER, useValue: {} }` for an `InjectionToken<boolean>` — `useValue` is `any`, and an object is truthy | a value of the token's type (`useValue: false`) — `no-mistyped-use-value` (§16), with type information |
|
|
4049
4221
|
| `no request matched …` (with the requests that were made listed) | the URL, verb or predicate does not describe anything the code under test sent, or nothing subscribed | read the list in the message — it is the requests that were actually made; then fix the URL, add `{ method }`, or subscribe (§13) |
|
|
@@ -4115,6 +4287,7 @@ diagnostic and is free to change: print it, never assert on it.
|
|
|
4115
4287
|
| `{ ...modelInstance, flag: true }` (drops every getter) | `withOverrides(modelInstance, { flag: true })` |
|
|
4116
4288
|
| `if ('params' in link) … else throw` in every spec | `narrow.byKey(link, 'params')` |
|
|
4117
4289
|
| `const x = a?.b; assert.exists(x); … x …` on every optional read | `narrow.defined(a?.b)` — it returns the value, so the narrowing sits in the expression |
|
|
4290
|
+
| `expect(body).toBeInstanceOf(FormData); const form = body as FormData` | `narrow.instanceOf(body, FormData)` — the check and the type in one expression |
|
|
4118
4291
|
| five `asInstance(...)` in one call | `...asInstances(a, b, c, d, e)` |
|
|
4119
4292
|
| `vi.stubGlobal('Image', vi.fn(() => ({ src: '' })))` | `stubConstructor(globalThis, 'Image', () => ({ src: '' }))` |
|
|
4120
4293
|
| `Object.defineProperty(document, 'cookie', { value })` | `mockValueProp(document, 'cookie', value)` |
|
|
@@ -4213,7 +4386,8 @@ suite that runs green but does not type-check is not done.
|
|
|
4213
4386
|
`doctor` is read-only and finds what a green run cannot: a `tsconfig` `include` pattern that
|
|
4214
4387
|
matches no file (so it type-checks nothing while `tsc --noEmit` still reports success), a
|
|
4215
4388
|
production module importing a `*.spec.ts`, a spec importing another spec, a `@jest-environment`
|
|
4216
|
-
pragma the runner never reads,
|
|
4389
|
+
pragma the runner never reads, configuration left behind for a runner that is gone, and a setup
|
|
4390
|
+
file an Angular or Nx unit-test target never runs because the target does not name it. Three of
|
|
4217
4391
|
its checks are about coverage, where the run is green and the report is simply not the one the
|
|
4218
4392
|
config describes — `coverage.all` on a Vitest that stopped reading the key, a source-only
|
|
4219
4393
|
`coverage.include` in a runner config over a bundle, and a scope so large that `picomatch`
|
|
@@ -4261,11 +4435,18 @@ command, an unknown flag, an unreadable `--only` / `--from` value, a path matchi
|
|
|
4261
4435
|
|
|
4262
4436
|
**Reading the output from a script: `--format json`**, on `doctor` and on `perf`. One JSON document
|
|
4263
4437
|
on stdout — `schema`, `exitCode`, `tally` (`errors`, `warnings`, `notes`), every finding with
|
|
4264
|
-
`check`, `severity`, `file`, `message`, `fix`; `perf` adds `run`, `budgets` and `gate.verdicts` (one row
|
|
4438
|
+
`check`, `severity`, `file`, `message`, `fix`; `perf` adds `run` (with `slowestFiles`, the `--top` slowest files per phase), `budgets` and `gate.verdicts` (one row
|
|
4265
4439
|
per candidate, `outcome` one of `confirmed`, `not reproduced`, `unconfirmed`, `single reading`,
|
|
4266
4440
|
`over budget`). Parse that rather than the text: the text is wrapped to the terminal (80 columns in a
|
|
4267
4441
|
pipe), groups one cause found in many files into one block, and ends in a tally line that starts
|
|
4268
|
-
with `N errors, N warnings, N notes`.
|
|
4442
|
+
with `N errors, N warnings, N notes`. `--format markdown` renders the same document as tables — for a
|
|
4443
|
+
merge request note or a job summary, not for parsing.
|
|
4444
|
+
|
|
4445
|
+
**`doctor --ignore <check,…>`** leaves the named checks out of the report, the tally, the exit code
|
|
4446
|
+
and the `--code-quality` file. Use it only for a finding the repository has answered in a way
|
|
4447
|
+
`doctor` cannot see — `no-agent-instructions` in CI where the instruction files are kept out of git,
|
|
4448
|
+
`angular-build-splitting-off` under a patched builder. `--min-severity` hides findings from the text
|
|
4449
|
+
only; `--ignore` removes them.
|
|
4269
4450
|
|
|
4270
4451
|
### If you were asked why a suite is slow
|
|
4271
4452
|
|
|
@@ -4321,7 +4502,7 @@ npx vitest-auto-spy perf --command 'npm test -- {paths:--include=}' --gate
|
|
|
4321
4502
|
`--command` puts `VITEST_AUTO_SPY_PERF_OUT` and `VITEST_AUTO_SPY_PERF_REPORTER` in that command's
|
|
4322
4503
|
environment; the configuration it reaches attaches the reporter itself (`reporters: perf ===
|
|
4323
4504
|
undefined ? ['default'] : ['default', perf]`), and the reporter writes nothing at all when the first
|
|
4324
|
-
variable is unset, so it can be declared permanently
|
|
4505
|
+
variable is unset, so it can be declared permanently — by name, `vitest-auto-spy/perf-reporter`. `{paths}` / `{paths:<prefix>}` is where the
|
|
4325
4506
|
files of a confirmation pass go.
|
|
4326
4507
|
|
|
4327
4508
|
**`--gate` is the only part of this command that fails anything**, and three rules keep it honest.
|