vitest-auto-spy 5.5.0 → 5.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +143 -30
- package/README.md +171 -43
- package/dist/angular-http.d.ts +4 -3
- package/dist/angular-http.js +46 -16
- package/dist/angular.d.ts +5 -5
- package/dist/angular.js +216 -77
- package/dist/bun-angular.d.ts +6 -6
- package/dist/bun-angular.js +25 -11
- package/dist/bun.d.ts +6 -33
- package/dist/bun.js +6 -5
- package/dist/{chunk-7FUZBDFD.js → chunk-2PBOTZ4S.js} +13 -2
- package/dist/{chunk-UF47X335.js → chunk-374SMZYV.js} +1 -1
- package/dist/{chunk-A7INJLDC.js → chunk-4EJRJ3QP.js} +1 -1
- package/dist/{chunk-XCIRU75U.js → chunk-6ER757ZM.js} +70 -11
- package/dist/{chunk-M2ZI62ZY.js → chunk-G73VE5ED.js} +3 -3
- package/dist/{chunk-EPTJRSSO.js → chunk-IW6ZHFTL.js} +1 -1
- package/dist/{chunk-SNRHI2QI.js → chunk-O5IKAXWI.js} +27 -13
- package/dist/{chunk-E2JLMCOR.js → chunk-RYJDNCAY.js} +2 -2
- package/dist/chunk-RZRKEUE6.js +15 -0
- package/dist/{chunk-FPMWEHKF.js → chunk-U53SDEP3.js} +7 -4
- package/dist/cli.js +12 -1
- package/dist/console.d.ts +9 -10
- package/dist/console.js +44 -19
- package/dist/dom-stubs.d.ts +1 -1
- package/dist/dom-stubs.js +17 -1
- package/dist/eslint-plugin.cjs +318 -12
- package/dist/{expect-emission-DYtyyKM5.d.ts → expect-emission-H8lAQz2f.d.ts} +6 -3
- package/dist/index.d.ts +5 -5
- package/dist/index.js +129 -21
- package/dist/jasmine-compat.d.ts +1 -1
- package/dist/jasmine.d.ts +2 -2
- package/dist/jasmine.js +9 -8
- package/dist/nestjs.d.ts +2 -2
- package/dist/nestjs.js +7 -6
- package/dist/node.cjs +110 -20
- package/dist/node.d.ts +5 -5
- package/dist/node.js +6 -5
- package/dist/package-identity-B3aSBJRI.d.ts +88 -0
- package/dist/{prop-mock-DmM-rPcG.d.ts → prop-mock-DSWMTtRE.d.ts} +1 -1
- package/dist/react.d.ts +5 -5
- package/dist/react.js +7 -6
- package/dist/rstest.d.ts +5 -5
- package/dist/rstest.js +7 -6
- package/dist/rxjs.d.ts +2 -2
- package/dist/setup.d.ts +215 -109
- package/dist/setup.js +387 -46
- package/dist/svelte.d.ts +5 -5
- package/dist/svelte.js +7 -6
- package/dist/{track-injections-D4TuS8yk.d.ts → track-injections-Bh7AZ2-0.d.ts} +1 -1
- package/dist/{types-DTi4dIeN.d.ts → types-DYGoBQGg.d.ts} +13 -7
- package/dist/vue.d.ts +6 -6
- package/dist/vue.js +8 -7
- package/dist/{zoneless-DM1AWQV2.d.ts → zoneless-CvgjkxKw.d.ts} +9 -5
- package/package.json +1 -1
- package/skills/vitest-auto-spy/SKILL.md +93 -78
- package/dist/package-identity-B1pqa-Sh.d.ts +0 -33
package/AGENTS.md
CHANGED
|
@@ -56,7 +56,7 @@ Add-ons, orthogonal to the runner:
|
|
|
56
56
|
| ----------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
57
57
|
| Observable spies | `import 'vitest-auto-spy/rxjs'` | `nextWith` & friends. **Side-effect import, once**, and in a file the `tsconfig` includes (§4) |
|
|
58
58
|
| observer-spy shim | `vitest-auto-spy/observer-spy` | `subscribeSpyTo` — the `@hirez_io/observer-spy` surface (§20). Its own entry so `/rxjs` does not carry it |
|
|
59
|
-
| Console spies | `vitest-auto-spy/console` | silent typed spies over the global `console` |
|
|
59
|
+
| Console spies | `vitest-auto-spy/console` | silent typed spies over the global `console` — `installConsoleSpies()` per test, `restoreConsole()` after |
|
|
60
60
|
| DOM stubs | `vitest-auto-spy/dom-stubs` | `stubIntersectionObserver` / `stubResizeObserver` / `stubMutationObserver` / `stubObserver`, `stubMediaElement`, `stubAbortController`, `intersectionEntry` / `resizeEntry` / `mutationRecord`. **Moved off the root in 4.0.0** |
|
|
61
61
|
| Run diagnostics | `vitest-auto-spy/diagnostics` | `compareTestRuns`, `summarizeTestRun`, `formatTestRunComparison`, `diffByField`. **Moved off the root in 4.0.0** |
|
|
62
62
|
| Angular HTTP | `vitest-auto-spy/angular-http` | `provideHttpTesting`, `expectRequest` — `httpResource()` / `HttpClient` (§13). Optional `@angular/common` peer, this entry only |
|
|
@@ -121,7 +121,8 @@ and is only ever asserted on — `createAutoMock`. Handed to the function under
|
|
|
121
121
|
(`detectVpnClient(url, logger)`, `applyPreferredTracks(target, …)`, `setLocalConfigEnabled(storage, …)`),
|
|
122
122
|
it has to satisfy `T` at the call site _and_ expose the spy helpers at the assertion, and
|
|
123
123
|
`autoMocked<T>()` is that intersection — otherwise every call site needs an `asInstance()` and the
|
|
124
|
-
noise scales with the number of them.
|
|
124
|
+
noise scales with the number of them. Both take the same second argument (`returns`, `name`, `strict`,
|
|
125
|
+
`observablePropsToSpyOn`).
|
|
125
126
|
|
|
126
127
|
`createMock<T>()` is the one to reach for on data shapes — it returns a plain `T`, so it satisfies a
|
|
127
128
|
`no-type-assertion` lint rule without an `eslint-disable` on every fixture.
|
|
@@ -720,20 +721,36 @@ users.currentTenant(); // throws here, not four frames later inside the componen
|
|
|
720
721
|
Called as: Cart.checkout(1,'now')
|
|
721
722
|
```
|
|
722
723
|
|
|
723
|
-
Also on `createAutoMock`, `provideAutoSpy`, and suite-wide as `setupAutoSpy({ strict: true })
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
724
|
+
Also on `createAutoMock`, `provideAutoSpy`, and suite-wide as `setupAutoSpy({ strict: true })` — which
|
|
725
|
+
reaches a double whichever bundle of the package built it: the default lives on `globalThis`. Before
|
|
726
|
+
this release it lived in module scope, and a setup file's `strict: true` reached no double a spec built.
|
|
727
|
+
Precedence, first one set wins: the double's `onUnstubbedCall` → the double's explicit **`strict: false`**
|
|
728
|
+
(the only way to exempt one double from a suite-wide default, a global handler included) → the global
|
|
729
|
+
`onUnstubbedCall` → the double's `strict: true` → the global `strict`.
|
|
727
730
|
|
|
728
731
|
- **It is not argument-level.** A `calledWith(1, 'now')` chain says the method is stubbed, so
|
|
729
732
|
`checkout(9, 'later')` still answers `undefined`. Use `mustBeCalledWith` for that.
|
|
730
|
-
- **`
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
733
|
+
- **`returns:` is a default in the spy's own container**: a `calledWith` / `resolveWith` / `failWith`
|
|
734
|
+
configured later wins for its arguments or supersedes it, every other call still gets it, and
|
|
735
|
+
`undefined` counts as configured. `returns: { save: undefined }` is how a `void` call is expected.
|
|
736
|
+
- **`mockReturnValue` / `mockImplementation` replace the dispatch**, so they never reach the guard —
|
|
737
|
+
and a `calledWith` configured after them is never consulted. After the last `mockReturnValueOnce`
|
|
738
|
+
the queue empties back onto the library dispatch and the next call **is** reported as unstubbed —
|
|
739
|
+
seed `mockReturnValue(...)` too when a `Once` sequence is meant to run out.
|
|
740
|
+
- **A throw something swallowed still fails the test.** Under `setupAutoSpy({ strict: true })` every
|
|
741
|
+
strict throw is recorded, and one that a `try`/`catch` in the code under test or an RxJS error with
|
|
742
|
+
no handler (its rethrow waits on a fake clock) kept from the test fails it after the test
|
|
743
|
+
(`swallowedStrictCalls`, `'throw'` by default with `strict: true`). Provoking one on purpose:
|
|
744
|
+
`expect(() => cart.total()).toThrow(…)` then `expect(takeStrictViolations()).toHaveLength(1)`.
|
|
734
745
|
- **It does not reach** accessor spies, observable-property spies, `mockDeep` nodes, `console-spy`,
|
|
735
746
|
`mockResourceProp`'s `reload` or a standalone `createFunctionSpy`. A strict double still answers
|
|
736
747
|
`undefined` for an unconfigured getter or `items$`. `fillMissing` members **are** covered.
|
|
748
|
+
- **Angular lifecycle hooks are exempt.** `ngOnDestroy`, `ngOnInit`, `ngOnChanges`, `ngDoCheck` and the
|
|
749
|
+
`ngAfter…` hooks answer `undefined` on a strict double: Angular calls `ngOnDestroy` itself on every
|
|
750
|
+
provided value at teardown (a `createAutoMock` / `provideAutoSpyForToken` double always has one), and
|
|
751
|
+
a throw there broke the teardown for every test after it. The calls are still recorded.
|
|
752
|
+
- A class instance or DOM node in the `Called as:` line prints as `[ClassName]`, data up to 200
|
|
753
|
+
characters per argument; `provideAutoSpyForToken` names the token (`createAutoMock` takes `name`).
|
|
737
754
|
- Use `onUnstubbedCall` to survey a suite before turning the throw on — it is handed
|
|
738
755
|
`{ className, method, args }` and whatever it returns is what the call answers, so a handler that
|
|
739
756
|
pushes to an array records the gap without failing anything.
|
|
@@ -1137,23 +1154,28 @@ setupAutoSpy({ strayTimers: true }); // wrap the schedulers, sweep the survivors
|
|
|
1137
1154
|
```
|
|
1138
1155
|
|
|
1139
1156
|
The pieces are exported too — `trackStrayTimers()` (idempotent, returns the undo),
|
|
1140
|
-
`cancelStrayTimers()` (returns how many it cancelled)
|
|
1141
|
-
`
|
|
1142
|
-
|
|
1157
|
+
`cancelStrayTimers()` (returns how many it cancelled), `countStrayTimers()` and
|
|
1158
|
+
`describeStrayTimers()` (each pending timer's kind, the spec file that scheduled it and up to five
|
|
1159
|
+
frames), all from `vitest-auto-spy/setup`. Use `expect(countStrayTimers()).toBe(0)` in an `afterEach`
|
|
1160
|
+
to make a leak fail rather than be tidied away, or take the per-file report from the sweep itself —
|
|
1161
|
+
`timers` carries the same origins, so the failure diff names the file and the scheduling call:
|
|
1143
1162
|
|
|
1144
1163
|
```ts
|
|
1145
|
-
setupAutoSpy({ strayTimers: true, onStrayTimers: ({
|
|
1164
|
+
setupAutoSpy({ strayTimers: true, onStrayTimers: ({ timers }) => expect(timers).toEqual([]) });
|
|
1146
1165
|
```
|
|
1147
1166
|
|
|
1167
|
+
A stray whose `file` is not the file that failed was scheduled after the previous file's sweep —
|
|
1168
|
+
the previous file is the one to fix.
|
|
1169
|
+
|
|
1148
1170
|
**With Vitest 4.1's `--detect-async-leaks`, run one or the other — not both silently.** The two
|
|
1149
1171
|
arrive at the same timer from opposite ends and the quiet one wins: the sweep cancels in `afterAll`,
|
|
1150
1172
|
Vitest collects its leaks afterwards, and a cancelled timeout is no longer referenced, so the run
|
|
1151
1173
|
reports **no leaks** for a file that leaks. Cancelling is still the right default — a callback firing
|
|
1152
1174
|
during a later file is the more expensive failure — so when both are on and no `onStrayTimers` is
|
|
1153
|
-
given, the sweep prints one line to stderr saying how many it took away
|
|
1154
|
-
scheduled
|
|
1155
|
-
at the `setTimeout` in the spec, because the library's own scheduler wrappers go
|
|
1156
|
-
`vi.defineHelper` and are dropped from the stack.
|
|
1175
|
+
given, the sweep prints one line to stderr saying how many it took away and where the first three
|
|
1176
|
+
were scheduled; `onStrayTimers` gets all of them. Vitest's own report, with `strayTimers` off, points
|
|
1177
|
+
its code frame at the `setTimeout` in the spec, because the library's own scheduler wrappers go
|
|
1178
|
+
through `vi.defineHelper` and are dropped from the stack.
|
|
1157
1179
|
|
|
1158
1180
|
**The one that keeps a suite green while it is wrong:** zone.js replaces the global `Promise`, and a
|
|
1159
1181
|
rejection nobody handled is drained into `console.error` and no further — it never reaches
|
|
@@ -1546,6 +1568,65 @@ a `useValue` provider or a test double — patch the prototype of the class the
|
|
|
1546
1568
|
`guardPrototypePollution(reaction)` from `/setup` registers the same check on its own, for a suite
|
|
1547
1569
|
that does not call `setupAutoSpy()`.
|
|
1548
1570
|
|
|
1571
|
+
### Failing on console output nothing absorbed
|
|
1572
|
+
|
|
1573
|
+
```ts
|
|
1574
|
+
setupAutoSpy({ strayConsole: 'throw' }); // default 'off'; 'warn' prints the same report without failing
|
|
1575
|
+
```
|
|
1576
|
+
|
|
1577
|
+
Any call to a console method that writes (`log`, `info`, `warn`, `error`, `debug`, `trace`, `table`,
|
|
1578
|
+
`dir`, `dirxml`, `timeLog`, `timeEnd`, `count`; `group` with a label; a falsy `assert`) made during a
|
|
1579
|
+
test that nothing absorbed fails **that test**, quoting the method, the first three lines and the first
|
|
1580
|
+
frame outside `node_modules`:
|
|
1581
|
+
|
|
1582
|
+
```
|
|
1583
|
+
[vitest-auto-spy] "CartService > reports a failed load" wrote to the console 1 time(s) and nothing absorbed it:
|
|
1584
|
+
- console.error: Error: load failed
|
|
1585
|
+
at CartService.load (src/app/cart.service.ts:41:15)
|
|
1586
|
+
```
|
|
1587
|
+
|
|
1588
|
+
- **Absorbed** means the call never reached the console: a `/console` spy installed for the test or
|
|
1589
|
+
the file, `vi.spyOn(console, m).mockImplementation(…)`, any replacement that does not call
|
|
1590
|
+
through. A bare `vi.spyOn(console, m)` calls through and **counts**.
|
|
1591
|
+
- Output **outside any test** — collection/import, `beforeAll` / `afterAll`, a callback after its
|
|
1592
|
+
test ended — fails the **file** in `afterAll` (`… outside any test …`).
|
|
1593
|
+
- Every console method a test replaced is put back after it; one a file replaced, after the file.
|
|
1594
|
+
- Under the guard, **importing `vitest-auto-spy/console` installs nothing** — call
|
|
1595
|
+
`installConsoleSpies()` in a `beforeEach` (or at the top of the file). Spies an import installed
|
|
1596
|
+
before the guard armed are taken off.
|
|
1597
|
+
- The library's own warnings are console output: under the guard they fail the test that caused them.
|
|
1598
|
+
- `strayConsole: { allow: ['…', /…/] }` is the last resort, for environment noise no spec can reach.
|
|
1599
|
+
Output written to `process.stdout` / `process.stderr` directly, and jsdom's own virtual console, is
|
|
1600
|
+
not seen.
|
|
1601
|
+
- The wrapper forwards every call unchanged, so Vitest's attribution and `onConsoleLog` still work.
|
|
1602
|
+
`guardStrayConsole(reaction)` registers the same guard on its own.
|
|
1603
|
+
|
|
1604
|
+
### One grade for everything — `preset: 'strict'`
|
|
1605
|
+
|
|
1606
|
+
```ts
|
|
1607
|
+
setupAutoSpy({ preset: 'strict' });
|
|
1608
|
+
```
|
|
1609
|
+
|
|
1610
|
+
Sets `duplicateCopies`, `propsOutsideHooks`, `guardGlobals`, `prototypePollution`, `strayConsole` and
|
|
1611
|
+
`misconfiguration` to `'throw'`, turns `strayTimers` on, and `strayRejections` on when zone.js is
|
|
1612
|
+
loaded. An option passed alongside still wins. **Not** included: `strict` (strict doubles change what
|
|
1613
|
+
an unconfigured call returns — a semantic switch, not a grade; the name was taken, hence `preset`),
|
|
1614
|
+
`blockNetwork` (changes the code under test), `restoreMocks` (drops `beforeAll` spies), failing on
|
|
1615
|
+
stray-timer counts (the sweep fails the file from `afterAll`, and a callback scheduled after the
|
|
1616
|
+
previous file's sweep is charged to the next — opt in with
|
|
1617
|
+
`onStrayTimers: ({ timers }) => expect(timers).toEqual([])`, whose diff names each one's file), and
|
|
1618
|
+
`enableAngularDiagnostics()`, which lives in `/angular` — call it in the same setup file as the Angular half of strict.
|
|
1619
|
+
|
|
1620
|
+
`misconfiguration: 'throw'` on its own makes the library's misuse reports — an `onlyMethodsToSpyOn`
|
|
1621
|
+
typo, `gettersToSpyOn` naming a method, a `returns` key no spy answers to, `injectSpy` handed a real
|
|
1622
|
+
instance, a `jasmine.DEFAULT_TIMEOUT_INTERVAL` write, `providedMethodNames` — throw at the call site,
|
|
1623
|
+
every occurrence. The grade is process-wide and released after the file. The printed grade of the
|
|
1624
|
+
`injectSpy` warning is de-duplicated per token **per spec file**, no longer per worker.
|
|
1625
|
+
|
|
1626
|
+
`withoutStrayTimerTracking(work)` runs setup work whose timers `strayTimers` neither counts nor
|
|
1627
|
+
cancels — jsdom answers every Web Storage write with a real `setTimeout(…, 0)`, and the library's own
|
|
1628
|
+
storage probe runs under it.
|
|
1629
|
+
|
|
1549
1630
|
### Hook order differs from Jest
|
|
1550
1631
|
|
|
1551
1632
|
Vitest runs `afterEach` hooks as a stack (innermost / last-registered first); Jest ran them in
|
|
@@ -2481,15 +2562,29 @@ mount(Greeting, { global: { provide } });
|
|
|
2481
2562
|
import { createSpyFromClass } from 'vitest-auto-spy/react';
|
|
2482
2563
|
```
|
|
2483
2564
|
|
|
2484
|
-
Console spies —
|
|
2485
|
-
`timeEnd` / `trace` / `warn` with silent typed spies, named `console<Method>Spy
|
|
2565
|
+
Console spies — `installConsoleSpies()` replaces `console.debug` / `error` / `info` / `log` / `time` /
|
|
2566
|
+
`timeEnd` / `trace` / `warn` with silent typed spies, named `console<Method>Spy`. Install them per test
|
|
2567
|
+
and take them off after it:
|
|
2486
2568
|
|
|
2487
2569
|
```ts
|
|
2488
|
-
import {
|
|
2570
|
+
import { type ConsoleSpies, installConsoleSpies, restoreConsole } from 'vitest-auto-spy/console';
|
|
2489
2571
|
|
|
2490
|
-
|
|
2572
|
+
let consoleSpies: ConsoleSpies;
|
|
2573
|
+
|
|
2574
|
+
beforeEach(() => {
|
|
2575
|
+
consoleSpies = installConsoleSpies();
|
|
2576
|
+
});
|
|
2577
|
+
afterEach(() => restoreConsole());
|
|
2578
|
+
|
|
2579
|
+
expect(consoleSpies.consoleInfoSpy).toHaveBeenCalledWith('done'); // the output is silenced, not printed
|
|
2491
2580
|
```
|
|
2492
2581
|
|
|
2582
|
+
The exported `consoleInfoSpy` & co. are the same objects; `restoreConsole()` keeps them and clears
|
|
2583
|
+
their calls. **Do not rely on the import to install them**: it does so once per worker, so under
|
|
2584
|
+
`isolate: false` the spies go on in whichever file imported them first and silence every later file
|
|
2585
|
+
(`no-import-time-console-spies` reports it). Under `setupAutoSpy({ strayConsole })` the import installs
|
|
2586
|
+
nothing at all.
|
|
2587
|
+
|
|
2493
2588
|
Import your runtime entry (`…/bun`, `…/node`) **before** `…/console`, or it registers the Vitest
|
|
2494
2589
|
adapter. Prefer not to touch the real global? `createAutoMock<Console>()` gives a detached one.
|
|
2495
2590
|
|
|
@@ -2546,17 +2641,22 @@ blanket downgrade so those keep their severity; do not copy the two names into a
|
|
|
2546
2641
|
| `no-unregistered-inject-spy` | `error` | — | `injectSpy(X)` for a token this file never registered → the real instance, whose spy helpers exist only for the compiler |
|
|
2547
2642
|
| `prefer-render-shallow` | `warn` | suggest | `TestBed.createComponent` in a file that never reads the template → `renderShallow(X)`; 0.24× the per-test cycle at 100 children |
|
|
2548
2643
|
| `prefer-observer-stub` | `error` | — | a hand-rolled observer global → `stubIntersectionObserver()` / `stubResizeObserver()` / `stubMutationObserver()`; the manual save-and-restore goes too, `restoreMockedProps()` runs the undo |
|
|
2644
|
+
| `no-passthrough-console-spy` | `error` | suggest | `vi.spyOn(console, m)` nothing gives an implementation — it calls through and prints → `installConsoleSpies()` + `consoleXSpy`, or `.mockImplementation(() => undefined)` |
|
|
2645
|
+
| `no-console-in-spec` | `error` | — | a spec calling `console.x(…)` itself, or `console.x = …`, which nothing restores → absorb the code's output through `vitest-auto-spy/console` |
|
|
2646
|
+
| `no-import-time-console-spies` | `error` | — | an import of `vitest-auto-spy/console` in a file that never calls `installConsoleSpies()` — the import installs once per worker and silences every later file → `installConsoleSpies()` in `beforeEach`, `restoreConsole()` in `afterEach` |
|
|
2647
|
+
| `no-mistyped-use-value` | `error` | — | `{ provide: TOKEN, useValue }` whose value is not assignable to the primitive `T` of `InjectionToken<T>` (string, number, boolean, bigint, enum, their literals, `null`, `undefined`) — `useValue` is `any`, so `{}` for a `boolean` token compiles and is truthy. **Type-aware**: silent without `parserOptions.project`; object-typed tokens are out of scope |
|
|
2648
|
+
| `no-instance-lifecycle-spy` | `warn` | — | `vi.spyOn(instance, 'ngOnInit')` (and `ngOnDestroy`, `ngDoCheck`, `ngAfterContent*`, `ngAfterView*`) — a view calls the hook read off the prototype, so the instance spy is never called and its stub never runs → `vi.spyOn(Cls.prototype, …)` before `createComponent`, or assert the effect. `ngOnChanges` is exempt: Angular calls it through the instance |
|
|
2549
2649
|
| `jasmine-namespace-without-entry` | `error` | — | `.and` / `.calls` / `.withArgs` on a library spy in a file that installs the compat layer nowhere — option: `{ setupModules: […] }` |
|
|
2550
2650
|
| `no-jasmine-globals` | `error` | — | `jasmine.*`, bare `spyOn(` / `spyOnProperty(` / `spyOnAllFunctions(` / `fail(` / `pending(`, `.withContext(` |
|
|
2551
2651
|
| `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()` — a no-op here, so the spec silently asserts on post-mutation state |
|
|
2552
2652
|
| `prefer-native-spy-api` | `error` | `--fix` / suggest | `.and` / `.calls` where the spy's own API says the same thing — turn it on for the last mile off the jasmine shim |
|
|
2553
2653
|
|
|
2554
|
-
|
|
2555
|
-
`no-stub-class-double` and `no-
|
|
2556
|
-
|
|
2654
|
+
Thirty rules, **every one an `error` since 4.0.0 except `prefer-render-shallow`,
|
|
2655
|
+
`no-stub-class-double`, `no-structural-double` and `no-instance-lifecycle-spy`**; three fix on their own, nine offer suggestions. Twenty-eight are syntactic; `no-private-member-access` and `no-mistyped-use-value`
|
|
2656
|
+
read types, and both report nothing at all without `parserOptions.project` / `projectService`
|
|
2557
2657
|
rather than guessing. The config used to be a graded mix of `error` / `warn` / `off`, which decided for the
|
|
2558
2658
|
consumer how much each finding mattered — a `warn` nothing reads is `off` with extra output. The
|
|
2559
|
-
|
|
2659
|
+
four `warn`s left are not judgements about how much a finding matters. `prefer-render-shallow` is
|
|
2560
2660
|
about the _kind_ of finding: every other rule names something wrong or dead, while this one names a
|
|
2561
2661
|
file that could render more cheaply, and `renderShallow` is a migration a suite either takes or does
|
|
2562
2662
|
not. At `error` the plugin would gate that migration — 491 findings across 398 of one consumer's
|
|
@@ -2569,7 +2669,9 @@ and moved a long way inside the release: measured on the same 1759 files, the tw
|
|
|
2569
2669
|
reports in 8 files and 115 in 74, and are 10 in 7 and 5 in 4 once `prefer-provide-auto-spy` learnt to
|
|
2570
2670
|
follow a name into a `useValue` — 112 of those doubles are handed to Angular DI one name away, where
|
|
2571
2671
|
a `provide:` settles it. That rule reports **154 times across 87 files** on the same suite, all at
|
|
2572
|
-
`error`.
|
|
2672
|
+
`error`. `no-instance-lifecycle-spy` is on the evidence too: Angular never calls an instance spy on a
|
|
2673
|
+
hook it read off the prototype, but a spec that calls `component.ngOnInit()` itself does, and so does
|
|
2674
|
+
the injector for a service's `ngOnDestroy`. Set any of the four `warn`s to `'error'` once the batch is done. Three of
|
|
2573
2675
|
them can report on a _correct_ project, and only one has an option:
|
|
2574
2676
|
`jasmine-namespace-without-entry` takes `['error', { setupModules: ['./test-setup'] }]`, naming the
|
|
2575
2677
|
file where `enableJasmineCompat()` is called; `prefer-native-spy-api` goes `'off'` for as long as a
|
|
@@ -2709,6 +2811,11 @@ packages, which a subpath export can never be.
|
|
|
2709
2811
|
| `extendWithAutoSpies needs Vitest 4.1 or newer` | the `test` handed in has only the object-form `extend` (Vitest ≤ 4.0); the builder form the helper is written against arrived in 4.1 | upgrade Vitest, or keep `provideAutoSpy` + `injectSpy` in a `beforeEach` until then |
|
|
2710
2812
|
| `advanceTimers() requires fake timers` | no fake timers installed | `setupFakeTimers()` or `vi.useFakeTimers()` first |
|
|
2711
2813
|
| `Nothing configured X.method, and strict mode is on` | a strict double was asked for a method no line configured | configure it (`mockReturnValue` / `resolveWith` / `nextWith` / `calledWith`), or `{ strict: false }` on that double (§5) |
|
|
2814
|
+
| `[vitest-auto-spy] "…" wrote to the console N time(s) and nothing absorbed it` | `setupAutoSpy({ strayConsole })` — the test printed, and nothing that does not call through stood on `console`: no `/console` spy installed for it, no `mockImplementation`. A bare `vi.spyOn(console, m)` calls through, and a `console.warn` from this library counts too| absorb and assert: `installConsoleSpies()` in a `beforeEach`, then `expect(consoleErrorSpy)…`; or fix the code that printed. Importing a spy installs nothing under the guard. `strayConsole: { allow: [...] }` only for environment noise no spec can reach |
|
|
2815
|
+
| `… wrote to the console N time(s) outside any test` | output while the file was imported, in a `beforeAll` / `afterAll`, from a callback after its test ended, or from a test whose `afterEach` never ran — the frame names the line | move the work into a test and absorb it there; a callback firing late is a leak — clear it where it is scheduled |
|
|
2816
|
+
| `setupAutoSpy({ strict: true })` has no effect — no double throws, a suite with it on stays green | before this release the default lived in module scope, and `/setup` and the factory bundles each carried a copy; a 1759-file consumer ran 12 717 tests green with it on | upgrade — the default, `onUnstubbedCall` and `setSpyEngine()` now live on `globalThis` |
|
|
2817
|
+
| a seeded getter reads `undefined`: `provideAutoSpy(X, { overrides: { remoteConfig } })` next to `gettersToSpyOn` or a `registerAutoSpyDefaults` row| before this release the seed was assigned into the spied accessor's setter, and the getter kept answering `undefined` | upgrade — `overrides` now seeds the getter spy; a later `accessorSpies.getters.x.mockReturnValue(…)` still wins |
|
|
2818
|
+
| `Cannot read properties of undefined (reading 'name')` on `value.constructor.name`, with `createAutoMock<T>()`| `constructor` answered `undefined` on an auto-mock | upgrade — it answers `Object`, as every other double does; or seed it: `createAutoMock<T>({ constructor: … })` |
|
|
2712
2819
|
| `Cannot redefine property: …` from a library accessor spy | the target is an ES module namespace a bundler inlined | no spy library can win this — give the code a real seam (inject it, pass it in) and spy on that |
|
|
2713
2820
|
| `Cannot spy on this instance in place: it is not extensible` | `createSpyFromInstance` was handed a frozen or sealed object | spy it before it is frozen, or build a separate double with `createSpyFromClass` (§2) |
|
|
2714
2821
|
| `Cannot mock the property 'X': it is not configurable` | the member was defined with `configurable: false`, so it cannot be redefined | members replaced before it are still restorable — `restoreSpiedInstance(instance)` or `restoreMockedProps()` |
|
|
@@ -2721,7 +2828,7 @@ packages, which a subpath export can never be.
|
|
|
2721
2828
|
| `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) |
|
|
2722
2829
|
| `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) |
|
|
2723
2830
|
| `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) |
|
|
2724
|
-
| `injectSpy(X): the injector returned a plain instance, not an auto-spy` | the token is provided for real; a `console.warn`
|
|
2831
|
+
| `injectSpy(X): the injector returned a plain instance, not an auto-spy` | the token is provided for real; a `console.warn` once per token and spec file, a **throw** under `enableAngularDiagnostics({ unspiedProviders })` or `setupAutoSpy({ misconfiguration: 'throw' })` | `provideAutoSpy(X)` / `provideAutoSpyForToken(TOKEN)`, or `TestBed.inject(X)` if the real thing is what the spec wants |
|
|
2725
2832
|
| `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) |
|
|
2726
2833
|
| `Spread syntax requires ...iterable[Symbol.iterator] to be a function`, at load time | a module-scope spread of an imported binding, inside a bundle | build the array in a function or a getter — `no-import-time-spread` (§16) |
|
|
2727
2834
|
| `the timers APIs are not mocked` in a nested `describe`'s `beforeAll` | fakes armed in `beforeEach` only; Jest armed them for the whole file | `setupFakeTimers(cfg, { betweenTests: true })` / `setupAutoSpy({ globalFakeTimers: true })` |
|
|
@@ -2778,12 +2885,14 @@ packages, which a subpath export can never be.
|
|
|
2778
2885
|
| a 30 s timeout, in a different file each run | module-level `vi.fn()` in a fixture shared by files | make the fixture a factory (§10) |
|
|
2779
2886
|
| a component's `afterNextRender` state is empty | `detectChanges()` does not run the after-render phase | `await stable(fixture)` (§11) |
|
|
2780
2887
|
| a green test whose only line about a call is `spy.m.calledWith(1);` | that is a **stub**, not an assertion — chai's `expect(fn).to.have.been.calledWith(x)` is the one that checks | continue the chain, or `expect(spy.m).toHaveBeenCalledWith(1)` — `no-bare-called-with` (§16) |
|
|
2781
|
-
| `--detect-async-leaks` reporting no leaks in a suite that visibly leaks timers | `setupAutoSpy({ strayTimers: true })` cancelled them before Vitest collected |
|
|
2888
|
+
| `--detect-async-leaks` reporting no leaks in a suite that visibly leaks timers | `setupAutoSpy({ strayTimers: true })` cancelled them before Vitest collected | the warning on stderr says how many were taken and where the first three were scheduled; `onStrayTimers` gets every one in `timers` (§10) |
|
|
2782
2889
|
| `createNestUnit(X): parameter #n of Y is typed as \`Object\``(or`undefined — usually a circular import`) | the compiler emitted no runtime class for that parameter — interface, union, primitive, or a circular import | `@Inject(TOKEN)` plus a `providers` entry, or `@Optional()` to receive `undefined`; `@Inject(forwardRef(() => X))` for the import cycle |
|
|
2783
2890
|
| `createNestUnit(X): Y declares n constructor parameter(s) but carries no design:paramtypes metadata` | no `emitDecoratorMetadata` (esbuild / Vite), or `reflect-metadata` loaded after the class | tsc or SWC with `decoratorMetadata`, `@Injectable()`, `reflect-metadata` first; or `@Inject(X)` on every parameter |
|
|
2784
2891
|
| `createNestUnit(X): A -> B -> A is a cycle among the classes built for real` | two exposed / `useClass` classes depend on each other; the helper does not resolve `forwardRef` cycles | expose one side less, or provide it |
|
|
2785
2892
|
| `createNestUnit(X).spies.get(Y): the unit never asked for that token` | same guard as `createWithAutoSpies` — the spy would not be the one the unit uses | stub the token the unit actually injects (the message lists the auto-spied ones) |
|
|
2786
2893
|
| `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` |
|
|
2894
|
+
| `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) |
|
|
2895
|
+
| 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 |
|
|
2787
2896
|
| `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) |
|
|
2788
2897
|
| `this TestBed has no HttpTestingController` | `expectRequest` / `expectNoRequest` on a module that never got the testing backend | `TestBed.configureTestingModule({ providers: [...provideHttpTesting()] })` from `vitest-auto-spy/angular-http` (§13) |
|
|
2789
2898
|
| `the test ended with N unanswered request(s)` | `provideHttpTesting({ verifyOnTeardown })` found a request the spec never flushed | answer it (`await expectRequest(url).flush(body)`), assert its absence (`expectNoRequest(url)`), or `{ verifyOnTeardown: false }` (§13) |
|
|
@@ -2819,6 +2928,8 @@ diagnostic and is free to change: print it, never assert on it.
|
|
|
2819
2928
|
| `createSpyFromClass(X) as unknown as X` | `asInstance(createSpyFromClass(X))` |
|
|
2820
2929
|
| `{ provide: X, useValue: { a: vi.fn(), b: vi.fn() } }` | `provideAutoSpy(X)` |
|
|
2821
2930
|
| `vi.spyOn(TestBed.inject(X), 'method')` | `injectSpy(X).method` |
|
|
2931
|
+
| `vi.spyOn(component, 'ngOnInit')` after `createComponent` | `vi.spyOn(Cls.prototype, 'ngOnInit')` before it, or assert the effect |
|
|
2932
|
+
| `{ provide: IS_BROWSER, useValue: {} }` for an `InjectionToken<boolean>` | `useValue: false` — a value of the type the token declares |
|
|
2822
2933
|
| `Object.defineProperty(service, 'ready', { value: true })` | `mockReadonlyProp(service, 'ready', true)` |
|
|
2823
2934
|
| `source$.subscribe(v => expect(v).toBe(1))` | `await expect(expectEmission(source$)).resolves.toBe(1)` |
|
|
2824
2935
|
| `await lastValueFrom(done$, { defaultValue: undefined })` | `await expectCompletion(done$)` |
|
|
@@ -2856,7 +2967,9 @@ diagnostic and is free to change: print it, never assert on it.
|
|
|
2856
2967
|
| an assertion containing a date, with no clock set | `mockSystemTime(iso)` first |
|
|
2857
2968
|
| `configureTestingModule` inside every `it()` | one per `describe` |
|
|
2858
2969
|
| `vi.mock('@angular/core')` to neutralise `effect()` | set the signals, `await stable(fixture)`, assert the result |
|
|
2859
|
-
| a second `vi.spyOn(console, 'error')` | `consoleErrorSpy` from `vitest-auto-spy/console`
|
|
2970
|
+
| a second `vi.spyOn(console, 'error')` | `consoleErrorSpy` from `vitest-auto-spy/console`, installed with `installConsoleSpies()` |
|
|
2971
|
+
| `import { consoleErrorSpy } from 'vitest-auto-spy/console'` as the install | `installConsoleSpies()` in `beforeEach`, `restoreConsole()` in `afterEach` — the import installs once per worker |
|
|
2972
|
+
| a bare `vi.spyOn(console, 'error')` to keep a spec quiet | `.mockImplementation(() => undefined)` — without it the line still prints |
|
|
2860
2973
|
| `mockReadonlyProp(c, 'items', vi.fn(() => []))` | `mockReadonlyProp(c, 'items', signal([]))` — a real signal |
|
|
2861
2974
|
| `spy.m.mockReturnValue(subject$)` for a `vi.fn(() => subject$)` | `spy.m.mockImplementation(() => subject$)` — the variable is re-read |
|
|
2862
2975
|
|