vitest-auto-spy 5.10.0 → 5.11.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 CHANGED
@@ -1724,6 +1724,25 @@ Three rules worth stating outright, because each of them cost a day somewhere:
1724
1724
  teardown — a green run with `NG0205: Injector has already been destroyed` in "Unhandled Errors"
1725
1725
  and a non-zero exit code.
1726
1726
 
1727
+ **`stable()` and `flushEffects()` are not zoneless-only, and their tick runs inside the `NgZone` so
1728
+ that they are not.** `TestBed.createComponent` builds the component inside `ngZone.run(…)`, so an
1729
+ `effect()` its constructor registers records the zone's inner zone as its own. A bare `TestBed.tick()`
1730
+ from a test body runs in the runner's zone, so Angular hops back into the recorded one to run a
1731
+ **dirty** effect, and leaving that hop takes the zone from unstable to stable.
1732
+ `provideZoneChangeDetection()` — which `@angular/build:unit-test` installs for every suite that loads
1733
+ zone.js — answers `NgZone.onMicrotaskEmpty` with `ApplicationRef._tick()`, guarded against its own
1734
+ scheduler but not against `ApplicationRef._runningTick`, so the tick already on the stack is
1735
+ re-entered: `NG0101: ApplicationRef.tick is called recursively`. Angular hands that to `ErrorHandler`
1736
+ instead of throwing it at the call site, so a suite that does not fail on console output stays green
1737
+ with the change detection it asked for unfinished. Measured on an Angular 22 suite of 1771 spec files:
1738
+ 451 `componentRef.setInput` calls rewritten to `setInputs` turned **57 green files red** on `NG0101`,
1739
+ and none of them after the tick moved inside the zone. Under zoneless `NgZone` is a `NoopNgZone` whose
1740
+ `run` is a straight call, so it costs nothing there. One thing is genuinely new for a zone-based
1741
+ consumer: leaving the zone reports it stable, so the same subscriber ticks once more — counted on
1742
+ `ApplicationRef.afterTick`, one `flushEffects()` is 1 → 2 application ticks, which is what
1743
+ `fixture.detectChanges()` has always done in the same position. Do not "simplify" either helper back
1744
+ to a bare `TestBed.tick()`; `src/zone-tests/stable.zone-test.ts` is the guard.
1745
+
1727
1746
  `flushEventLoopUntil(isDone, { turns, label })` is the same thing with a condition and a budget —
1728
1747
  for a chunk becoming reachable, an SDK reporting itself ready, a queue draining. Use it instead of a
1729
1748
  hand-tuned turn count: the count depends on the dependency, not on the spec, and a condition that
@@ -2369,11 +2388,17 @@ Five things to know:
2369
2388
  `provideRouter()` is what tests a navigation.
2370
2389
  - **`emitNavigation()` takes what you have** — nothing, a URL string, or an event you built. A
2371
2390
  `NavigationEnd` moves the URL with it; any other event does not.
2372
- - **The navigation in flight follows the events.** `setCurrentNavigation({ extras: { state } })`
2373
- puts one up and `setCurrentNavigation(null)` ends it; a `NavigationStart` pushed through
2374
- `emitNavigation()` starts one with that event's id, URL and trigger, and a `NavigationEnd`,
2375
- `NavigationCancel`, `NavigationError` or `NavigationSkipped` drops it back to `null` — which is
2376
- what the real router answers to a component reading it while handling a `NavigationEnd`.
2391
+ - **The navigation in flight follows the events, and a terminal event ends it _after_ delivering
2392
+ it.** `setCurrentNavigation({ extras: { state } })` puts one up and `setCurrentNavigation(null)`
2393
+ ends it; a `NavigationStart` pushed through `emitNavigation()` starts one with that event's id,
2394
+ URL and trigger, and a `NavigationEnd`, `NavigationCancel`, `NavigationError` or
2395
+ `NavigationSkipped` drops it back to `null` once its subscribers have run. Angular's doc comment
2396
+ — "the current navigation becomes to null after the NavigationEnd event is emitted" — means
2397
+ _after_ literally: the router emits the terminal event from a `tap` with the navigation still in
2398
+ flight and clears it in the `finalize` below, and `events` is a Subject, so a synchronous
2399
+ subscriber runs between the two. **A component that reads `currentNavigation()` while handling a
2400
+ `NavigationEnd` gets the navigation that just finished**, here and in production; it reads `null`
2401
+ only once `navigate()` has resolved. Probed against a real `provideRouter()` on Angular 22.
2377
2402
  - **Ordering does not matter here.** `Router` is `providedIn: 'root'` and `provideRouter()` does not
