vitest-auto-spy 5.10.1 → 5.12.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 +35 -3
- package/README.md +90 -49
- package/dist/angular-http.js +1 -1
- package/dist/angular-router.js +1 -1
- package/dist/angular.d.ts +1 -1
- package/dist/angular.js +171 -89
- package/dist/bun-angular.d.ts +1 -1
- package/dist/bun-angular.js +24 -10
- package/dist/bun.js +4 -4
- package/dist/{chunk-BDJYPKLB.js → chunk-33NJHIS2.js} +51 -27
- package/dist/{chunk-JOI3BLCL.js → chunk-ADBB2HQG.js} +3 -3
- package/dist/{chunk-F334GPEI.js → chunk-BWT7U4V4.js} +2 -2
- package/dist/{chunk-GNJLDNJS.js → chunk-M5BULP7X.js} +4 -1
- package/dist/{chunk-NQNHA3R4.js → chunk-MUJTPXMJ.js} +68 -11
- package/dist/{chunk-HZPV3CHD.js → chunk-OS4PTU6D.js} +2 -16
- package/dist/{chunk-FNOXP6VT.js → chunk-PA4ZNLNZ.js} +1 -1
- package/dist/{chunk-HXH2QXK2.js → chunk-RPEYIZPL.js} +1 -1
- package/dist/{chunk-GZLFVXCY.js → chunk-ZU55UY2M.js} +1 -1
- package/dist/cli.js +1 -1
- package/dist/console.js +3 -3
- package/dist/dom-stubs.js +59 -25
- package/dist/eslint-plugin.cjs +384 -155
- package/dist/index.js +142 -75
- package/dist/jasmine.js +6 -6
- package/dist/nestjs.js +7 -7
- package/dist/node.cjs +63 -20
- package/dist/node.js +4 -4
- package/dist/react.js +6 -6
- package/dist/rstest.js +5 -5
- package/dist/setup.js +6 -6
- package/dist/svelte.js +6 -6
- package/dist/{track-signal-runs-BoA6j-74.d.ts → track-signal-runs-C-DgtCj9.d.ts} +13 -11
- package/dist/vue.js +7 -7
- package/package.json +2 -2
- package/skills/vitest-auto-spy/SKILL.md +19 -2
package/AGENTS.md
CHANGED
|
@@ -771,6 +771,15 @@ Precedence, first one set wins: the double's `onUnstubbedCall` → the double's
|
|
|
771
771
|
and a `calledWith` configured after them is never consulted. After the last `mockReturnValueOnce`
|
|
772
772
|
the queue empties back onto the library dispatch and the next call **is** reported as unstubbed —
|
|
773
773
|
seed `mockReturnValue(...)` too when a `Once` sequence is meant to run out.
|
|
774
|
+
- **That pair is reported, in both orders.** `mockReturnValue` after a `calledWith` erases it;
|
|
775
|
+
`calledWith` after a `mockReturnValue` is dead on arrival. Neither fails on its own — the spec goes
|
|
776
|
+
green on a branch nobody configured — so the library says so as a misconfiguration (warn, or throw
|
|
777
|
+
under the `strict` preset). The whole family replaces: `mockImplementation`, `mockReturnValue`,
|
|
778
|
+
`mockReturnThis`, `mockThrow`, `mockResolvedValue`, `mockRejectedValue`. When both a fallback and a
|
|
779
|
+
per-argument value are wanted, the fallback goes in the spy's container — `returns:` where the
|
|
780
|
+
double is built, or `resolveWith` / `nextWith` / `failWith` — which a `calledWith` still wins over.
|
|
781
|
+
The report is this library's own spy engine's, so Vitest and Rstest have it; Bun, `node:test` and
|
|
782
|
+
`setSpyEngine('runner')` do not. The `Once` family is never reported.
|
|
774
783
|
- **A throw something swallowed still fails the test.** Under `setupAutoSpy({ strict: true })` every
|
|
775
784
|
strict throw is recorded, and one that a `try`/`catch` in the code under test or an RxJS error with
|
|
776
785
|
no handler (its rethrow waits on a fake clock) kept from the test fails it after the test
|
|
@@ -1724,6 +1733,25 @@ Three rules worth stating outright, because each of them cost a day somewhere:
|
|
|
1724
1733
|
teardown — a green run with `NG0205: Injector has already been destroyed` in "Unhandled Errors"
|
|
1725
1734
|
and a non-zero exit code.
|
|
1726
1735
|
|
|
1736
|
+
**`stable()` and `flushEffects()` are not zoneless-only, and their tick runs inside the `NgZone` so
|
|
1737
|
+
that they are not.** `TestBed.createComponent` builds the component inside `ngZone.run(…)`, so an
|
|
1738
|
+
`effect()` its constructor registers records the zone's inner zone as its own. A bare `TestBed.tick()`
|
|
1739
|
+
from a test body runs in the runner's zone, so Angular hops back into the recorded one to run a
|
|
1740
|
+
**dirty** effect, and leaving that hop takes the zone from unstable to stable.
|
|
1741
|
+
`provideZoneChangeDetection()` — which `@angular/build:unit-test` installs for every suite that loads
|
|
1742
|
+
zone.js — answers `NgZone.onMicrotaskEmpty` with `ApplicationRef._tick()`, guarded against its own
|
|
1743
|
+
scheduler but not against `ApplicationRef._runningTick`, so the tick already on the stack is
|
|
1744
|
+
re-entered: `NG0101: ApplicationRef.tick is called recursively`. Angular hands that to `ErrorHandler`
|
|
1745
|
+
instead of throwing it at the call site, so a suite that does not fail on console output stays green
|
|
1746
|
+
with the change detection it asked for unfinished. Measured on an Angular 22 suite of 1771 spec files:
|
|
1747
|
+
451 `componentRef.setInput` calls rewritten to `setInputs` turned **57 green files red** on `NG0101`,
|
|
1748
|
+
and none of them after the tick moved inside the zone. Under zoneless `NgZone` is a `NoopNgZone` whose
|
|
1749
|
+
`run` is a straight call, so it costs nothing there. One thing is genuinely new for a zone-based
|
|
1750
|
+
consumer: leaving the zone reports it stable, so the same subscriber ticks once more — counted on
|
|
1751
|
+
`ApplicationRef.afterTick`, one `flushEffects()` is 1 → 2 application ticks, which is what
|
|
1752
|
+
`fixture.detectChanges()` has always done in the same position. Do not "simplify" either helper back
|
|
1753
|
+
to a bare `TestBed.tick()`; `src/zone-tests/stable.zone-test.ts` is the guard.
|
|
1754
|
+
|
|
1727
1755
|
`flushEventLoopUntil(isDone, { turns, label })` is the same thing with a condition and a budget —
|
|
1728
1756
|
for a chunk becoming reachable, an SDK reporting itself ready, a queue draining. Use it instead of a
|
|
1729
1757
|
hand-tuned turn count: the count depends on the dependency, not on the spec, and a condition that
|
|
@@ -2688,6 +2716,7 @@ const { fixture, component } = renderShallow(TaskListComponent, {
|
|
|
2688
2716
|
// change an input after the first render — one setInput per key, then the wait, in one await
|
|
2689
2717
|
await setInputs(fixture, { projectId: 7 }); // a name the component does not declare is refused here
|
|
2690
2718
|
await setInputs(fixture, { projectId: 7 }, { label: 'the task list' }); // options go to stable()
|
|
2719
|
+
// a fixture whose componentType carries no ɵcmp is refused by name too, not as a bare TypeError
|
|
2691
2720
|
|
|
2692
2721
|
// build a class through DI, every unprovided token auto-spied
|
|
2693
2722
|
const { instance, spies } = createWithAutoSpies(CartService, {
|
|
@@ -3115,7 +3144,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
|
|
|
3115
3144
|
| --------------------------------- | ------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
3116
3145
|
| `no-expect-in-subscribe` | `error` | suggest | `expect()` inside `subscribe()` → `expectEmission` / `firstValueFrom` |
|
|
3117
3146
|
| `no-object-define-property` | `error` | suggest | `Object.defineProperty` in a spec → `mockReadonlyProp` / `mockValueProp` |
|
|
3118
|
-
| `prefer-provide-auto-spy` | `error` |
|
|
3147
|
+
| `prefer-provide-auto-spy` | `error` | fix | a hand-rolled `useValue`, `useFactory`, `useClass` or `useExisting` (also `useValue: new StubMock()`), in a provider **or** a `TestBed.overrideProvider(X, { … })` → `provideAutoSpy(Class)` / `provideAutoSpyForToken(TOKEN)`. A name in the slot is followed one step, through an initialiser or a single `beforeEach` assignment. `{ provide: X, useValue: createSpyFromClass(X, config) }` — the factory's own body, written out — is reported and **rewritten** to `provideAutoSpy(X, config)`; a double read from a _different_ class (`{ provide: LocalStorage, useValue: createSpyFromClass(BaseLocalStorage) }`) is not, since the abstract token has no prototype for `provideAutoSpy` to read |
|
|
3119
3148
|
| `prefer-create-spy-from-class` | `error` | — | an object literal of 2+ `vi.fn()`s → `createSpyFromClass` (a factory's own seed is exempt) |
|
|
3120
3149
|
| `no-stub-class-double` | `warn` | — | a class whose fields are `vi.fn()`s → `createSpyFromClass` / `provideAutoSpy` and delete the stub; a decorated, `implements`-ing, `extends`-ing or unnamed class is exempt — option: `{ minRunnerFns }` |
|
|
3121
3150
|
| `no-structural-double` | `warn` | — | an object of `vi.fn()`s bound to a name declared `{ load: Mock }` → `createAutoMock<T>()`; a bare `let fn: Mock` is a callback and never reported — option: `{ minRunnerFns }` |
|
|
@@ -3133,6 +3162,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
|
|
|
3133
3162
|
| `no-import-time-spread` | `error` | suggest | `export const x = [...Imported]` at module scope → a `TypeError` while the bundle loads |
|
|
3134
3163
|
| `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 |
|
|
3135
3164
|
| `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 |
|
|
3165
|
+
| `prefer-set-inputs` | `warn` | suggest | a run of `fixture.componentRef.setInput('title', v)` on one fixture → `await setInputs(fixture, { title: v })` — the name is resolved against the compiled definition before the first write (an undeclared one is an `NG0303` and no change) and the value is typed. The run collapses into one call and a `detectChanges()` under it goes; offered, not applied, because `stable()` ticks and a zone.js suite answers that with `NG0101` |
|
|
3136
3166
|
| `prefer-observer-stub` | `error` | — | a hand-rolled observer global → `stubIntersectionObserver()` / `stubResizeObserver()` / `stubMutationObserver()`; the manual save-and-restore goes too, `restoreMockedProps()` runs the undo |
|
|
3137
3167
|
| `prefer-provide-activated-route` | `error` | — | a hand-built `ActivatedRoute` — any `useValue` / `useClass` / `useFactory` / `useExisting`, and `provideAutoSpy(ActivatedRoute)` too → `provideActivatedRoute({ … })`; the double knows either the streams or the snapshot, never both, and `injectActivatedRoute().setParams(…)` moves them together mid-test |
|
|
3138
3168
|
| `no-passthrough-console-spy` | `error` | suggest | `vi.spyOn(console, m)` nothing gives an implementation — it calls through and prints → `installConsoleSpies()` + `consoleXSpy`, or `.mockImplementation(() => undefined)` |
|
|
@@ -3151,8 +3181,8 @@ blanket downgrade so those keep their severity; do not copy the two names into a
|
|
|
3151
3181
|
| `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()` — a no-op here, so the spec silently asserts on post-mutation state |
|
|
3152
3182
|
| `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 |
|
|
3153
3183
|
|
|
3154
|
-
Thirty-
|
|
3155
|
-
`no-stub-class-double`, `no-structural-double
|
|
3184
|
+
Thirty-eight rules, **every one an `error` since 4.0.0 except `prefer-render-shallow`,
|
|
3185
|
+
`no-stub-class-double`, `no-structural-double`, `no-instance-lifecycle-spy` and `prefer-set-inputs`**; four fix on their own, thirteen offer suggestions. Thirty-five are syntactic; `no-private-member-access`, `no-mistyped-use-value` and
|
|
3156
3186
|
`no-unknown-use-value-key` read types, and all three report nothing at all without `parserOptions.project` / `projectService`
|
|
3157
3187
|
rather than guessing. `no-compile-components` waits the same way for a fact no file holds — which
|
|
3158
3188
|
builder the project has — and reports nothing until `{ builder: 'inline-resources' }` states it. The config used to be a graded mix of `error` / `warn` / `off`, which decided for the
|
|
@@ -3396,6 +3426,8 @@ packages, which a subpath export can never be.
|
|
|
3396
3426
|
| `nextWith` demanding `HttpEvent<T>` on a generated API client | `Parameters`/`ReturnType` read the **last** overload | `asSpy<Client, { overload: 'first' }>(…)`, or `Overload<M, 0>` — never `@ts-expect-error`, which `no-ts-expect-error-on-double` reports (§16) |
|
|
3397
3427
|
| `NG0303` / `NG0304` / nothing at all, from a directive spec | the host is `standalone: false`, or the module is in the TestBed | `createDirectiveHost({ template, scope: [Module] })` (§13) |
|
|
3398
3428
|
| `setInputs: … declares no input named …` | the key is not an input of that component — a typo, an input renamed since, or one of its own fields | set the names the message lists; a signal the component owns is moved through the signal (§13) |
|
|
3429
|
+
| `setInputs: X carries no ɵcmp, so there are no inputs to set` | the fixture's `componentType` is not a compiled `@Component` — a `@Directive` or `@Pipe` class, or one Angular never compiled (a barrel import that resolved to `undefined`) | pass a component fixture; a directive takes its inputs through the host element that applies it (`createDirectiveHost`), and for `undefined` import the class from its own file (§13) |
|
|
3430
|
+
| `TypeError: dependencies is not a function`, from `renderShallow({ keepTemplate: true })` | the compiled `ɵcmp.dependencies` is the flat array AOT emits when no cycle forced a factory, and the trim called it | upgrade — all three shapes (array, factory, `null`) are read; nothing to change in the spec |
|
|
3399
3431
|
| `TS2540: Cannot assign to 'X' because it is a read-only property` | a `readonly` field of an object under test | `mockValueProp(obj, 'X', value)` — on a class **getter**, `mockReadonlyProp` |
|
|
3400
3432
|
| `TypeError: Cannot read properties of undefined (reading 'mockReturnValue')` | the member is an **instance field**, not on the prototype — `Router.currentNavigation` since Angular 20 | `provideAutoSpy(Router, { instanceMethodsToSpyOn: ['currentNavigation'] })` (§5) |
|
|
3401
3433
|
| `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` |
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ faster at suite scale ([benchmarks](#benchmarks)) — and for
|
|
|
23
23
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
24
24
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
25
25
|
[](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
|
|
26
|
-
[](#install)
|
|
27
27
|
[](https://www.npmjs.com/package/vitest-auto-spy)
|
|
28
28
|
[](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
|
|
29
29
|
[](./LICENSE)
|
|
@@ -79,7 +79,7 @@ faster at suite scale ([benchmarks](#benchmarks)) — and for
|
|
|
79
79
|
- 🧩 Module mocks that prove they applied — `assertMocked`, `moduleNamespace`, for a `vi.mock()` a bundler quietly ignored
|
|
80
80
|
- 🧾 Fixtures without casts — deep-partial `createMock`, `createFixture` / `createFixtureFactory`, `narrow()`, `withOverrides()`, `asInstances()`, `captureArg()`
|
|
81
81
|
- 🚚 A migration you can verify — `vitest-auto-spy/diagnostics`: `compareTestRuns` on the two JSON reports, `summarizeTestRun` / `formatTestRunComparison` to read the answer, `diffByField` for the assertion the reporter collapses, `explainSpy` for a double that answered something you did not configure
|
|
82
|
-
- 📏 Lint rules and one-line test-run hygiene — thirty-
|
|
82
|
+
- 📏 Lint rules and one-line test-run hygiene — thirty-eight rules in `vitest-auto-spy/eslint-plugin` (three `--fix`, thirteen suggestions, four of them for a suite mid-migration off jasmine), `setupAutoSpy()` — with `preset: 'strict'` for every guard at its strictest grade
|
|
83
83
|
- 🩺 [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
|
|
84
84
|
- 🔎 [`npx vitest-auto-spy doctor`](#the-cli--doctor-perf-codemod-and-init) — suite-level defects **that never fail a run**: a `tsconfig` `include` matching no file, a production module importing a spec, a `@jest-environment` pragma the runner never reads, config left behind for a runner that is gone. Read-only, no config, exits 1 in CI
|
|
85
85
|
- ⏱️ [`npx vitest-auto-spy perf`](#perf--where-the-cpu-time-actually-goes) — where a suite's CPU time actually goes, phase by phase, and which spec files to act on: the ones that reach no DOM and could run under `node`, the ones that import a barrel. Runs Vitest once with a reporter this package ships, reads `TestModule.diagnostic()`, names files, states the rule behind each finding
|
|
@@ -2091,6 +2091,17 @@ myService.getName.mustBeCalledWith(1).mockReturnValue('Fake Name');
|
|
|
2091
2091
|
expect(() => myService.getName(2)).toThrow();
|
|
2092
2092
|
```
|
|
2093
2093
|
|
|
2094
|
+
**The first line and the second do not layer.** `mockReturnValue` and its family
|
|
2095
|
+
(`mockImplementation`, `mockResolvedValue`, `mockRejectedValue`, `mockThrow`, `mockReturnThis`)
|
|
2096
|
+
install an implementation on the host mock, and the dispatch that reads a `calledWith` chain **is**
|
|
2097
|
+
the implementation they replace — so the one written later wins outright and the other decides
|
|
2098
|
+
nothing, in either order, without failing. Both orders are now reported as a misconfiguration (a
|
|
2099
|
+
warning, a throw under the `strict` preset). Where both are wanted, the fallback goes in the spy's
|
|
2100
|
+
own container, which a chain still wins over: the `returns` option where the double is built, or
|
|
2101
|
+
`resolveWith` / `nextWith` / `failWith`. The report comes from this library's own spy engine — so
|
|
2102
|
+
Vitest and Rstest, not Bun or `node:test`, and not under `setSpyEngine('runner')` — and the `Once`
|
|
2103
|
+
family is never reported, because its queue drains back onto the dispatch.
|
|
2104
|
+
|
|
2094
2105
|
The failure prints both sides, because the diagnosis is the comparison rather than either half of
|
|
2095
2106
|
it — and every configured call when there is more than one, matchers included, so a config that
|
|
2096
2107
|
never matched is visible instead of inferred:
|
|
@@ -2371,8 +2382,14 @@ which shallow rendering touches.
|
|
|
2371
2382
|
override keeps the component's own `imports` minus the child _components_, so the template renders
|
|
2372
2383
|
with its own pipes and directives working while every child component in it resolves to nothing
|
|
2373
2384
|
under `NO_ERRORS_SCHEMA`. (Before 5.4.0 the whole scope was dropped, which made a pipe in a kept
|
|
2374
|
-
template throw `NG0302` and an attribute directive silently never apply.)
|
|
2375
|
-
|
|
2385
|
+
template throw `NG0302` and an attribute directive silently never apply.) Those imports are read off
|
|
2386
|
+
`ɵcmp`, where the compiler leaves them in one of three shapes — the flat array, a factory returning
|
|
2387
|
+
it, or `null` for a component that imports nothing — and which one is not decided by JIT against
|
|
2388
|
+
AOT: JIT always emits the factory, AOT emits the array unless a cycle between two components forces
|
|
2389
|
+
it to defer the read. All three are handled; calling the array unconditionally was a
|
|
2390
|
+
`TypeError: dependencies is not a function` on an AOT-compiled component with no cycle in its
|
|
2391
|
+
imports. A child re-exported by an imported `NgModule` still renders — the module is kept whole.
|
|
2392
|
+
Reach for it when the spec reads
|
|
2376
2393
|
something the template creates, and keep `keepChildren` for the handful of children it genuinely
|
|
2377
2394
|
needs resolvable. Full write-up:
|
|
2378
2395
|
[Performance](https://asdalexey.github.io/vitest-auto-spy/core/performance#_2-rendering-the-child-subtree).
|
|
@@ -2417,8 +2434,20 @@ flushEffects(); // the no-fixture half: services, stores, runInInjectionContext
|
|
|
2417
2434
|
`fixture.detectChanges()` runs a single change-detection pass and does **not** flush pending
|
|
2418
2435
|
effects, so an assertion right after it reads state that has not finished computing. In a zoneless
|
|
2419
2436
|
app the state that matters is signal-derived and effects are what move it forward. `stable` does
|
|
2420
|
-
both, in the right order; `flushEffects`
|
|
2421
|
-
|
|
2437
|
+
both, in the right order; `flushEffects` is `TestBed.tick()`, run inside the `NgZone`.
|
|
2438
|
+
|
|
2439
|
+
**Both work under zone.js, and the zone is why the tick is wrapped.** The name says zoneless and the
|
|
2440
|
+
helpers are written for it, but nothing in them is — `stable` and `setInputs` are the same two lines
|
|
2441
|
+
in a zone-based suite. `TestBed.createComponent` builds the component inside `ngZone.run(…)`, so an
|
|
2442
|
+
`effect()` its constructor registers records the zone's inner zone; a tick started from a test body
|
|
2443
|
+
runs in the runner's zone, hops back to run a **dirty** effect, and leaving that hop reports the zone
|
|
2444
|
+
stable mid-tick. `provideZoneChangeDetection()` — which the Angular CLI's unit-test builder installs
|
|
2445
|
+
for every suite that loads zone.js — answers that with `ApplicationRef._tick()`, guarded against its
|
|
2446
|
+
own scheduler but not against the tick already on the stack, which is
|
|
2447
|
+
`NG0101: ApplicationRef.tick is called recursively`. Running the tick inside the zone removes the hop.
|
|
2448
|
+
Measured on an Angular 22 suite of 1771 spec files: 451 `componentRef.setInput` calls rewritten to
|
|
2449
|
+
`setInputs` turned **57 green files red** on `NG0101`, and none of them after the fix. Under zoneless
|
|
2450
|
+
`NgZone` is a `NoopNgZone` whose `run` is a straight call, so it costs nothing there.
|
|
2422
2451
|
|
|
2423
2452
|
The wait is bounded: `stable` gives the fixture **2000 ms** and then throws the cause, instead of
|
|
2424
2453
|
letting the runner report a 5 s file-level timeout that names neither the helper nor the fixture.
|
|
@@ -3336,7 +3365,7 @@ import autoSpy from 'vitest-auto-spy/eslint-plugin';
|
|
|
3336
3365
|
export default [{ files: ['**/*.spec.ts'], ...autoSpy.configs.recommended }];
|
|
3337
3366
|
```
|
|
3338
3367
|
|
|
3339
|
-
**Every rule is an `error` since 4.0.0, bar
|
|
3368
|
+
**Every rule is an `error` since 4.0.0, bar five.** The config used to grade them `error` / `warn` /
|
|
3340
3369
|
`off`, which chose for you how much each finding mattered; a `warn` in a repository that does not read
|
|
3341
3370
|
lint output is `off` with extra noise, and which findings block a merge is one line of config either
|
|
3342
3371
|
way. To turn one down, spread the rule map as well — a bare `rules` key beside the spread config
|
|
@@ -3392,51 +3421,52 @@ object of `vi.fn()`s is perfectly reasonable in application code. Flat config on
|
|
|
3392
3421
|
`.eslintrc` `plugins: ['…']` form resolves names to `eslint-plugin-*` packages, which a subpath
|
|
3393
3422
|
export can never be.
|
|
3394
3423
|
|
|
3395
|
-
| Rule | Recommended | Fix | Flags
|
|
3396
|
-
| --------------------------------- | :---------: | ----------------- |
|
|
3397
|
-
| `prefer-provide-auto-spy` | `error` |
|
|
3398
|
-
| `prefer-create-spy-from-class` | `error` | — | an object literal of two or more `vi.fn()`s → `createSpyFromClass` / `createAutoMock`, unless it is a factory's own seed
|
|
3399
|
-
| `no-stub-class-double` | `warn` | — | a class whose fields are `vi.fn()`s → `createSpyFromClass` / `provideAutoSpy`, the stub class deleted
|
|
3400
|
-
| `no-structural-double` | `warn` | — | an object of `vi.fn()`s bound to a name declared `{ load: Mock }` → `createAutoMock<T>()`
|
|
3401
|
-
| `prefer-inject-spy` | `error` | suggest | `vi.spyOn(TestBed.inject(X), 'm')`, in one step or two → `injectSpy(X).m`
|
|
3402
|
-
| `no-object-define-property` | `error` | suggest | `Object.defineProperty` in a spec → `mockReadonlyProp` / `mockValueProp`
|
|
3403
|
-
| `no-expect-in-subscribe` | `error` | suggest | `expect()` inside a `subscribe()` callback → `expectEmission` / `firstValueFrom`
|
|
3404
|
-
| `no-shared-module-level-mock` | `error` | — | an **exported** value holding `vi.fn()`s → export a factory that returns it
|
|
3405
|
-
| `no-mocked-for-spy` | `error` | `--fix` / suggest | `Mocked<T>` in any type position → `Spy<T>`, import and all — a suggestion where the value assigned is not one of this library's factories
|
|
3406
|
-
| `prefer-as-spy` | `error` | `--fix` | `TestBed.inject(X) as Spy<X>` → `asSpy<X>(TestBed.inject(X))`, import and all
|
|
3407
|
-
| `no-done-callback` | `error` | — | `it('x', (done) => …)` → `async` + an awaited assertion, and `done.fail(…)` at the call site
|
|
3408
|
-
| `no-floating-assertion` | `error` | — | `expect()` in a `.then()` nobody awaits → `expect(await promise)`
|
|
3409
|
-
| `no-bare-called-with` | `error` | — | `spy.m.calledWith(1);` as a statement of its own — a stub nobody continued, asserting nothing
|
|
3410
|
-
| `no-overridden-provider` | `error` | suggest | two providers for one token in one array → the earlier one never runs; the exact duplicate can be deleted
|
|
3411
|
-
| `no-inject-before-override` | `error` | — | `TestBed.inject()` / `injectSpy()` / `renderShallow()` in a hook, in a suite that still calls `override*`
|
|
3412
|
-
| `no-private-member-access` | `error` | — | `instance['privateMember']`, `(instance as any).privateMember` and `vi.spyOn(Object.getPrototypeOf(x), 'm')` — the three spellings that get past `private`; **type-aware**, and silent without `parserOptions.project`
|
|
3413
|
-
| `no-dead-schemas` | `error` | — | `schemas` on a testing module that declares nothing — a charm protecting nobody, and one that starts working the day `declarations` arrive
|
|
3414
|
-
| `no-import-time-spread` | `error` | suggest | `export const x = [...Imported]` at module scope → a `TypeError` while the bundle loads
|
|
3415
|
-
| `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
|
|
3416
|
-
| `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
|
|
3417
|
-
| `prefer-
|
|
3418
|
-
| `prefer-
|
|
3419
|
-
| `
|
|
3420
|
-
| `no-console-
|
|
3421
|
-
| `no-
|
|
3422
|
-
| `no-
|
|
3423
|
-
| `no-
|
|
3424
|
-
| `no-
|
|
3425
|
-
| `no-
|
|
3426
|
-
| `no-
|
|
3427
|
-
| `no-
|
|
3428
|
-
| `no-
|
|
3429
|
-
| `no-
|
|
3430
|
-
| `
|
|
3431
|
-
| `
|
|
3432
|
-
| `no-
|
|
3433
|
-
| `
|
|
3424
|
+
| Rule | Recommended | Fix | Flags |
|
|
3425
|
+
| --------------------------------- | :---------: | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
3426
|
+
| `prefer-provide-auto-spy` | `error` | fix | a hand-rolled `useValue`, `useFactory` **or** `useClass` → `provideAutoSpy(Class)` / `provideAutoSpyForToken(TOKEN)`; and `{ provide: X, useValue: createSpyFromClass(X) }` — the factory written out — which it rewrites |
|
|
3427
|
+
| `prefer-create-spy-from-class` | `error` | — | an object literal of two or more `vi.fn()`s → `createSpyFromClass` / `createAutoMock`, unless it is a factory's own seed |
|
|
3428
|
+
| `no-stub-class-double` | `warn` | — | a class whose fields are `vi.fn()`s → `createSpyFromClass` / `provideAutoSpy`, the stub class deleted |
|
|
3429
|
+
| `no-structural-double` | `warn` | — | an object of `vi.fn()`s bound to a name declared `{ load: Mock }` → `createAutoMock<T>()` |
|
|
3430
|
+
| `prefer-inject-spy` | `error` | suggest | `vi.spyOn(TestBed.inject(X), 'm')`, in one step or two → `injectSpy(X).m` |
|
|
3431
|
+
| `no-object-define-property` | `error` | suggest | `Object.defineProperty` in a spec → `mockReadonlyProp` / `mockValueProp` |
|
|
3432
|
+
| `no-expect-in-subscribe` | `error` | suggest | `expect()` inside a `subscribe()` callback → `expectEmission` / `firstValueFrom` |
|
|
3433
|
+
| `no-shared-module-level-mock` | `error` | — | an **exported** value holding `vi.fn()`s → export a factory that returns it |
|
|
3434
|
+
| `no-mocked-for-spy` | `error` | `--fix` / suggest | `Mocked<T>` in any type position → `Spy<T>`, import and all — a suggestion where the value assigned is not one of this library's factories |
|
|
3435
|
+
| `prefer-as-spy` | `error` | `--fix` | `TestBed.inject(X) as Spy<X>` → `asSpy<X>(TestBed.inject(X))`, import and all |
|
|
3436
|
+
| `no-done-callback` | `error` | — | `it('x', (done) => …)` → `async` + an awaited assertion, and `done.fail(…)` at the call site |
|
|
3437
|
+
| `no-floating-assertion` | `error` | — | `expect()` in a `.then()` nobody awaits → `expect(await promise)` |
|
|
3438
|
+
| `no-bare-called-with` | `error` | — | `spy.m.calledWith(1);` as a statement of its own — a stub nobody continued, asserting nothing |
|
|
3439
|
+
| `no-overridden-provider` | `error` | suggest | two providers for one token in one array → the earlier one never runs; the exact duplicate can be deleted |
|
|
3440
|
+
| `no-inject-before-override` | `error` | — | `TestBed.inject()` / `injectSpy()` / `renderShallow()` in a hook, in a suite that still calls `override*` |
|
|
3441
|
+
| `no-private-member-access` | `error` | — | `instance['privateMember']`, `(instance as any).privateMember` and `vi.spyOn(Object.getPrototypeOf(x), 'm')` — the three spellings that get past `private`; **type-aware**, and silent without `parserOptions.project` |
|
|
3442
|
+
| `no-dead-schemas` | `error` | — | `schemas` on a testing module that declares nothing — a charm protecting nobody, and one that starts working the day `declarations` arrive |
|
|
3443
|
+
| `no-import-time-spread` | `error` | suggest | `export const x = [...Imported]` at module scope → a `TypeError` while the bundle loads |
|
|
3444
|
+
| `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 |
|
|
3445
|
+
| `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 |
|
|
3446
|
+
| `prefer-set-inputs` | `warn` | suggest | a run of `fixture.componentRef.setInput('title', v)` on one fixture → `await setInputs(fixture, { title: v })`, which resolves every name against the compiled definition before it writes one — an undeclared name is an `NG0303` on the console and no change at all — and types the value. The suggestion collapses the run, drops a `detectChanges()` under it and makes the callback `async`; it is offered rather than applied, because `stable()` ticks and a zone.js suite can answer that with `NG0101` |
|
|
3447
|
+
| `prefer-observer-stub` | `error` | — | a hand-rolled `IntersectionObserver` / `ResizeObserver` / `MutationObserver` written into a global → `stubIntersectionObserver()` and friends, whose undo `restoreMockedProps()` already runs |
|
|
3448
|
+
| `prefer-provide-activated-route` | `error` | — | a hand-built `ActivatedRoute` — any `useValue` / `useClass` / `useFactory`, and `provideAutoSpy(ActivatedRoute)` too → `provideActivatedRoute({ … })`; the double knows either the streams or the snapshot, never both, and `injectActivatedRoute().setParams(…)` moves them together mid-test |
|
|
3449
|
+
| `no-passthrough-console-spy` | `error` | suggest | `vi.spyOn(console, m)` nothing gives an implementation — it calls through and still prints → `installConsoleSpies()` from `vitest-auto-spy/console`, or `.mockImplementation(() => undefined)` |
|
|
3450
|
+
| `no-console-in-spec` | `error` | — | a spec that calls `console.x(…)` itself, or replaces a method with `console.x = …`, which nothing puts back — under `isolate: false` every later file inherits it |
|
|
3451
|
+
| `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, under `isolate: false`, silences every later file |
|
|
3452
|
+
| `no-mistyped-use-value` | `error` | — | `{ provide: TOKEN, useValue }` whose value is not assignable to the primitive `TOKEN` declares — `useValue` is `any`, so `{}` for an `InjectionToken<boolean>` compiles and is truthy; **type-aware**, and silent without `parserOptions.project` |
|
|
3453
|
+
| `no-unknown-use-value-key` | `error` | — | a key of an object `useValue` literal the provided type does not have — `T` of an `InjectionToken<T>`, or a class's instance; keys only, never the values; silent on `any` / `unknown`, an index signature, a spread and `multi: true`; **type-aware** |
|
|
3454
|
+
| `no-instance-lifecycle-spy` | `warn` | — | `vi.spyOn(component, 'ngOnInit')` and the other hooks a view reads off the prototype — Angular never calls the instance spy → spy on `Cls.prototype` before the component is created, or assert the hook's effect |
|
|
3455
|
+
| `no-ts-expect-error-on-double` | `error` | — | `@ts-expect-error` / `@ts-ignore` above `nextWith`, `resolveWith`, `mockReturnValue`, `calledWith(…)` and the other helpers that check a stub against the method's signature — an overloaded method takes `Spy<X, { overload: { m: 'first' } }>`, anything else is a fixture of the wrong shape |
|
|
3456
|
+
| `no-constant-expect` | `error` | — | `expect(true).toBe(true)`, `expect({ … }).toBeDefined()` — a value the spec spelled out, under a matcher whose answer that value already fixes → assert on what the code produced, or `expect.fail(…)` for a branch the test must not reach |
|
|
3457
|
+
| `no-redundant-smoke-test` | `error` | suggest | `it('should create', () => expect(pipe).toBeTruthy())` — a test whose whole body asks whether the subject is there, in a block whose other tests run the same `beforeEach` and would fail first, naming what they were doing → delete it; the suggestion does. A block whose only running test is that one is left alone |
|
|
3458
|
+
| `no-compile-components` | `error` | suggest | `compileComponents()` under a builder that inlines `templateUrl` / `styleUrls` — a promise already settled; **silent** until `{ builder: 'inline-resources' }` says so, because a JIT setup that loads resources at run time needs the call. One exception the rule cannot see: a component whose template holds a `@defer` block ships async class metadata, so the call stays load-bearing there |
|
|
3459
|
+
| `no-sync-testbed-await` | `error` | suggest | `await` on a TestBed call that answers the TestBed or a fixture — `configureTestingModule`, every `override*`, `resetTestingModule`, `createComponent`, `getLastFixture`; none of them is a promise, so the `await` waits for nothing and the `async` it forced on the hook awaits nothing either. The suggestion drops both. Reads no types, so it reports without `parserOptions.project` |
|
|
3460
|
+
| `jasmine-namespace-without-entry` | `error` | — | `.and` / `.calls` / `.withArgs` on a library spy in a file that installs the compatibility layer nowhere |
|
|
3461
|
+
| `no-jasmine-globals` | `error` | — | `jasmine.*`, `spyOn(` / `spyOnProperty(` / `spyOnAllFunctions(` / `fail(` / `pending(`, `.withContext(` — none of them exist under Vitest |
|
|
3462
|
+
| `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()`, which is a no-op here → take the copy at call time |
|
|
3463
|
+
| `prefer-native-spy-api` | `error` | `--fix` / suggest | `.and` / `.calls` where the spy's own API says the same thing — the last mile off the jasmine shim |
|
|
3434
3464
|
|
|
3435
3465
|
Every message ends with a link to the matching [recipe](#how-to-mock): a rule that only says
|
|
3436
3466
|
"don't" moves the problem rather than solving it. Rules travel with the API they recommend, so they
|
|
3437
3467
|
are versioned together and stop being re-written in every project that installs the package.
|
|
3438
3468
|
|
|
3439
|
-
**
|
|
3469
|
+
**Four of the thirty-eight fix on their own, thirteen offer suggestions**, and the split is not about how hard
|
|
3440
3470
|
the rewrite is. `no-mocked-for-spy` touches a _declaration_: get it wrong and the file stops
|
|
3441
3471
|
compiling, which is the loudest, cheapest failure there is — so `--fix` rewrites the type, adds
|
|
3442
3472
|
`import type { Spy } from 'vitest-auto-spy'` and drops the `Mocked` import once nothing else uses
|
|
@@ -3529,6 +3559,17 @@ line named after construction. It is weighed against the tests that share its se
|
|
|
3529
3559
|
its block. Measured over an Angular suite of 1771 spec files: 569 findings in 540 of them, 515 still carrying the
|
|
3530
3560
|
`should create` / `should be created` title `ng generate` wrote; on a second suite of 845 files, 97 in 87. The suggestion deletes the test together with the blank line above it.
|
|
3531
3561
|
|
|
3562
|
+
`prefer-set-inputs` reports a run of `fixture.componentRef.setInput('name', value)` on one fixture.
|
|
3563
|
+
Angular checks that name against nothing: an input the component does not declare answers with an
|
|
3564
|
+
`NG0303` on the console and **no change at all**, so the spec fails several lines later, on state
|
|
3565
|
+
nothing moved. `setInputs(fixture, { … })` resolves every key against the compiled definition before
|
|
3566
|
+
it writes the first one and types the value — on the 1771-file suite, rewriting the 650 calls it can
|
|
3567
|
+
rewrite turned 72 drifted fixtures into compile errors across 21 files. It reports 504 times in 140
|
|
3568
|
+
files there, and the edit is a **suggestion**: `setInputs` awaits `stable()`, whose `TestBed.tick()`
|
|
3569
|
+
re-enters the tick zone.js schedules for itself, and accepting all 451 of them turned 57 of 105 green
|
|
3570
|
+
files red on `NG0101: ApplicationRef.tick is called recursively`. That is also why it ships as a
|
|
3571
|
+
`warn`: the finding is a fact, the repair is a migration a project takes file by file.
|
|
3572
|
+
|
|
3532
3573
|
`no-compile-components` says nothing until `['error', { builder: 'inline-resources' }]`
|
|
3533
3574
|
states that the builder inlines `templateUrl` / `styleUrls`; then `compileComponents()` is a promise
|
|
3534
3575
|
already settled, and a suggestion deletes it together with the `async` of a hook that awaits nothing
|
|
@@ -3584,7 +3625,7 @@ package's own — it needs its ESLint integration switched on.
|
|
|
3584
3625
|
### WebStorm and the other JetBrains IDEs
|
|
3585
3626
|
|
|
3586
3627
|
No plugin to install: WebStorm, IntelliJ IDEA Ultimate, PhpStorm, PyCharm Professional and RubyMine
|
|
3587
|
-
all run ESLint natively, so the thirty-
|
|
3628
|
+
all run ESLint natively, so the thirty-eight rules appear inline, in the **Problems** tool window, and
|
|
3588
3629
|
under **Code → Inspect Code** for the whole project.
|
|
3589
3630
|
|
|
3590
3631
|
```js
|
|
@@ -3601,7 +3642,7 @@ has supported flat config since 2023.3); scope the block to spec files yourself;
|
|
|
3601
3642
|
the fixes and suggestions live.
|
|
3602
3643
|
|
|
3603
3644
|
A native JetBrains plugin is **not** planned — it would duplicate an integration the IDE already has
|
|
3604
|
-
and then keep a second copy of thirty-
|
|
3645
|
+
and then keep a second copy of thirty-eight rules, in Kotlin, in step with the TypeScript ones.
|
|
3605
3646
|
|
|
3606
3647
|
### VS Code, Cursor, Windsurf, VSCodium
|
|
3607
3648
|
|
package/dist/angular-http.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { flushEffects } from './chunk-
|
|
1
|
+
import { flushEffects } from './chunk-M5BULP7X.js';
|
|
2
2
|
import { withDocs, DOCS_LINKS } from './chunk-Q477VHHF.js';
|
|
3
3
|
import { provideHttpClient } from '@angular/common/http';
|
|
4
4
|
import { provideHttpClientTesting, HttpTestingController } from '@angular/common/http/testing';
|
package/dist/angular-router.js
CHANGED
package/dist/angular.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { o as AngularTokenProvider, A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, R as RenderShallowOptions, c as ResourceStatusLike, d as RunCounter, S as SettleResourceOptions, e as ShallowRender, f as SpyRegistry, g as StableOptions, h as createWithAutoSpies, i as flushEffects, j as injectSpy, p as provideAutoSpy, q as provideAutoSpyForToken, r as renderShallow, k as runEffect, s as setInputs, l as settleResource, m as stable, t as trackEffectRuns, n as trackRecomputations } from './track-signal-runs-
|
|
1
|
+
export { o as AngularTokenProvider, A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, R as RenderShallowOptions, c as ResourceStatusLike, d as RunCounter, S as SettleResourceOptions, e as ShallowRender, f as SpyRegistry, g as StableOptions, h as createWithAutoSpies, i as flushEffects, j as injectSpy, p as provideAutoSpy, q as provideAutoSpyForToken, r as renderShallow, k as runEffect, s as setInputs, l as settleResource, m as stable, t as trackEffectRuns, n as trackRecomputations } from './track-signal-runs-C-DgtCj9.js';
|
|
2
2
|
import { InjectionToken, Provider, Type, WritableSignal, Signal, ProviderToken, FactoryProvider, AbstractType, Injector, ValueProvider } from '@angular/core';
|
|
3
3
|
import { A as AutoMockConfiguration, D as DeepReadonly } from './expect-emission-CmN9ADyN.js';
|
|
4
4
|
export { C as CallbackSubscribable, E as EmissionObserver, b as EmissionOptions, c as EmissionSource, S as SubscribableLike, g as expectCompletion, h as expectEmission, i as expectEmissions, j as expectError, k as expectNoEmission, s as setEmissionTimeout } from './expect-emission-CmN9ADyN.js';
|