2378
2403
  re-provide the token, so the double wins in either order — and a `TestBed` without it hands out a
2379
2404
  real router, which is what `injectRouterDouble()` says by name.
@@ -2682,6 +2707,7 @@ const { fixture, component } = renderShallow(TaskListComponent, {
2682
2707
  // change an input after the first render — one setInput per key, then the wait, in one await
2683
2708
  await setInputs(fixture, { projectId: 7 }); // a name the component does not declare is refused here
2684
2709
  await setInputs(fixture, { projectId: 7 }, { label: 'the task list' }); // options go to stable()
2710
+ // a fixture whose componentType carries no ɵcmp is refused by name too, not as a bare TypeError
2685
2711
 
2686
2712
  // build a class through DI, every unprovided token auto-spied
2687
2713
  const { instance, spies } = createWithAutoSpies(CartService, {
@@ -3109,7 +3135,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
3109
3135
  | --------------------------------- | ------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
3110
3136
  | `no-expect-in-subscribe` | `error` | suggest | `expect()` inside `subscribe()` → `expectEmission` / `firstValueFrom` |
3111
3137
  | `no-object-define-property` | `error` | suggest | `Object.defineProperty` in a spec → `mockReadonlyProp` / `mockValueProp` |
3112
- | `prefer-provide-auto-spy` | `error` | — | 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 |
3138
+ | `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 |
3113
3139
  | `prefer-create-spy-from-class` | `error` | — | an object literal of 2+ `vi.fn()`s → `createSpyFromClass` (a factory's own seed is exempt) |
3114
3140
  | `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 }` |
3115
3141
  | `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 }` |
@@ -3127,6 +3153,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
3127
3153
  | `no-import-time-spread` | `error` | suggest | `export const x = [...Imported]` at module scope → a `TypeError` while the bundle loads |
3128
3154
  | `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 |
3129
3155
  | `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 |
3156
+ | `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` |
3130
3157
  | `prefer-observer-stub` | `error` | — | a hand-rolled observer global → `stubIntersectionObserver()` / `stubResizeObserver()` / `stubMutationObserver()`; the manual save-and-restore goes too, `restoreMockedProps()` runs the undo |
3131
3158
  | `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 |
3132
3159
  | `no-passthrough-console-spy` | `error` | suggest | `vi.spyOn(console, m)` nothing gives an implementation — it calls through and prints → `installConsoleSpies()` + `consoleXSpy`, or `.mockImplementation(() => undefined)` |
@@ -3145,8 +3172,8 @@ blanket downgrade so those keep their severity; do not copy the two names into a
3145
3172
  | `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()` — a no-op here, so the spec silently asserts on post-mutation state |
3146
3173
  | `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 |
3147
3174
 
3148
- Thirty-seven rules, **every one an `error` since 4.0.0 except `prefer-render-shallow`,
3149
- `no-stub-class-double`, `no-structural-double` and `no-instance-lifecycle-spy`**; three fix on their own, twelve offer suggestions. Thirty-four are syntactic; `no-private-member-access`, `no-mistyped-use-value` and
3175
+ Thirty-eight rules, **every one an `error` since 4.0.0 except `prefer-render-shallow`,
3176
+ `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
3150
3177
  `no-unknown-use-value-key` read types, and all three report nothing at all without `parserOptions.project` / `projectService`
3151
3178
  rather than guessing. `no-compile-components` waits the same way for a fact no file holds — which
3152
3179
  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
@@ -3390,6 +3417,7 @@ packages, which a subpath export can never be.
3390
3417
  | `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) |
3391
3418
  | `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) |
3392
3419
  | `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) |
3420
+ | `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) |
3393
3421
  | `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` |
3394
3422
  | `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) |
3395
3423
  | `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
@@ -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-seven rules in `vitest-auto-spy/eslint-plugin` (three `--fix`, twelve suggestions, four of them for a suite mid-migration off jasmine), `setupAutoSpy()` — with `preset: 'strict'` for every guard at its strictest grade
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
@@ -2417,8 +2417,20 @@ flushEffects(); // the no-fixture half: services, stores, runInInjectionContext
2417
2417
  `fixture.detectChanges()` runs a single change-detection pass and does **not** flush pending
2418
2418
  effects, so an assertion right after it reads state that has not finished computing. In a zoneless
2419
2419
  app the state that matters is signal-derived and effects are what move it forward. `stable` does
2420
- both, in the right order; `flushEffects` prefers `TestBed.tick()` (Angular ≥ 20) and falls back to
2421
- `ApplicationRef.tick()`.
2420
+ both, in the right order; `flushEffects` is `TestBed.tick()`, run inside the `NgZone`.
2421
+
2422
+ **Both work under zone.js, and the zone is why the tick is wrapped.** The name says zoneless and the
2423
+ helpers are written for it, but nothing in them is — `stable` and `setInputs` are the same two lines
2424
+ in a zone-based suite. `TestBed.createComponent` builds the component inside `ngZone.run(…)`, so an
2425
+ `effect()` its constructor registers records the zone's inner zone; a tick started from a test body
2426
+ runs in the runner's zone, hops back to run a **dirty** effect, and leaving that hop reports the zone
2427
+ stable mid-tick. `provideZoneChangeDetection()` — which the Angular CLI's unit-test builder installs
2428
+ for every suite that loads zone.js — answers that with `ApplicationRef._tick()`, guarded against its
2429
+ own scheduler but not against the tick already on the stack, which is
2430
+ `NG0101: ApplicationRef.tick is called recursively`. Running the tick inside the zone removes the hop.
2431
+ Measured on an Angular 22 suite of 1771 spec files: 451 `componentRef.setInput` calls rewritten to
2432
+ `setInputs` turned **57 green files red** on `NG0101`, and none of them after the fix. Under zoneless
2433
+ `NgZone` is a `NoopNgZone` whose `run` is a straight call, so it costs nothing there.
2422
2434
 
2423
2435
  The wait is bounded: `stable` gives the fixture **2000 ms** and then throws the cause, instead of
2424
2436
  letting the runner report a 5 s file-level timeout that names neither the helper nor the fixture.
@@ -3336,7 +3348,7 @@ import autoSpy from 'vitest-auto-spy/eslint-plugin';
3336
3348
  export default [{ files: ['**/*.spec.ts'], ...autoSpy.configs.recommended }];
3337
3349
  ```
3338
3350
 
3339
- **Every rule is an `error` since 4.0.0, bar four.** The config used to grade them `error` / `warn` /
3351
+ **Every rule is an `error` since 4.0.0, bar five.** The config used to grade them `error` / `warn` /
3340
3352
  `off`, which chose for you how much each finding mattered; a `warn` in a repository that does not read
3341
3353
  lint output is `off` with extra noise, and which findings block a merge is one line of config either
3342
3354
  way. To turn one down, spread the rule map as well — a bare `rules` key beside the spread config
@@ -3392,51 +3404,52 @@ object of `vi.fn()`s is perfectly reasonable in application code. Flat config on
3392
3404
  `.eslintrc` `plugins: ['…']` form resolves names to `eslint-plugin-*` packages, which a subpath
3393
3405
  export can never be.
3394
3406
 
3395
- | Rule | Recommended | Fix | Flags |
3396
- | --------------------------------- | :---------: | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
3397
- | `prefer-provide-auto-spy` | `error` | — | a hand-rolled `useValue`, `useFactory` **or** `useClass` → `provideAutoSpy(Class)` / `provideAutoSpyForToken(TOKEN)` |
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-observer-stub` | `error` | — | a hand-rolled `IntersectionObserver` / `ResizeObserver` / `MutationObserver` written into a global → `stubIntersectionObserver()` and friends, whose undo `restoreMockedProps()` already runs |
3418
- | `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 |
3419
- | `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)` |
3420
- | `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 |
3421
- | `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 |
3422
- | `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` |
3423
- | `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** |
3424
- | `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 |
3425
- | `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 |
3426
- | `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 |
3427
- | `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 |
3428
- | `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 |
3429
- | `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` |
3430
- | `jasmine-namespace-without-entry` | `error` | — | `.and` / `.calls` / `.withArgs` on a library spy in a file that installs the compatibility layer nowhere |
3431
- | `no-jasmine-globals` | `error` | — | `jasmine.*`, `spyOn(` / `spyOnProperty(` / `spyOnAllFunctions(` / `fail(` / `pending(`, `.withContext(` — none of them exist under Vitest |
3432
- | `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()`, which is a no-op here → take the copy at call time |
3433
- | `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 |
3407
+ | Rule | Recommended | Fix | Flags |
3408
+ | --------------------------------- | :---------: | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
3409
+ | `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 |
3410
+ | `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 |
3411
+ | `no-stub-class-double` | `warn` | — | a class whose fields are `vi.fn()`s → `createSpyFromClass` / `provideAutoSpy`, the stub class deleted |
3412
+ | `no-structural-double` | `warn` | — | an object of `vi.fn()`s bound to a name declared `{ load: Mock }` → `createAutoMock<T>()` |
3413
+ | `prefer-inject-spy` | `error` | suggest | `vi.spyOn(TestBed.inject(X), 'm')`, in one step or two → `injectSpy(X).m` |
3414
+ | `no-object-define-property` | `error` | suggest | `Object.defineProperty` in a spec → `mockReadonlyProp` / `mockValueProp` |
3415
+ | `no-expect-in-subscribe` | `error` | suggest | `expect()` inside a `subscribe()` callback → `expectEmission` / `firstValueFrom` |
3416
+ | `no-shared-module-level-mock` | `error` | — | an **exported** value holding `vi.fn()`s → export a factory that returns it |
3417
+ | `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 |
3418
+ | `prefer-as-spy` | `error` | `--fix` | `TestBed.inject(X) as Spy<X>` → `asSpy<X>(TestBed.inject(X))`, import and all |
3419
+ | `no-done-callback` | `error` | — | `it('x', (done) => …)` → `async` + an awaited assertion, and `done.fail(…)` at the call site |
3420
+ | `no-floating-assertion` | `error` | — | `expect()` in a `.then()` nobody awaits → `expect(await promise)` |
3421
+ | `no-bare-called-with` | `error` | — | `spy.m.calledWith(1);` as a statement of its own — a stub nobody continued, asserting nothing |
3422
+ | `no-overridden-provider` | `error` | suggest | two providers for one token in one array → the earlier one never runs; the exact duplicate can be deleted |
3423
+ | `no-inject-before-override` | `error` | — | `TestBed.inject()` / `injectSpy()` / `renderShallow()` in a hook, in a suite that still calls `override*` |
3424
+ | `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` |
3425
+ | `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 |
3426
+ | `no-import-time-spread` | `error` | suggest | `export const x = [...Imported]` at module scope → a `TypeError` while the bundle loads |
3427
+ | `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 |
3428
+ | `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 |
3429
+ | `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` |
3430
+ | `prefer-observer-stub` | `error` | — | a hand-rolled `IntersectionObserver` / `ResizeObserver` / `MutationObserver` written into a global → `stubIntersectionObserver()` and friends, whose undo `restoreMockedProps()` already runs |
3431
+ | `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 |
3432
+ | `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)` |
3433
+ | `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 |
3434
+ | `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 |
3435
+ | `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` |
3436
+ | `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** |
3437
+ | `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 |
3438
+ | `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 |
3439
+ | `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 |
3440
+ | `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 |
3441
+ | `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 |
3442
+ | `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` |
3443
+ | `jasmine-namespace-without-entry` | `error` | — | `.and` / `.calls` / `.withArgs` on a library spy in a file that installs the compatibility layer nowhere |
3444
+ | `no-jasmine-globals` | `error` | — | `jasmine.*`, `spyOn(` / `spyOnProperty(` / `spyOnAllFunctions(` / `fail(` / `pending(`, `.withContext(` — none of them exist under Vitest |
3445
+ | `no-save-arguments-by-value` | `error` | — | `spy.calls.saveArgumentsByValue()`, which is a no-op here → take the copy at call time |
3446
+ | `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
3447
 
3435
3448
  Every message ends with a link to the matching [recipe](#how-to-mock): a rule that only says
3436
3449
  "don't" moves the problem rather than solving it. Rules travel with the API they recommend, so they
3437
3450
  are versioned together and stop being re-written in every project that installs the package.
3438
3451
 
3439
- **Three of the thirty-seven fix on their own, twelve offer suggestions**, and the split is not about how hard
3452
+ **Four of the thirty-eight fix on their own, thirteen offer suggestions**, and the split is not about how hard
3440
3453
  the rewrite is. `no-mocked-for-spy` touches a _declaration_: get it wrong and the file stops
3441
3454
  compiling, which is the loudest, cheapest failure there is — so `--fix` rewrites the type, adds
3442
3455
  `import type { Spy } from 'vitest-auto-spy'` and drops the `Mocked` import once nothing else uses
@@ -3529,6 +3542,17 @@ line named after construction. It is weighed against the tests that share its se
3529
3542
  its block. Measured over an Angular suite of 1771 spec files: 569 findings in 540 of them, 515 still carrying the
3530
3543
  `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
3544
 
3545
+ `prefer-set-inputs` reports a run of `fixture.componentRef.setInput('name', value)` on one fixture.
3546
+ Angular checks that name against nothing: an input the component does not declare answers with an
3547
+ `NG0303` on the console and **no change at all**, so the spec fails several lines later, on state
3548
+ nothing moved. `setInputs(fixture, { … })` resolves every key against the compiled definition before
3549
+ it writes the first one and types the value — on the 1771-file suite, rewriting the 650 calls it can
3550
+ rewrite turned 72 drifted fixtures into compile errors across 21 files. It reports 504 times in 140
3551
+ files there, and the edit is a **suggestion**: `setInputs` awaits `stable()`, whose `TestBed.tick()`
3552
+ re-enters the tick zone.js schedules for itself, and accepting all 451 of them turned 57 of 105 green
3553
+ files red on `NG0101: ApplicationRef.tick is called recursively`. That is also why it ships as a
3554
+ `warn`: the finding is a fact, the repair is a migration a project takes file by file.
3555
+
3532
3556
  `no-compile-components` says nothing until `['error', { builder: 'inline-resources' }]`
3533
3557
  states that the builder inlines `templateUrl` / `styleUrls`; then `compileComponents()` is a promise
3534
3558
  already settled, and a suggestion deletes it together with the `async` of a hook that awaits nothing
@@ -3584,7 +3608,7 @@ package's own — it needs its ESLint integration switched on.
3584
3608
  ### WebStorm and the other JetBrains IDEs
3585
3609
 
3586
3610
  No plugin to install: WebStorm, IntelliJ IDEA Ultimate, PhpStorm, PyCharm Professional and RubyMine
3587
- all run ESLint natively, so the thirty-seven rules appear inline, in the **Problems** tool window, and
3611
+ all run ESLint natively, so the thirty-eight rules appear inline, in the **Problems** tool window, and
3588
3612
  under **Code → Inspect Code** for the whole project.
3589
3613
 
3590
3614
  ```js
@@ -3601,7 +3625,7 @@ has supported flat config since 2023.3); scope the block to spec files yourself;
3601
3625
  the fixes and suggestions live.
3602
3626
 
3603
3627
  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-seven rules, in Kotlin, in step with the TypeScript ones.
3628
+ and then keep a second copy of thirty-eight rules, in Kotlin, in step with the TypeScript ones.
3605
3629
 
3606
3630
  ### VS Code, Cursor, Windsurf, VSCodium
3607
3631
 
@@ -1,4 +1,4 @@
1
- import { flushEffects } from './chunk-GNJLDNJS.js';
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';
@@ -246,22 +246,22 @@ function createUrlState(initial) {
246
246
  }
247
247
  };
248
248
  }
249
- function followEvent(event, inFlight, navigationOf) {
250
- if (event instanceof NavigationStart) {
251
- inFlight.set(
252
- navigationOf({
253
- id: event.id,
254
- initialUrl: serializer.parse(event.url),
255
- extractedUrl: serializer.parse(event.url),
256
- trigger: event.navigationTrigger ?? "imperative"
257
- })
258
- );
259
- return event.id;
260
- }
261
- if (event instanceof NavigationEnd || event instanceof NavigationCancel || event instanceof NavigationError || event instanceof NavigationSkipped) {
262
- inFlight.set(null);
249
+ function startEvent(event, inFlight, navigationOf) {
250
+ if (!(event instanceof NavigationStart)) {
251
+ return void 0;
263
252
  }
264
- return void 0;
253
+ inFlight.set(
254
+ navigationOf({
255
+ id: event.id,
256
+ initialUrl: serializer.parse(event.url),
257
+ extractedUrl: serializer.parse(event.url),
258
+ trigger: event.navigationTrigger ?? "imperative"
259
+ })
260
+ );
261
+ return event.id;
262
+ }
263
+ function endsNavigation(event) {
264
+ return event instanceof NavigationEnd || event instanceof NavigationCancel || event instanceof NavigationError || event instanceof NavigationSkipped;
265
265
  }
266
266
  function buildNavigation(state, id, named) {
267
267
  return {
@@ -334,8 +334,11 @@ function createRouterDouble(init = {}) {
334
334
  if (next instanceof NavigationEnd) {
335
335
  state.set(next.urlAfterRedirects);
336
336
  }
337
- navigationId = followEvent(next, inFlight, navigationOf) ?? navigationId;
337
+ navigationId = startEvent(next, inFlight, navigationOf) ?? navigationId;
338
338
  events.next(next);
339
+ if (endsNavigation(next)) {
340
+ inFlight.set(null);
341
+ }
339
342
  }
340
343
  };
341
344
  doubles2.set(router, double);
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-BoA6j-74.js';
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';
package/dist/angular.js CHANGED
@@ -2,7 +2,7 @@ import { registerMockAdapter, getMockAdapter, emissionTimeout, getObservableSupp
2
2
  export { setEmissionTimeout } from './shared-state.js';
3
3
  import { vi, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
4
4
  import { TestBed, getTestBed } from '@angular/core/testing';
5
- import { DOCUMENT, isStandalone, Component, Pipe, Directive, Input, Output, Injector, runInInjectionContext, NO_ERRORS_SCHEMA, untracked, isSignal, signal, ɵSIGNAL as _SIGNAL, computed, InjectionToken, model, EventEmitter, input } from '@angular/core';
5
+ import { DOCUMENT, isStandalone, Component, Pipe, Directive, Input, Output, Injector, runInInjectionContext, NO_ERRORS_SCHEMA, untracked, NgZone, isSignal, signal, ɵSIGNAL as _SIGNAL, computed, InjectionToken, model, EventEmitter, input } from '@angular/core';
6
6
  import { By } from '@angular/platform-browser';
7
7
  import { ReplaySubject } from 'rxjs';
8
8
 
@@ -3326,7 +3326,9 @@ function runEffect(effectRef) {
3326
3326
  var setTimer = globalThis.setTimeout.bind(globalThis);
3327
3327
  var clearTimer = globalThis.clearTimeout.bind(globalThis);
3328
3328
  function flushEffects() {
3329
- TestBed.tick();
3329
+ TestBed.inject(NgZone).run(() => {
3330
+ TestBed.tick();
3331
+ });
3330
3332
  }
3331
3333
  async function stable(fixture, options = {}) {
3332
3334
  flushEffects();
@@ -3359,7 +3361,10 @@ function unstableError(timeout, label) {
3359
3361
 
3360
3362
  // src/lib/set-inputs.ts
3361
3363
  function inputNames(component) {
3362
- const definition2 = Reflect.get(component, "\u0275cmp");
3364
+ const definition2 = typeof component === "function" ? Reflect.get(component, "\u0275cmp") : void 0;
3365
+ if (!definition2) {
3366
+ throw missingDefinitionError(component);
3367
+ }
3363
3368
  const declared = Object.entries(definition2.inputs);
3364
3369
  const names = new Map(declared.map(([publicName, [property]]) => [property, publicName]));
3365
3370
  declared.forEach(([publicName]) => names.set(publicName, publicName));
@@ -3368,6 +3373,15 @@ function inputNames(component) {
3368
3373
  function quote(names) {
3369
3374
  return names.map((name) => `'${name}'`).join(", ");
3370
3375
  }
3376
+ function missingDefinitionError(component) {
3377
+ const name = typeof component === "function" ? component.name : String(component);
3378
+ return new Error(
3379
+ withDocs(
3380
+ `[vitest-auto-spy] setInputs: ${name} carries no \u0275cmp, so there are no inputs to set. The fixture must be a @Component's: a @Directive (\u0275dir) takes its inputs through the host element that applies it, a @Pipe (\u0275pipe) has none, and a class Angular never compiled carries no definition at all. For \`undefined\`, import the component from its own file rather than from a barrel.`,
3381
+ DOCS_LINKS.angular
3382
+ )
3383
+ );
3384
+ }
3371
3385
  function unknownInputsError(component, unknown, declared) {
3372
3386
  const known = declared.length > 0 ? `Its inputs are ${quote(declared)}.` : "It declares no inputs at all.";
3373
3387
  return new Error(
@@ -3,7 +3,7 @@ export { ArgCaptor, AsInstances, AssertMockedOptions, ConstructorMock, Construct
3
3
  export { A as AutoMockConfiguration, a as AutoSpyDefaultEntry, C as CallbackSubscribable, E as EmissionObserver, b as EmissionOptions, c as EmissionSource, S as SubscribableLike, d as autoMocked, e as clearAutoSpyDefaults, f as createAutoMock, g as expectCompletion, h as expectEmission, i as expectEmissions, j as expectError, k as expectNoEmission, r as registerAutoSpyDefaults, s as setEmissionTimeout } from './expect-emission-CmN9ADyN.js';
4
4
  export { c as createFunctionSpy, d as describeDuplicateCopies, g as getPackageCopies } from './package-identity-lm-gqZAR.js';
5
5
  export { A as AccessorImplementations, O as OutsideHookReaction, R as RestoreProp, c as countMockedProps, m as mockAccessorsProp, a as mockReadonlyProp, b as mockReadonlyPropGetter, d as mockValueProp, r as reportPropsOutsideHooks, e as restoreMockedProps } from './prop-mock-DnYNBNJL.js';
6
- export { 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, 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-BoA6j-74.js';
6
+ export { 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, 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';
7
7
  import 'vitest';
8
8
  import '@angular/core';
9
9
  import '@angular/core/testing';
@@ -1,5 +1,5 @@
1
- import { stable, flushEffects } from './chunk-GNJLDNJS.js';
2
- export { flushEffects, stable } from './chunk-GNJLDNJS.js';
1
+ import { stable, flushEffects } from './chunk-M5BULP7X.js';
2
+ export { flushEffects, stable } from './chunk-M5BULP7X.js';
3
3
  import './chunk-M6VMOOZ2.js';
4
4
  export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, stubConstructor, withOverrides } from './chunk-JOI3BLCL.js';
5
5
  import { currentSpecFile, mockValueProp } from './chunk-N3T7KWWA.js';
@@ -414,7 +414,10 @@ function runEffect(effectRef) {
414
414
 
415
415
  // src/lib/set-inputs.ts
416
416
  function inputNames(component) {
417
- const definition = Reflect.get(component, "\u0275cmp");
417
+ const definition = typeof component === "function" ? Reflect.get(component, "\u0275cmp") : void 0;
418
+ if (!definition) {
419
+ throw missingDefinitionError(component);
420
+ }
418
421
  const declared = Object.entries(definition.inputs);
419
422
  const names = new Map(declared.map(([publicName, [property]]) => [property, publicName]));
420
423
  declared.forEach(([publicName]) => names.set(publicName, publicName));
@@ -423,6 +426,15 @@ function inputNames(component) {
423
426
  function quote2(names) {
424
427
  return names.map((name) => `'${name}'`).join(", ");
425
428
  }
429
+ function missingDefinitionError(component) {
430
+ const name = typeof component === "function" ? component.name : String(component);
431
+ return new Error(
432
+ withDocs(
433
+ `[vitest-auto-spy] setInputs: ${name} carries no \u0275cmp, so there are no inputs to set. The fixture must be a @Component's: a @Directive (\u0275dir) takes its inputs through the host element that applies it, a @Pipe (\u0275pipe) has none, and a class Angular never compiled carries no definition at all. For \`undefined\`, import the component from its own file rather than from a barrel.`,
434
+ DOCS_LINKS.angular
435
+ )
436
+ );
437
+ }
426
438
  function unknownInputsError(component, unknown, declared) {
427
439
  const known = declared.length > 0 ? `Its inputs are ${quote2(declared)}.` : "It declares no inputs at all.";
428
440
  return new Error(
@@ -1,10 +1,13 @@
1
1
  import { withDocs, DOCS_LINKS } from './chunk-Q477VHHF.js';
2
+ import { NgZone } from '@angular/core';
2
3
  import { TestBed } from '@angular/core/testing';
3
4
 
4
5
  var setTimer = globalThis.setTimeout.bind(globalThis);
5
6
  var clearTimer = globalThis.clearTimeout.bind(globalThis);
6
7
  function flushEffects() {
7
- TestBed.tick();
8
+ TestBed.inject(NgZone).run(() => {
9
+ TestBed.tick();
10
+ });
8
11
  }
9
12
  async function stable(fixture, options = {}) {
10
13
  flushEffects();
package/dist/cli.js CHANGED
@@ -2453,7 +2453,7 @@ function checkForeignPragma(graph) {
2453
2453
  }
2454
2454
 
2455
2455
  // src/cli/checks/export-map.generated.ts
2456
- var EXPORT_MAP_VERSION = "5.10.0";
2456
+ var EXPORT_MAP_VERSION = "5.11.0";
2457
2457
  var ENTRY_SPECIFIERS = "vitest-auto-spy vitest-auto-spy/bun vitest-auto-spy/bun-angular vitest-auto-spy/node vitest-auto-spy/rstest vitest-auto-spy/rxjs vitest-auto-spy/console vitest-auto-spy/dom-stubs vitest-auto-spy/diagnostics vitest-auto-spy/jasmine vitest-auto-spy/jasmine-compat vitest-auto-spy/observer-spy vitest-auto-spy/angular vitest-auto-spy/angular-http vitest-auto-spy/angular-router vitest-auto-spy/signal-forms vitest-auto-spy/nestjs vitest-auto-spy/react vitest-auto-spy/vue vitest-auto-spy/svelte vitest-auto-spy/setup vitest-auto-spy/zone vitest-auto-spy/eslint-plugin";
2458
2458
  var EXPORTED_BY = {
2459
2459
  AccessorImplementations: "0 1 2 3 4 12 17 18 19",