vitest-auto-spy 5.36.0 → 5.38.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/CHANGELOG.md CHANGED
@@ -10,6 +10,115 @@ The latest released version here must always match the one published on
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [5.38.0] - 2026-09-26
14
+
15
+ ### Added
16
+
17
+ - **`doctor` notes `mock-reset-config-unread`.** A `no-redundant-mock-reset` entry whose options name
18
+ a `configFile` and no flag beside it, where that file's default export is a call to anything but
19
+ `defineConfig` / `defineProject` — a project factory such as `createProjectConfig({ alias })`, or
20
+ `mergeConfig(base, …)`. The rule reads the file as text, so it never sees the flags that call sets:
21
+ up to Vitest 4 it stays silent on every redundant reset, from Vitest 5 it assumes `clearMocks` on
22
+ even where the factory turns it off, and nothing says so. The note names the callee and the fix —
23
+ write the flags beside `configFile`, which win over the file. Only a literal `configFile` is read,
24
+ resolved against the repository root. The rule itself still reports nothing about its config.
25
+
26
+ ### Changed
27
+
28
+ - **The block `init` writes into `AGENTS.md` / `CLAUDE.md` / `GEMINI.md` follows the 5.35.0 split.**
29
+ It called the package's `AGENTS.md` "the authoritative reference"; since 5.35.0 that file is a short
30
+ map and the reference is `agent-docs/*.md`. The block now says what the skill stub already did: read
31
+ the map whole, then only the topic files it names, and look a failure up with
32
+ `grep -n -F '<error text>' node_modules/vitest-auto-spy/agent-docs/errors.md`. The rxjs line names
33
+ the setup file "or a module / `.d.ts` it loads", since under `@angular/build:unit-test` 22.2 the
34
+ import may sit in a shared module the setup file reaches and the types in a `.d.ts`. Both land in
35
+ one change, so the block's `sha=` moves once: **`init --check` reports the root stubs as changed
36
+ until `npx vitest-auto-spy init` runs again**, and that run rewrites only the block. The pointer in
37
+ the glob-scoped rule files and the skill stub are unchanged. The hand-written snippets on the agents
38
+ page and in the README say the same as the block.
39
+
40
+ ### Fixed
41
+
42
+ - **`createSpyFromInstance` recognises happy-dom's `MediaQueryList` as a live DOM/BOM object.** What
43
+ `matchMedia()` returns belongs to a class no global names, built on happy-dom's private
44
+ `EventTarget`, so discovery ran unrestricted over it without the warning that asks for
45
+ `onlyMethodsToSpyOn`. An event target built on the `EventTarget` the engine built `Node` on now
46
+ counts too; a user class over the global `EventTarget`, or over a polyfill's, still does not.
47
+
48
+ ### Docs
49
+
50
+ - **`no-redundant-mock-reset`: only the runner's flags count.** `AGENTS.md` §16, the README and the
51
+ skill now say what the rule reference already did: `setupAutoSpy({ restoreMocks: true })` restores
52
+ in an `afterEach`, after the test, and is not the runner's `restoreMocks`, so it is never a reason
53
+ to pass the rule `{ restoreMocks: true }`. The README also names the factory / `mergeConfig` case,
54
+ and the Russian rule page gains both paragraphs.
55
+ - **`TestBed.createDirective` on the Angular page.** The zoneless-waiting, `hostElement` and
56
+ directive-host sections say that `stable`, `hostElement` and `toHaveDirectiveApplied` take Angular
57
+ 22.2's `DirectiveFixture`, and the `toHaveDirectiveApplied` section that the fixture's root element
58
+ is searched too, so a `hostDirectives` entry of the component under test counts. `AGENTS.md` §13
59
+ already said so; the site did not.
60
+ - **What `vitest-auto-spy/angular` re-exports from the core.** The Angular page, `AGENTS.md` §13, the
61
+ skill and the README say it outright: the `Spy<T>` type (5.35.0), the `mock*Prop` helpers with
62
+ `restoreMockedProps` / `countMockedProps`, the `expectEmission` family and `registerAutoSpyDefaults`
63
+ / `clearAutoSpyDefaults` — and nothing else. `createSpyFromClass`, `createMock`, `createAutoMock`,
64
+ `spyOnVoidMethod`, `spyOnOwnMethod`, `stubConstructor`, `asInstance` and the rest stay on
65
+ `vitest-auto-spy`, so a spec that needs one keeps a second import line rather than folding it into
66
+ the `/angular` one. The `/angular-http` pages no longer say every other subpath re-exports the core.
67
+ - **`toHaveDirectiveApplied` as the guard for a `hostDirectives` attribute.** `AGENTS.md` §13 and the
68
+ Angular page (English and Russian) show the matcher asserting the host directive itself, with no
69
+ selector, next to the attribute check that stays green when the entry is dropped and the attribute
70
+ is also written statically — and that it exists only after `registerDirectiveMatchers()` from
71
+ `vitest-auto-spy/angular/matchers` runs in the setup file.
72
+ - **The `createSpyFromInstance` refusal of a non-configurable, non-enumerable member** (`AGENTS.md`
73
+ §17) says that members the same call replaced before it stay spied until `restoreSpiedInstance` or
74
+ the `setupAutoSpy()` sweep puts them back.
75
+
76
+ ## [5.37.0] - 2026-09-26
77
+
78
+ ### Added
79
+
80
+ - **`stable()` takes Angular 22.2's `TestBed.createDirective` fixture.** It only ever awaited
81
+ `whenStable()`, but was typed on `ComponentFixture`, so `await stable(TestBed.createDirective(Dir,
82
+ { tagName }))` failed with `TS2345` and specs fell back to `detectChanges()`, which flushes no
83
+ effects. It now accepts any fixture with `whenStable()`; nothing in the signature names
84
+ `DirectiveFixture`, so Angular 20 and 21 compile as before.
85
+
86
+ ### Fixed
87
+
88
+ - **`toHaveDirectiveApplied` finds a directive on the element a fixture is rooted at.** It searched
89
+ with `queryAll`, which never returns the root, so on a `TestBed.createDirective` fixture — rooted at
90
+ the element that carries the directive — it failed with `it is not on any element of this fixture`
91
+ and advised `createDirectiveHost`. The root is searched too; on a `ComponentFixture` that also counts
92
+ a `hostDirectives` entry of the component under test.
93
+ - **`vitest-auto-spy/eslint-plugin` is assignable to ESLint's own `Plugin` and flat-config types.**
94
+ `plugins: { 'vitest-auto-spy': autoSpy }` in a typed `eslint.config.ts`, or in
95
+ `new Linter().verify(code, [...])`, failed with `TS2322` / `TS2769`: `configs.*.plugins` was
96
+ `Record<string, unknown>` and each rule's `create` was typed on the library's own context. The
97
+ configs now name the plugin type, and a published rule is typed the way ESLint calls it. The plugin
98
+ still imports nothing from ESLint.
99
+ - **`strayListeners` no longer blames a spec file for jsdom's own wiring, and no longer strips it.**
100
+ Under Vitest 5, assigning a window global in jsdom or happy-dom also writes it to the environment's
101
+ real window, so the tracking wrapper sat where jsdom's internals call it too. The first
102
+ `querySelectorAll`, `matches` or `getComputedStyle` on a document makes jsdom's selector engine
103
+ register nine focus, keyboard and mouse listeners on the window, once; the file that happened to
104
+ run it failed under `onStrayListeners: 'throw'` with `left 9 window/document listeners attached`,
105
+ and the sweep took them off, which broke `:focus` and `:hover` matching for every later file in
106
+ the worker. The same held under a VM pool (`vmThreads`, the Analog default) on any Vitest version.
107
+ A registration whose first caller frame is inside `jsdom`, `happy-dom` or `@asamuzakjp/*` is now
108
+ the environment's and is neither counted nor swept — which also covers the `resize` listener
109
+ happy-dom's `matchMedia` adds per `change` listener. A `document.querySelectorAll('*')` warm-up
110
+ placed before `setupAutoSpy()` to work around this can go.
111
+ - **Stray timer and listener reports quote the caller instead of `__VITEST_HELPER__`.** When every
112
+ frame of a registration was inside dependencies, the first one shown was the `vi.defineHelper`
113
+ wrapper around the tracker. It is now skipped like the library's own frames.
114
+
115
+ ### Docs
116
+
117
+ - **`no-redundant-mock-reset` says what it cannot read.** A runner config assembled by a factory or
118
+ a `mergeConfig` from another module sets `clearMocks` in a file the rule never opens; the docs now
119
+ say to write the flag beside `configFile`. The Vitest 5 default for a config that leaves it out
120
+ arrived in 5.35.0.
121
+
13
122
  ## [5.36.0] - 2026-09-26
14
123
 
15
124
  **Why upgrade.** Eleven entries that crashed on import under `bun:test` load now, and every spec
@@ -5374,7 +5483,9 @@ moved under 1 %: `/angular` +192 B (+1.0 %) for the override queue and the HTTP
5374
5483
  This copy ships with the package and carries 5.x only. Releases before 5.0.0 are in the
5375
5484
  [full changelog](https://github.com/ASDAlexey/vitest-auto-spy/blob/master/CHANGELOG.md).
5376
5485
 
5377
- [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.36.0...HEAD
5486
+ [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.38.0...HEAD
5487
+ [5.38.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.37.0...v5.38.0
5488
+ [5.37.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.36.0...v5.37.0
5378
5489
  [5.36.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.35.0...v5.36.0
5379
5490
  [5.35.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.34.0...v5.35.0
5380
5491
  [5.34.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.33.0...v5.34.0
package/README.md CHANGED
@@ -1035,29 +1035,30 @@ error tsconfig-glob-matches-nothing libs/users/tsconfig.spec.json
1035
1035
  3 errors, 4 warnings, 1 note
1036
1036
  ```
1037
1037
 
1038
- | Check | What it finds |
1039
- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1040
- | `tsconfig-glob-matches-nothing` | An `include` pattern that matches no file — so it type-checks nothing; `info` when nothing beside the config is there to miss yet |
1041
- | `tsconfig-file-missing` | A `files` entry naming a file that is gone |
1042
- | `spec-imported-by-non-spec` | A production module importing a `*.spec.ts` |
1043
- | `spec-exports-fixture` | A spec importing another spec, whose hooks then run in a foreign file |
1044
- | `foreign-runner-pragma` | `@jest-environment` left in a spec, which Vitest never reads |
1045
- | `dead-runner-config` | `jest.config.*` / `karma.conf.*` for a runner that is not installed |
1046
- | `orphan-runner-file` | A setup file only that dead config referenced |
1047
- | `angular-build-splitting-off` | `@angular/build` in `[22.1.5, 22.1.7)` — the OOM under `--coverage` |
1048
- | `analog-behind-angular-build` | `@analogjs/vite-plugin-angular` below 2.7.5 next to `@angular/build` 22.2+ — `TypeError: cache.has is not a function` at startup |
1049
- | `coverage-all-removed` | `coverage.all` in a config, on a Vitest that stopped reading the key |
1050
- | `coverage-include-misses-bundle` | A `coverage.include` of sources only, in a runner config over a bundle |
1051
- | `coverage-include-recompiles-globs` | A coverage scope large enough that `picomatch` recompiling it per file costs more than the coverage. Info |
1052
- | `vitest-5-removed` | What Vitest 5 removed — `vitest/reporters`-style imports, `.sequential`, `--outputJson`/`--compare`; error on 5, note on 4. Also `poolOptions` (warning, ignored since 4) |
1053
- | `vitest-5-deprecated` | `experimental_clearCache` / `experimental_parseSpecifications` on Vitest 5, which nothing warns about at run time. Info |
1054
- | `vitest-5-clear-mocks` | `clearMocks: true` restating the Vitest 5 default, or a Vitest 4 suite that never decided and will have mocks cleared after the upgrade. Info |
1055
- | `vitest-5-available` | Vitest 4 with nothing holding back 5 — what it buys (−46 % v8 / −35.5 % istanbul on a 700-file Angular suite with coverage) — or what holds it back. Info |
1056
- | `fs-module-cache-not-persisted` | `fsModuleCache` on, and no CI config caches its directory — every CI run starts cold |
1057
- | `jasmine-era-project` | `jasmine-core`, `@types/jasmine`, `karma.conf.*` or `@hirez_io/observer-spy` still installed. Info, not an error |
1058
- | `no-agent-instructions` | No instruction file names the package. A note, not an error |
1059
- | `helper-from-wrong-entry` | A named import taken from an entry that does not export it — `provideAutoSpy` from the root |
1060
- | `no-unawaited-helper` | An `expectEmission` / `stable` / `flushEventLoop` call dropped as a bare statement, so nothing awaits it |
1038
+ | Check | What it finds |
1039
+ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1040
+ | `tsconfig-glob-matches-nothing` | An `include` pattern that matches no file — so it type-checks nothing; `info` when nothing beside the config is there to miss yet |
1041
+ | `tsconfig-file-missing` | A `files` entry naming a file that is gone |
1042
+ | `spec-imported-by-non-spec` | A production module importing a `*.spec.ts` |
1043
+ | `spec-exports-fixture` | A spec importing another spec, whose hooks then run in a foreign file |
1044
+ | `foreign-runner-pragma` | `@jest-environment` left in a spec, which Vitest never reads |
1045
+ | `dead-runner-config` | `jest.config.*` / `karma.conf.*` for a runner that is not installed |
1046
+ | `orphan-runner-file` | A setup file only that dead config referenced |
1047
+ | `angular-build-splitting-off` | `@angular/build` in `[22.1.5, 22.1.7)` — the OOM under `--coverage` |
1048
+ | `analog-behind-angular-build` | `@analogjs/vite-plugin-angular` below 2.7.5 next to `@angular/build` 22.2+ — `TypeError: cache.has is not a function` at startup |
1049
+ | `coverage-all-removed` | `coverage.all` in a config, on a Vitest that stopped reading the key |
1050
+ | `coverage-include-misses-bundle` | A `coverage.include` of sources only, in a runner config over a bundle |
1051
+ | `coverage-include-recompiles-globs` | A coverage scope large enough that `picomatch` recompiling it per file costs more than the coverage. Info |
1052
+ | `vitest-5-removed` | What Vitest 5 removed — `vitest/reporters`-style imports, `.sequential`, `--outputJson`/`--compare`; error on 5, note on 4. Also `poolOptions` (warning, ignored since 4) |
1053
+ | `vitest-5-deprecated` | `experimental_clearCache` / `experimental_parseSpecifications` on Vitest 5, which nothing warns about at run time. Info |
1054
+ | `vitest-5-clear-mocks` | `clearMocks: true` restating the Vitest 5 default, or a Vitest 4 suite that never decided and will have mocks cleared after the upgrade. Info |
1055
+ | `mock-reset-config-unread` | `no-redundant-mock-reset` given a `configFile` whose default export is a factory or `mergeConfig` call and no flag beside it — the rule reads none of the flags that call sets. Info |
1056
+ | `vitest-5-available` | Vitest 4 with nothing holding back 5 — what it buys (−46 % v8 / −35.5 % istanbul on a 700-file Angular suite with coverage) — or what holds it back. Info |
1057
+ | `fs-module-cache-not-persisted` | `fsModuleCache` on, and no CI config caches its directory — every CI run starts cold |
1058
+ | `jasmine-era-project` | `jasmine-core`, `@types/jasmine`, `karma.conf.*` or `@hirez_io/observer-spy` still installed. Info, not an error |
1059
+ | `no-agent-instructions` | No instruction file names the package. A note, not an error |
1060
+ | `helper-from-wrong-entry` | A named import taken from an entry that does not export it — `provideAutoSpy` from the root |
1061
+ | `no-unawaited-helper` | An `expectEmission` / `stable` / `flushEventLoop` call dropped as a bare statement, so nothing awaits it |
1061
1062
 
1062
1063
  The check that motivated the tool: a spec showing `Cannot find name 'vi'` in the editor while
1063
1064
  `tsc --noEmit` reported zero errors. A migration codemod editing `include` had eaten a `/**`,
@@ -1293,8 +1294,8 @@ Add this to the instruction file your agent actually reads — the table below s
1293
1294
 
1294
1295
  ```md
1295
1296
  When writing or fixing tests that use `vitest-auto-spy`, first read
1296
- `node_modules/vitest-auto-spy/AGENTS.md`. It is the authoritative reference for the API,
1297
- the configuration semantics and the common mistakes.
1297
+ `node_modules/vitest-auto-spy/AGENTS.md` whole — a short map — then only the topic files in
1298
+ `agent-docs/` it names for the task.
1298
1299
  ```
1299
1300
 
1300
1301
  The text is the same everywhere; only the filename changes. **Two files cover the whole field: a
@@ -1344,8 +1345,8 @@ cat >> AGENTS.md <<'MD'
1344
1345
  ## Tests that use `vitest-auto-spy`
1345
1346
 
1346
1347
  When writing or fixing tests that use `vitest-auto-spy`, first read
1347
- `node_modules/vitest-auto-spy/AGENTS.md`. It is the authoritative reference for the API,
1348
- the configuration semantics and the common mistakes.
1348
+ `node_modules/vitest-auto-spy/AGENTS.md` whole — a short map — then only the topic files in
1349
+ `agent-docs/` it names for the task.
1349
1350
  MD
1350
1351
 
1351
1352
  # 2 — CLAUDE.md: Claude Code, and GLM / Kimi running inside it. One line, no second copy to maintain
@@ -1588,6 +1589,12 @@ Node / Bun / React / Vue project pulls **neither rxjs nor Angular into its runti
1588
1589
  > `@nestjs/*`, `react`, `vue`/`pinia` and `svelte` stay your own (already-present) dev dependencies and
1589
1590
  > never reach this package's runtime bundle.
1590
1591
 
1592
+ > **`vitest-auto-spy/angular` is a companion to the root, not a copy of it.** Of the core it
1593
+ > re-exports only the `Spy<T>` type, the `mock*Prop` helpers with `restoreMockedProps`, the
1594
+ > `expectEmission` family and `registerAutoSpyDefaults`. `createSpyFromClass`, `createMock`,
1595
+ > `createAutoMock`, `spyOnVoidMethod`, `spyOnOwnMethod`, `stubConstructor`, `asInstance` and the rest
1596
+ > stay on `vitest-auto-spy`, so an Angular spec that needs one imports from both.
1597
+
1591
1598
  > **The two entries every suite loads are shipped as one module each.** Importing an entry costs
1592
1599
  > per-module loader work rather than per-byte work, and every consumer imports the root on every spec
1593
1600
  > file while every Angular consumer imports `/angular` alongside it. The root now reaches the loader
@@ -3097,7 +3104,7 @@ off, and `verifyNoPendingRequests()` runs the same check by hand.
3097
3104
 
3098
3105
  It is a separate entry because it is the only part of the package that imports `@angular/common` —
3099
3106
  an **optional** peer, so `vitest-auto-spy/angular` keeps loading in a project that does not have it.
3100
- Like `/angular-router` and unlike every other subpath, it does not re-export the core; it is a companion to
3107
+ Like `/angular-router`, it does not re-export the core; it is a companion to
3101
3108
  `vitest-auto-spy/angular`, not a replacement.
3102
3109
 
3103
3110
  ### React (Testing Library)
@@ -3972,7 +3979,7 @@ export can never be.
3972
3979
  | `prefer-settle-dynamic-import` | `error` | suggest | `await import('./thing')` — or `import('./thing').then(…)` — written in a test body or a hook to settle a module the code under test lazy-loads → `await settleDynamicImport(() => import('./thing'))`. Awaiting the specifier waits for the **module**; the handler's own continuation, the lines after _its_ `await`, is still queued behind it, so the assertion reads the state one turn early. The suggestion wraps the `import()` and imports the helper. Reported only where the innermost enclosing function is the runner's own callback, which is what exempts a `vi.mock` factory, a lazy route's `loadComponent` and a callback the spec hands to production code |
3973
3980
  | `prefer-create-mock` | `warn` | suggest | an object literal under `as SomeType` — `{ id: '1', isOffline: false } as Device`, and the `<Device>{ … }` spelling → `createMock<Device>({ … })`, which takes a `DeepPartial<Device>` and answers a `Device`. A cast asks whether the two types overlap, so it passes a key the type does not declare **and** a required field the fixture never sets; both type gates stay silent and the fixture is then spread into an expected payload. Silent on `as const`, on `as unknown` / `as any` and the double cast built from them, on a cast of anything that is not a literal, and on a literal inside one of this library's own factories. `warn`, not `error`: the repair makes a drifted fixture red on the next type-check, which is the finding and also a migration taken file by file |
3974
3981
  | `no-mock-cast` | `error` | suggest | `TestBed.inject(S).m as Mock` — a cast to Vitest's `Mock` / `MockInstance` over a **member access** → `injectSpy(S).m`, which the suggestion writes when the token is in view. `Mock` with no parameters is `Mock<any>`, so `mockReturnValue` takes anything and `toHaveBeenCalledWith` stops comparing arguments; the worse form, `(spy.m.mockReturnValue as Mock)(…)`, gets a message of its own because the cast sits on the member that installs the answer. The name has to resolve to a runner's import, so a domain `Mock` is never reported |
3975
- | `no-redundant-mock-reset` | `error` | `--fix` / suggest | a mock reset **the runner already performs** — `vi.clearAllMocks()` / `resetAllMocks()` / `restoreAllMocks()`, `x.mockClear()` / `mockReset()` / `mockRestore()` — as the first statement of a `beforeEach` no other `beforeEach` precedes, or a clear closing an `afterEach` → delete it. **Silent until** the flags are known: `['error', { clearMocks: true, restoreMocks: true, mockReset: true }]`, `{ configFile }` naming the runner config, or a `vitest.config.*` / `vite.config.*` found above the file. The flag has to match the call: `restoreMocks` alone does not make a `clearAllMocks()` dead, since it never reaches a plain `vi.fn()`. `--fix` only in a file whose only before-hook it is, a suggestion everywhere else, and **never** in a test body |
3982
+ | `no-redundant-mock-reset` | `error` | `--fix` / suggest | a mock reset **the runner already performs** — `vi.clearAllMocks()` / `resetAllMocks()` / `restoreAllMocks()`, `x.mockClear()` / `mockReset()` / `mockRestore()` — first in a `beforeEach` no other `beforeEach` precedes, or a clear closing an `afterEach` → delete it. **Silent until** the runner's flags are known: `['error', { clearMocks: true }]` and siblings, `{ configFile }`, or a `vitest.config.*` / `vite.config.*` above the file, read as text; a factory or `mergeConfig` in another module is not read through, and `setupAutoSpy({ restoreMocks })` is not the runner's flag. The flag has to match the call: `restoreMocks` never reaches a plain `vi.fn()`. `--fix` only where it is the file's only before-hook, a suggestion elsewhere, **never** in a test body |
3976
3983
  | `no-unasserted-argument` | `warn` | — | a bare `expect(spy).toHaveBeenCalled()` where the file itself says the arguments are the point: the same subject is pinned with `toHaveBeenCalledWith(…)` in another test of the file, or the test's title says `with` and its body asserts nothing else → name the arguments. Narrower than `vitest/prefer-called-with` on purpose — 175 findings against its 1941 on the same suite |
3977
3984
  | `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 |
3978
3985
  | `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` |
@@ -26,6 +26,14 @@ helper a suite names once in a setup file must not ride along with every `provid
26
26
  the diagnostics, doubles and matcher registrars left on that rule (`trackInjections` stays —
27
27
  `createWithAutoSpies` needs its module).
28
28
 
29
+ **`/angular` is not a second copy of the core.** Of the root entry it re-exports only the `Spy<T>`
30
+ type (5.35.0), the `mock*Prop` family with `restoreMockedProps` / `countMockedProps`, the
31
+ `expectEmission` family and `registerAutoSpyDefaults` / `clearAutoSpyDefaults`. Every other runtime
32
+ name — `createSpyFromClass`, `createMock`, `createAutoMock`, `createSpyFromInstance`,
33
+ `spyOnVoidMethod`, `spyOnOwnMethod`, `stubConstructor`, `asInstance`, `asSpy` — stays on
34
+ `vitest-auto-spy`, so a spec that needs one keeps a second import line; do not fold it into the
35
+ `/angular` import. `/bun-angular` differs: a runner entry, it re-exports the whole core.
36
+
29
37
  ### The same thing as fixtures — `extendWithAutoSpies` (Vitest 4.1+)
30
38
 
31
39
  The block above, written once instead of once per dependency, with the types inferred rather than
@@ -476,7 +484,7 @@ and the next line reads the settled value.
476
484
 
477
485
  Three things to know before reaching for it:
478
486
 
479
- - **It does not re-export the core** — nor does `/angular-router`; every other subpath does. It is a
487
+ - **It does not re-export the core** — nor does `/angular-router`, and `/angular` only a few names (§13). It is a
480
488
  companion to `vitest-auto-spy/angular`, which stays the import for spies, `TestBed` helpers and
481
489
  `settleResource`.
482
490
  Staying narrow is what keeps `@angular/common` — an **optional** peer — out of every other entry.
@@ -977,6 +985,7 @@ spies.get(PricingService).total.mockReturnValue(100);
977
985
  // zoneless waiting
978
986
  await stable(fixture); // flush effects, then await the fixture; fails at 2000 ms naming the cause
979
987
  await stable(fixture, { timeout: 5000, label: 'the products fixture' });
988
+ await stable(TestBed.createDirective(TooltipDirective, { tagName: 'button' })); // any fixture with whenStable()
980
989
  flushEffects(); // the no-fixture half: services, stores, runInInjectionContext
981
990
 
982
991
  // resources — one wait for httpResource(), resource() and rxResource()
@@ -1177,6 +1186,27 @@ _is_ declared; `NG0304` calls a missing directive a missing component; a bare at
1177
1186
  nothing at all). `schemas: [NO_ERRORS_SCHEMA]` next to a standalone component is a dead entry —
1178
1187
  schemas apply to a testing module's `declarations` only.
1179
1188
 
1189
+ It is also the guard for an attribute a `hostDirectives` entry puts on the component under test. An
1190
+ assertion on the attribute alone stays green when the entry is dropped and the attribute is also
1191
+ written statically, or set by something else; the fixture's root element is searched, so the entry
1192
+ itself can be asserted with no selector:
1193
+
1194
+ ```ts
1195
+ import { registerDirectiveMatchers } from 'vitest-auto-spy/angular/matchers';
1196
+
1197
+ registerDirectiveMatchers(); // once, in the setup file; nothing registers the matcher for you
1198
+
1199
+ const fixture = TestBed.createComponent(CardComponent); // hostDirectives: [TestIdDirective]
1200
+
1201
+ expect(fixture).toHaveDirectiveApplied(TestIdDirective);
1202
+ expect(hostElement(fixture).getAttribute('data-testid')).toBe('card');
1203
+ ```
1204
+
1205
+ Angular ≥ 22.2: when the directive needs no static host attribute, no `TemplateRef` (structural) and
1206
+ no sibling markup, `TestBed.createDirective(Dir, { tagName, bindings })` builds the host itself.
1207
+ `stable(fixture)`, `hostElement(fixture)` and `toHaveDirectiveApplied` take its `DirectiveFixture`;
1208
+ `setInputs` does not (no `componentRef`) — bind a signal with `inputBinding` and set it.
1209
+
1180
1210
  ### A stand-in for a child — `createComponentStub`
1181
1211
 
1182
1212
  Do not hand-write `class MockChartComponent` with a copied selector: nothing checks the copy, so a
@@ -48,7 +48,9 @@ a unit-test target, Analog below 2.7.5, `vite` below 6.4, or Node below 22.12 in
48
48
  `.node-version`, CI or a private package's `engines`. `fs-module-cache-not-persisted` warns when
49
49
  `fsModuleCache` is on and no CI config caches its directory. Renamed config keys Vitest 5 still
50
50
  honours (`experimental.fsModuleCache`, `browser.isolate`, `cache.dir`, …) are left to Vitest's own
51
- deprecation line.
51
+ deprecation line. `mock-reset-config-unread` notes a `no-redundant-mock-reset` whose `configFile` names
52
+ a config built by a factory or `mergeConfig`, with no flag beside it: the rule cannot see the flags
53
+ that call sets.
52
54
  It is worth one run after any large edit to a test suite — especially after a codemod, which is where
53
55
  the eaten glob below came from.
54
56
 
@@ -47,9 +47,9 @@ Part of the agent reference [`AGENTS.md`](../AGENTS.md), which maps every sectio
47
47
  | `Cannot read properties of undefined (reading 'name')` on `value.constructor.name`, with `createAutoMock<T>()` | `constructor` answered `undefined` on an auto-mock | upgrade — it answers `Object`, as every other double does; or seed it: `createAutoMock<T>({ constructor: … })` |
48
48
  | `Cannot redefine property: …` from a library accessor spy | the target is an ES module namespace a bundler inlined | no spy library can win this — give the code a real seam (inject it, pass it in) and spy on that |
49
49
  | `Cannot spy on this instance in place: it is not extensible` | `createSpyFromInstance` was handed a frozen or sealed object | spy it before it is frozen, or build a separate double with `createSpyFromClass` (§2) |
50
- | `createSpyFromInstance(X): no onlyMethodsToSpyOn was given for a live DOM/BOM object` | discovery is about to walk the whole prototype chain of a live `Node`, `window` or other engine-provided event target (not a user class extending `EventTarget`), engine internals included — the bare-array shorthand does not avoid this, since it merges as `methodsToSpyOn`, not `onlyMethodsToSpyOn` | list the methods the test needs: `{ onlyMethodsToSpyOn: [...] }`; for one method on a node that must keep living a DOM life, `mockValueProp` / `spyOnVoidMethod` instead (§2, live DOM node) |
50
+ | `createSpyFromInstance(X): no onlyMethodsToSpyOn was given for a live DOM/BOM object` | discovery is about to walk the whole prototype chain of a live `Node`, `window` or other engine-provided event target, happy-dom's `MediaQueryList` included (not a user class extending `EventTarget`), engine internals included — the bare-array shorthand does not avoid this, since it merges as `methodsToSpyOn`, not `onlyMethodsToSpyOn` | list the methods the test needs: `{ onlyMethodsToSpyOn: [...] }`; for one method on a node that must keep living a DOM life, `mockValueProp` / `spyOnVoidMethod` instead (§2, live DOM node) |
51
51
  | `Cannot mock the property 'X': it is not configurable` | the member was defined with `configurable: false`, so it cannot be redefined | members replaced before it are still restorable — `restoreSpiedInstance(instance)` or `restoreMockedProps()` |
52
- | `createSpyFromInstance`: `Cannot spy on 'X' in place: it is a non-configurable, non-enumerable own property` | the value can be rewritten but not made enumerable, so `resetAutoSpy` would never find the spy | spy it alone with `mockValueProp(instance, 'X', vi.fn())`, or build a double with `createSpyFromClass` |
52
+ | `createSpyFromInstance`: `Cannot spy on 'X' in place: it is a non-configurable, non-enumerable own property` | the value can be rewritten but not made enumerable, so `resetAutoSpy` would never find the spy | spy it alone with `mockValueProp(instance, 'X', vi.fn())`, or build a double with `createSpyFromClass`; members the same call replaced before `X` stay spied until `restoreSpiedInstance(instance)` or the `setupAutoSpy()` sweep |
53
53
  | `overrideComponentProvider(…): the override did not apply — C resolved T to …` | the component injects a different token, or something configured the token again after the call; the message says whether the real service or a different double won | pass the token the component actually injects (a base class, an `InjectionToken`), and override after nothing else re-configures |
54
54
  | `.value() was read while the resource is in the error state: …` | `mockResourceProp`'s double after `fail(reason)`, as a real `ResourceRef` behaves; the reason is on `cause` | branch on `hasValue()` / `status()` first, or assert with `toHaveResourceError()` (§13) |
55
55
  | `@angular/core <version> no longer carries …, which this package reads` | an Angular internal this package reads has moved; checked once per worker | nothing is fixable from a spec — report the version in the message and pin the previous Angular until a release reads the new shape (§13) |
@@ -126,7 +126,7 @@ Part of the agent reference [`AGENTS.md`](../AGENTS.md), which maps every sectio
126
126
  | `flushEventLoopUntil` timing out on the **first** such test only, the rest green | a cold dynamic `import()` outran the turn budget; later tests hit the module cache | `await settleDynamicImport(() => import('…'))` — await the module, do not count turns |
127
127
  | `Expected to be running in 'ProxyZone', but it was not found` | `zone.js/testing` patches jasmine/mocha/jest, not Vitest | `import 'vitest-auto-spy/zone'` after zone.js, with `globals: true` (§14) |
128
128
  | `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) |
129
- | `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) |
129
+ | `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] })`; on Angular ≥ 22.2, for a directive with no static host attribute, `TemplateRef` or sibling markup, `TestBed.createDirective(Dir, { tagName, bindings })` (§13) |
130
130
  | `setInputs: … declares no input named …` (with a `Did you mean` for a near typo) | the key is not an input of that component — a typo, an input renamed since, or one of its own fields | take the suggestion, or set a name the message lists; a signal the component owns is moved through the signal (§13) |
131
131
  | `setInputs: X carries no ɵcmp, so there are no inputs to set` (or `the component class is undefined`, `X is a @Pipe`) | the fixture's `componentType` is not a compiled `@Component` — a `@Pipe`, a class Angular never compiled, or `undefined` from a barrel split across chunks | pass the `@Component` class the fixture was created from; call a pipe's `transform()` directly; for `undefined` import the class from its own file (§13) |
132
132
  | `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 |
@@ -75,7 +75,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
75
75
  | `prefer-settle-dynamic-import` | `error` | suggest | `await import('./thing')` and `import('./thing').then(…)` inside an `it` / `test` body or a `beforeEach` / `beforeAll` / `afterEach` / `afterAll` hook (the `.only`, `.skip` and `.each` spellings included) → `await settleDynamicImport(() => import('./thing'))`, which is the same load plus `flushEventLoop(1)` and returns the namespace, so the destructured form keeps reading the same way. The bare `await` waits for the **module** and not for the continuation of the code that was loading it, so the assertion runs a turn early and the test is green only while that continuation is short. Syntax only. Silent wherever a function of its own sits between the runner's callback and the import — a `vi.mock` factory, a lazy route's `loadComponent` / `loadChildren`, a callback handed to production code, `settleDynamicImport`'s own `() => import(…)` — and, by the same reading, silent on a spec-local `const load = async () => { await import('…') }`, which is written identically whether the spec calls it or the code under test does |
76
76
  | `prefer-create-mock` | `warn` | suggest | an object literal under `as SomeType` — `{ id: '1', isOffline: false } as Device`, `<Device>{ … }` too → `createMock<Device>({ … })`. A cast asks whether the two types overlap rather than whether the value is one of them, so it passes a key the type does not declare (the excess-property check is skipped) **and** a required field the fixture never sets; both type gates stay silent, and the same object is then spread into the expected payload of a call assertion. Where the literal already sits in a typed slot the first repair is to delete the cast. Silent on `as const`, `as unknown` / `as any` and the double cast built from them, on a cast of anything but a literal, and on a literal inside one of this library's own factories. `warn`, because accepting the suggestion makes a drifted fixture red on the next type-check — the finding, and a migration taken file by file |
77
77
  | `no-mock-cast` | `error` | suggest | a cast to Vitest's `Mock` / `MockInstance` over a **member access** — `TestBed.inject(S).m as Mock` → `injectSpy(S).m`, which the suggestion writes whenever the token is in view (directly, or through a `const` the file settled with a `TestBed.inject`). `Mock` with no parameters is `Mock<any>`: `mockReturnValue` takes anything and `toHaveBeenCalledWith` stops comparing arguments. `(spy.m.mockReturnValue as Mock)(…)` gets its own message — the cast is on the member that installs the answer, so neither the value nor the method's return type is checked. The name must resolve to an import from `vitest` / `@rstest/core` / `bun:test` / `jest`, or to nothing at all, so a domain `Mock` is left alone |
78
- | `no-redundant-mock-reset` | `error` | `--fix` / suggest | a mock reset the runner already performs between tests — `vi.clearAllMocks()` / `resetAllMocks()` / `restoreAllMocks()`, `x.mockClear()` / `mockReset()` / `mockRestore()` — standing where nothing the file wrote ran since the runner's own: the first statement of a `beforeEach` no other `beforeEach` precedes, or a clear as the last statement of an `afterEach` → delete it, and the hook with it where that is all it held. A restore or reset in `afterEach` / `afterAll` is never reported: after the last test it is what takes a spy off before the `afterAll` hooks. **Silent** until the flags are known: options `{ clearMocks, restoreMocks, mockReset }` (or `{ configFile }` naming the runner config) first, then a `vitest.config.*` / `vite.config.*` searched upwards from the file and read as text. Flag matches call, never family — `restoreMocks` walks only the spies `vi.spyOn` installed. `--fix` only in a file whose only before-hook it is; a suggestion otherwise; never inside a test body |
78
+ | `no-redundant-mock-reset` | `error` | `--fix` / suggest | a mock reset the runner already performs between tests — `vi.clearAllMocks()` / `resetAllMocks()` / `restoreAllMocks()`, `x.mockClear()` / `mockReset()` / `mockRestore()` — standing where nothing the file wrote ran since the runner's own: the first statement of a `beforeEach` no other `beforeEach` precedes, or a clear as the last statement of an `afterEach` → delete it, and the hook with it where that is all it held. A restore or reset in `afterEach` / `afterAll` is never reported: after the last test it is what takes a spy off before the `afterAll` hooks. **Silent** until the flags are known: options `{ clearMocks, restoreMocks, mockReset }` (or `{ configFile }` naming the runner config) first, then a `vitest.config.*` / `vite.config.*` searched upwards from the file and read as text; a config that leaves `clearMocks` out counts it on from Vitest 5 (its default). A config built by a factory or `mergeConfig` from another module is not read through — write its flags beside `configFile` (`doctor` notes the case as `mock-reset-config-unread`). `setupAutoSpy({ restoreMocks: true })` is not the runner's `restoreMocks` (it restores in an `afterEach`, after the test) — never pass the rule a flag only the setup file sets. Flag matches call, never family — `restoreMocks` walks only the spies `vi.spyOn` installed. `--fix` only in a file whose only before-hook it is; a suggestion otherwise; never inside a test body |
79
79
  | `no-unasserted-argument` | `warn` | — | a bare `expect(spy).toHaveBeenCalled()` on evidence from the file: the same subject asserted with `toHaveBeenCalledWith(…)` in another test, or a title containing `with` over a body whose every assertion is a bare call → `toHaveBeenCalledWith(…)` / `mustBeCalledWith(…)`. `not.toHaveBeenCalled()` and the counting matchers are never reported |
80
80
  | `no-compile-components` | `error` | suggest | `compileComponents()` under a builder that inlines `templateUrl` / `styleUrls` — a promise already settled → delete it, and the `async` of a hook that awaits nothing else. **Silent until** `['error', { builder: 'inline-resources' }]`: under a JIT setup that loads resources at run time the call is load-bearing. **Keep the call for a component whose template holds a `@defer` block** — that ships async class metadata, which `TestBed` resolves in this very call whatever the builder did, and dropping it fails the test with `has unresolved metadata`; the rule cannot see another file's template, so list those classes in `{ ignoreComponents: ['CardComponent'] }` (a spec naming one is left alone) or keep a `// eslint-disable-next-line vitest-auto-spy/no-compile-components -- @defer: async class metadata` |
81
81
  | `no-sync-testbed-await` | `error` | suggest | `await` on a TestBed call that answers the TestBed or a fixture — `configureTestingModule`, `override*`, `resetTestingModule`, `createComponent`, `getLastFixture`, through `TestBed`, `getTestBed()`, a chain of those, or a name holding one → drop the `await`, and the `async` of a hook that then awaits nothing else; the suggestion does both. Reads **no types**, so it reports without `parserOptions.project`, where `@typescript-eslint/await-thenable` cannot. `inject` and `runInInjectionContext` are never reported: each answers whatever the token or the callback holds, which can be a promise |
@@ -202,7 +202,9 @@ setupAutoSpy({ strayListeners: true }); // wrap addEventListener, sweep what the
202
202
 
203
203
  The split is the one `pruneMockRegistry` uses: a `beforeAll` marks the listeners already on
204
204
  `window`/`document` — registered while the module graph was evaluated, a framework's one-time
205
- initialisation — and the `afterAll` takes off everything added since. The pieces are exported too:
205
+ initialisation — and the `afterAll` takes off everything added since. A registration jsdom or
206
+ happy-dom makes itself (jsdom wires nine focus, keyboard and mouse listeners the first time a
207
+ document is queried) is the environment's: neither counted nor swept. The pieces are exported too:
206
208
  `trackStrayListeners()` (idempotent, returns the undo), `baselineStrayListeners()`,
207
209
  `removeStrayListeners()` (returns how many), `countStrayListeners()` (throws before
208
210
  `trackStrayListeners()` has run, like the timer counter) and `describeStrayListeners()` — each
@@ -1,12 +1,12 @@
1
1
  import { pendingRequestsReport, assertAngularInternals, instrumentTestBed, onTestingModuleConfigured, onComponentCreated, beforeTestBedReset, verifyOnTeardown } from './chunk-GKSVYPZQ.js';
2
2
  export { disableTestBedDiagnostics, enableTestBedDiagnostics, formatSpecTiming, getTestBedTiming, instrumentTestBed, reportSpecTiming } from './chunk-GKSVYPZQ.js';
3
- import { failOnUnspiedProvider } from './chunk-VWNFNWTG.js';
3
+ import { failOnUnspiedProvider } from './chunk-2KBRDNIB.js';
4
4
  import './chunk-GG7BQKEK.js';
5
5
  import './chunk-FWXDSPZD.js';
6
- import './chunk-F4URI3UE.js';
6
+ import './chunk-6KP5QMMV.js';
7
7
  import './chunk-FOL5RH22.js';
8
8
  import './chunk-PC3FAZT3.js';
9
- import './chunk-LA2CMIPM.js';
9
+ import './chunk-RYFF55PV.js';
10
10
  import { isAutoSpyLike } from './chunk-Q7L4AWI7.js';
11
11
  import { count, taskName } from './chunk-M3NTXLZG.js';
12
12
  import { angularPendingRequests, angularShadowedProviders, angularDeadSchemas, angularNgModuleScopesAuto } from './chunk-5FQZGJLJ.js';
@@ -1,11 +1,11 @@
1
1
  import { describeInstance } from './chunk-BS432KB2.js';
2
- import { useVitestAdapter } from './chunk-YWSOJH5C.js';
3
- import './chunk-GTDJ22IQ.js';
2
+ import { useVitestAdapter } from './chunk-YNXQ2WFH.js';
3
+ import './chunk-B6C3K27C.js';
4
4
  import './chunk-YHDBRBGU.js';
5
5
  import './chunk-TXDBDMIL.js';
6
6
  import './chunk-FOL5RH22.js';
7
7
  import './chunk-PC3FAZT3.js';
8
- import { createFunctionSpy } from './chunk-LA2CMIPM.js';
8
+ import { createFunctionSpy } from './chunk-RYFF55PV.js';
9
9
  import './chunk-Q7L4AWI7.js';
10
10
  import './chunk-M3NTXLZG.js';
11
11
  import { angularDialog } from './chunk-5FQZGJLJ.js';
@@ -17,11 +17,14 @@ function rootOf(received) {
17
17
  candidate
18
18
  ) : void 0;
19
19
  }
20
+ function matching(root, predicate) {
21
+ return predicate(root) ? [root, ...root.queryAll(predicate)] : root.queryAll(predicate);
22
+ }
20
23
  function diagnose(directive, selector, root) {
21
- const matching = selector === void 0 ? [] : root.queryAll(By.css(selector));
24
+ const elements = selector === void 0 ? [] : matching(root, By.css(selector));
22
25
  const where = selector === void 0 ? "" : ` on '${selector}'`;
23
26
  const prefix = `[vitest-auto-spy] expected ${directive.name} to be applied${where}`;
24
- if (selector !== void 0 && matching.length === 0) {
27
+ if (selector !== void 0 && elements.length === 0) {
25
28
  return withDocs(
26
29
  `${prefix}, but no element matches that selector.
27
30
  Run fixture.detectChanges() before asserting, and check the selector against the template.`,
@@ -47,14 +50,14 @@ function directiveResult(received, directive, selector) {
47
50
  if (!root) {
48
51
  throw new Error(
49
52
  withDocs(
50
- `[vitest-auto-spy] toHaveDirectiveApplied: expected a ComponentFixture or a DebugElement, received ${describeReceived(received)}.
53
+ `[vitest-auto-spy] toHaveDirectiveApplied: expected a ComponentFixture, a DirectiveFixture or a DebugElement, received ${describeReceived(received)}.
51
54
  Pass the fixture itself, or fixture.debugElement \u2014 not fixture.nativeElement.`,
52
55
  angularDirectiveApplied
53
56
  )
54
57
  );
55
58
  }
56
- const withDirective = root.queryAll(By.directive(directive));
57
- const applied = selector === void 0 ? withDirective.length > 0 : root.queryAll(By.css(selector)).some((element) => element.providerTokens.includes(directive));
59
+ const withDirective = matching(root, By.directive(directive));
60
+ const applied = selector === void 0 ? withDirective.length > 0 : matching(root, By.css(selector)).some((element) => element.providerTokens.includes(directive));
58
61
  return {
59
62
  pass: applied,
60
63
  message: () => applied ? `[vitest-auto-spy] expected ${directive.name} not to be applied, but it is on ${count(withDirective.length, "element")}.` : diagnose(directive, selector, root)
@@ -1,6 +1,6 @@
1
1
  import { describeInstance } from './chunk-BS432KB2.js';
2
2
  import { angularInternalsError } from './chunk-GG7BQKEK.js';
3
- import { createFunctionSpy } from './chunk-LA2CMIPM.js';
3
+ import { createFunctionSpy } from './chunk-RYFF55PV.js';
4
4
  import './chunk-Q7L4AWI7.js';
5
5
  import { count } from './chunk-M3NTXLZG.js';
6
6
  import { angularRouteDouble, angularRouterInject, angularRouterEvents, angularLocationDouble, angularRouteWiring, angularRouterDouble } from './chunk-5FQZGJLJ.js';
package/dist/angular.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { u as AngularTokenProvider, A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, E as ElementConstructor, N as NativeElementHolder, 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 hostElement, k as injectSpy, p as provideAutoSpy, v as provideAutoSpyForToken, q as queryElement, r as renderShallow, l as runEffect, s as setInputs, m as settleResource, n as stable, t as trackEffectRuns, o as trackRecomputations } from './track-signal-runs-BuJk_Yi8.js';
1
+ export { u as AngularTokenProvider, A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, E as ElementConstructor, N as NativeElementHolder, 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 hostElement, k as injectSpy, p as provideAutoSpy, v as provideAutoSpyForToken, q as queryElement, r as renderShallow, l as runEffect, s as setInputs, m as settleResource, n as stable, t as trackEffectRuns, o as trackRecomputations } from './track-signal-runs-C7SrW3Ub.js';
2
2
  import { o as DeepPartial, C as ClassType, m as ClassSpyConfiguration, S as Spy, s as OnlyMethodKeysOf, h as AddSpyMethodsByReturnTypes, r as NotAPublicKey } from './types-BVKEkhWj.js';
3
3
  import { InjectionToken, Provider, Type, WritableSignal, Signal } from '@angular/core';
4
4
  import { A as AutoMockConfiguration, D as DeepReadonly } from './expect-emission-wUaoy0NW.js';
@@ -3,7 +3,7 @@ export { AdoptMockOptions, AdoptedMock, ArgCaptor, AsInstances, AssertMockedOpti
3
3
  export { A as AutoMockConfiguration, a as AutoMocked, b as AutoSpyDefaultEntry, C as CallbackSubscribable, E as EmissionObserver, c as EmissionOptions, d as EmissionSource, S as SubscribableLike, e as autoMocked, f as clearAutoSpyDefaults, g as createAutoMock, h as expectAllEmissions, i as expectCompletion, j as expectEmission, k as expectEmissions, l as expectError, m as expectNoEmission, r as registerAutoSpyDefaults, s as setEmissionTimeout } from './expect-emission-wUaoy0NW.js';
4
4
  export { c as createFunctionSpy, d as describeDuplicateCopies, g as getPackageCopies } from './package-identity-5pFBRWW9.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-8Ps9usRa.js';
6
- export { A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, E as ElementConstructor, N as NativeElementHolder, 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 hostElement, k as injectSpy, p as provideAutoSpy, q as queryElement, r as renderShallow, l as runEffect, s as setInputs, m as settleResource, n as stable, t as trackEffectRuns, o as trackRecomputations } from './track-signal-runs-BuJk_Yi8.js';
6
+ export { A as AngularValueProvider, a as AutoSpiedInstance, C as ComponentInputs, b as CreateWithAutoSpiesOptions, E as ElementConstructor, N as NativeElementHolder, 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 hostElement, k as injectSpy, p as provideAutoSpy, q as queryElement, r as renderShallow, l as runEffect, s as setInputs, m as settleResource, n as stable, t as trackEffectRuns, o as trackRecomputations } from './track-signal-runs-C7SrW3Ub.js';
7
7
  import 'vitest';
8
8
  import '@angular/core';
9
9
  import '@angular/core/testing';
@@ -1,19 +1,19 @@
1
- import { createSpyForToken } from './chunk-464WKNEQ.js';
2
- export { injectSpy, provideAutoSpy } from './chunk-VWNFNWTG.js';
1
+ import { createSpyForToken } from './chunk-2TZCJ3MM.js';
2
+ export { injectSpy, provideAutoSpy } from './chunk-2KBRDNIB.js';
3
3
  import { stable, flushEffects } from './chunk-DBC6526L.js';
4
4
  export { flushEffects, stable } from './chunk-DBC6526L.js';
5
5
  import { angularInternalsError } from './chunk-GG7BQKEK.js';
6
6
  import './chunk-QDBRZQJO.js';
7
- export { adoptMock, asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createLog, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectAllEmissions, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, outOfType, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, spyOnOwnMethod, spyOnVoidMethod, stubConstructor, withOverrides } from './chunk-GAE5EGO7.js';
7
+ export { adoptMock, asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createLog, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectAllEmissions, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, outOfType, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, spyOnOwnMethod, spyOnVoidMethod, stubConstructor, withOverrides } from './chunk-5TYNQH5Q.js';
8
8
  import { mockValueProp } from './chunk-FWXDSPZD.js';
9
9
  export { countMockedProps, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockValueProp, reportPropsOutsideHooks, restoreMockedProps } from './chunk-FWXDSPZD.js';
10
- import { closestName } from './chunk-F4URI3UE.js';
11
- export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-F4URI3UE.js';
10
+ import { closestName } from './chunk-6KP5QMMV.js';
11
+ export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-6KP5QMMV.js';
12
12
  import { fakeClockBacklog } from './chunk-W5EXSI22.js';
13
13
  import './chunk-TXDBDMIL.js';
14
14
  import './chunk-FOL5RH22.js';
15
15
  import './chunk-PC3FAZT3.js';
16
- export { createFunctionSpy, errorHandler } from './chunk-LA2CMIPM.js';
16
+ export { createFunctionSpy, errorHandler } from './chunk-RYFF55PV.js';
17
17
  import { libraryWarn } from './chunk-Q7L4AWI7.js';
18
18
  import { displayPath, count } from './chunk-M3NTXLZG.js';
19
19
  import { angularEffects, angularTrackRuns, bunAngular, angularUnreadResource, angularCreateWithAutoSpies, angularResources, angularTypedElements, angularInputs, angularKeepTemplate } from './chunk-5FQZGJLJ.js';
package/dist/bun.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import './chunk-QDBRZQJO.js';
2
- export { adoptMock, asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createLog, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectAllEmissions, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, outOfType, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, spyOnOwnMethod, spyOnVoidMethod, stubConstructor, withOverrides } from './chunk-GAE5EGO7.js';
2
+ export { adoptMock, asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createLog, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectAllEmissions, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, outOfType, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, spyOnOwnMethod, spyOnVoidMethod, stubConstructor, withOverrides } from './chunk-5TYNQH5Q.js';
3
3
  export { countMockedProps, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockValueProp, reportPropsOutsideHooks, restoreMockedProps } from './chunk-FWXDSPZD.js';
4
- export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-F4URI3UE.js';
4
+ export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-6KP5QMMV.js';
5
5
  import './chunk-W5EXSI22.js';
6
6
  import './chunk-TXDBDMIL.js';
7
7
  import './chunk-FOL5RH22.js';
8
8
  import './chunk-PC3FAZT3.js';
9
- export { createFunctionSpy, errorHandler } from './chunk-LA2CMIPM.js';
9
+ export { createFunctionSpy, errorHandler } from './chunk-RYFF55PV.js';
10
10
  import './chunk-Q7L4AWI7.js';
11
11
  import './chunk-M3NTXLZG.js';
12
12
  import './chunk-5FQZGJLJ.js';
@@ -1,5 +1,5 @@
1
1
  import { currentSpecFile } from './chunk-FWXDSPZD.js';
2
- import { createSpyFromClass } from './chunk-F4URI3UE.js';
2
+ import { createSpyFromClass } from './chunk-6KP5QMMV.js';
3
3
  import { isAutoSpyLike, misconfigurationThrows, reportMisconfiguration } from './chunk-Q7L4AWI7.js';
4
4
  import { angularInjectSpyReal } from './chunk-5FQZGJLJ.js';
5
5
  import { withDocs } from './chunk-ENCEEH23.js';
@@ -1,4 +1,4 @@
1
- import { createSpyFromClass, createAutoMock } from './chunk-F4URI3UE.js';
1
+ import { createSpyFromClass, createAutoMock } from './chunk-6KP5QMMV.js';
2
2
  import { trackInjectionsUntracked } from './chunk-5FQZGJLJ.js';
3
3
  import { withDocs } from './chunk-ENCEEH23.js';
4
4
 
@@ -1,9 +1,9 @@
1
1
  import { mockValueProp, mockAccessorsProp } from './chunk-FWXDSPZD.js';
2
- import { resolveConfiguration, mergeAutoSpyDefaults, resolveReadGuard, mergeMethodNames, getCallableMemberNames, createTrackedPropSpy, applyConfiguredReturns, createSpyFromClass, warnOnUnknownMethods, warnOnAccessorNamingAMethod, resolveAccessors, createAccessorsSpies, ownerOf, createProxyPropStore, storeWriteTraps, describeStoredProp, hasStoredProp, isProtocolKey, isDeletedProp, readStoredAccessor, NOT_STORED, disposeAutoSpy, writeStoredValue } from './chunk-F4URI3UE.js';
2
+ import { resolveConfiguration, mergeAutoSpyDefaults, resolveReadGuard, mergeMethodNames, getCallableMemberNames, createTrackedPropSpy, applyConfiguredReturns, createSpyFromClass, warnOnUnknownMethods, warnOnAccessorNamingAMethod, resolveAccessors, createAccessorsSpies, ownerOf, createProxyPropStore, storeWriteTraps, describeStoredProp, hasStoredProp, isProtocolKey, isDeletedProp, readStoredAccessor, NOT_STORED, disposeAutoSpy, writeStoredValue } from './chunk-6KP5QMMV.js';
3
3
  import { unpatchedSetTimeout, unpatchedClearTimeout, fakeClockAdvice, fakeClockBacklog } from './chunk-W5EXSI22.js';
4
4
  import { redefineFailure } from './chunk-FOL5RH22.js';
5
5
  import { DISPOSE } from './chunk-PC3FAZT3.js';
6
- import { defineHelper, adoptFunctionSpy, reinstallDispatch, serializeValue, resolveUnstubbedGuard, createFunctionSpy } from './chunk-LA2CMIPM.js';
6
+ import { defineHelper, adoptFunctionSpy, reinstallDispatch, serializeValue, resolveUnstubbedGuard, createFunctionSpy } from './chunk-RYFF55PV.js';
7
7
  import { isMarkedMock, reportMisconfiguration, DEEP_CHILDREN } from './chunk-Q7L4AWI7.js';
8
8
  import { count, displayPath } from './chunk-M3NTXLZG.js';
9
9
  import { moduleMocksAssert, moduleMocksNoOp, narrow, adoptMock, observableFailures, strictModePassthrough, createSpyFromClassReturns, captureArg, eventLoopUntil, mockConstructor, createSpyFromClassLiveDomNode } from './chunk-5FQZGJLJ.js';
@@ -79,12 +79,15 @@ function looksLikeLiveHostObject(instance) {
79
79
  continue;
80
80
  }
81
81
  if (constructor.name === "EventTarget") {
82
- return engineLevel;
82
+ return engineLevel || isEnginePrivateEventTarget(constructor);
83
83
  }
84
84
  engineLevel ||= Reflect.get(globalThis, constructor.name) === constructor;
85
85
  }
86
86
  return false;
87
87
  }
88
+ function isEnginePrivateEventTarget(constructor) {
89
+ return typeof Node !== "undefined" && constructor !== Reflect.get(globalThis, "EventTarget") && Reflect.getPrototypeOf(Node.prototype) === Reflect.get(constructor, "prototype");
90
+ }
88
91
  function warnOnUndiscriminatedHostDiscovery(label, listedAdditions) {
89
92
  const additions = listedAdditions ? "The methods listed (a bare array is methodsToSpyOn) are spied in addition to that discovery, not instead of it. " : "";
90
93
  reportMisconfiguration(
@@ -1,5 +1,5 @@
1
1
  import { DISPOSE } from './chunk-PC3FAZT3.js';
2
- import { defaultStrict, resolveUnstubbedGuard, seedReturnValue, createFunctionSpy } from './chunk-LA2CMIPM.js';
2
+ import { defaultStrict, resolveUnstubbedGuard, seedReturnValue, createFunctionSpy } from './chunk-RYFF55PV.js';
3
3
  import { runClearHook, runConfigReset, reportMisconfiguration, AUTO_SPY_MARK, markAsMock, isMarkedMock, readDeepChildren } from './chunk-Q7L4AWI7.js';
4
4
  import { autoMockHeldBack, createSpyFromClassInstanceMethods, createSpyFromClassAccessors, createSpyFromClassReturns, createSpyFromClassLazySpies } from './chunk-5FQZGJLJ.js';
5
5
  import { withDocs } from './chunk-ENCEEH23.js';
@@ -1,6 +1,6 @@
1
1
  import { guardAccessorSpies, spyOnAccessorByRedefine } from './chunk-TXDBDMIL.js';
2
2
  import { DISPOSE } from './chunk-PC3FAZT3.js';
3
- import { isFastSpy, FAST_SPY_BRAND, isThenable } from './chunk-LA2CMIPM.js';
3
+ import { isFastSpy, FAST_SPY_BRAND, isThenable } from './chunk-RYFF55PV.js';
4
4
  import { setSharedHelperSink, hooksOf } from './chunk-Q7L4AWI7.js';
5
5
  import { mockConstructor } from './chunk-5FQZGJLJ.js';
6
6
  import { withDocs } from './chunk-ENCEEH23.js';
@@ -768,11 +768,15 @@ function installSettledResultsPolyfill(mockFn) {
768
768
  // src/lib/stack-frames.ts
769
769
  var FOREIGN_FRAME = /node_modules|node:internal|\(node:/;
770
770
  var LIBRARY_FRAME = /[/\\]vitest-auto-spy[/\\]dist[/\\]|[/\\]\.vite[/\\]deps[^/\\]*[/\\]vitest-auto-spy[^/\\]*\.js/;
771
+ var HELPER_FRAME = /\b__VITEST_HELPER__\b/;
772
+ function isLibraryFrame(frame) {
773
+ return LIBRARY_FRAME.test(frame) || HELPER_FRAME.test(frame);
774
+ }
771
775
  function stackFrames(stack) {
772
776
  return String(stack).split("\n").filter((line) => /^\s*at\s/.test(line)).map((line) => line.trim());
773
777
  }
774
778
  function ownFrames(frames, limit) {
775
- const outside = frames.filter((frame) => !LIBRARY_FRAME.test(frame));
779
+ const outside = frames.filter((frame) => !isLibraryFrame(frame));
776
780
  const own = outside.filter((frame) => !FOREIGN_FRAME.test(frame));
777
781
  const shown = [own, outside].find((candidates) => candidates.length > 0) ?? frames;
778
782
  return shown.slice(0, limit);
@@ -1,4 +1,4 @@
1
- import { createRunnerMockAdapter } from './chunk-GTDJ22IQ.js';
1
+ import { createRunnerMockAdapter } from './chunk-B6C3K27C.js';
2
2
  import { SWEEP_SENTINEL } from './chunk-YHDBRBGU.js';
3
3
  import { hasMockAdapter, registerMockAdapter } from './shared-state.js';
4
4
  import { vi } from 'vitest';
package/dist/cli.js CHANGED
@@ -536,6 +536,7 @@ var DOCTOR_CHECKS = [
536
536
  "fs-module-cache-not-persisted",
537
537
  "helper-from-wrong-entry",
538
538
  "jasmine-era-project",
539
+ "mock-reset-config-unread",
539
540
  "module-mock-leak",
540
541
  "no-agent-instructions",
541
542
  "no-unawaited-helper",
@@ -3421,7 +3422,7 @@ function checkForeignPragma(graph) {
3421
3422
  }
3422
3423
 
3423
3424
  // src/cli/checks/export-map.generated.ts
3424
- var EXPORT_MAP_VERSION = "5.35.0";
3425
+ var EXPORT_MAP_VERSION = "5.37.0";
3425
3426
  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/diagnostics vitest-auto-spy/angular/doubles vitest-auto-spy/angular/matchers 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 vitest-auto-spy/perf-reporter";
3426
3427
  var EXPORTED_BY = {
3427
3428
  AccessorImplementations: "0 1 2 3 4 12 20 21 22",
@@ -3701,6 +3702,7 @@ var EXPORTED_BY = {
3701
3702
  PendingRequestsOptions: "13",
3702
3703
  PerTestHandle: "23",
3703
3704
  PlatformOverrides: "14",
3705
+ PluginRule: "25",
3704
3706
  PropStubValue: "0 1 2 3 4 20 21 22",
3705
3707
  PrototypePollutionReaction: "23",
3706
3708
  provideActivatedRoute: "17",
@@ -4058,6 +4060,73 @@ function checkJasmineEra(profile) {
4058
4060
  }
4059
4061
  ];
4060
4062
  }
4063
+ var RULE_KEY = /["'][\w/@-]*\/no-redundant-mock-reset["']\s*:\s*\[/g;
4064
+ var CONFIG_FILE = /\bconfigFile\s*:\s*(["'`])([^"'`]+)\1/g;
4065
+ var OPTION_FLAG = /\b(?:clearMocks|mockReset|restoreMocks)\s*:/g;
4066
+ var ANY_FLAG = /\b(?:clearMocks|mockReset|restoreMocks)\b/;
4067
+ var DEFAULT_CALL = /\b(?:export\s+default|module\.exports\s*=)\s*([$A-Z_a-z][\w$.]*)\s*(?:<[^>]*>)?\s*\(/;
4068
+ var READABLE = /* @__PURE__ */ new Set(["defineConfig", "defineProject"]);
4069
+ function closingBracket(text, spans, open) {
4070
+ let depth = 0;
4071
+ for (let index = open; index < text.length; index += 1) {
4072
+ const span = spans.find((candidate) => candidate.start === index);
4073
+ if (span !== void 0) {
4074
+ index = span.end - 1;
4075
+ continue;
4076
+ }
4077
+ const char = text.charAt(index);
4078
+ depth += char === "[" ? 1 : char === "]" ? -1 : 0;
4079
+ if (depth === 0) {
4080
+ return index + 1;
4081
+ }
4082
+ }
4083
+ return text.length;
4084
+ }
4085
+ function inCode(spans, match, base) {
4086
+ return !isInsideLiteral(spans, base + match.index);
4087
+ }
4088
+ function unflaggedConfigFiles(text) {
4089
+ const spans = literalSpans(text);
4090
+ const found = [];
4091
+ for (const match of text.matchAll(RULE_KEY)) {
4092
+ if (spans.find((span) => span.start <= match.index && match.index < span.end)?.start !== match.index) {
4093
+ continue;
4094
+ }
4095
+ const open = match.index + match[0].length - 1;
4096
+ const options = text.slice(open, closingBracket(text, spans, open));
4097
+ const configFile = [...options.matchAll(CONFIG_FILE)].find((entry) => inCode(spans, entry, open))?.[2];
4098
+ const flagged = [...options.matchAll(OPTION_FLAG)].some((entry) => inCode(spans, entry, open));
4099
+ if (configFile !== void 0 && !flagged) {
4100
+ found.push(configFile);
4101
+ }
4102
+ }
4103
+ return found;
4104
+ }
4105
+ function unreadableCallee(configText) {
4106
+ if (ANY_FLAG.test(configText)) {
4107
+ return void 0;
4108
+ }
4109
+ const callee = DEFAULT_CALL.exec(configText)?.[1];
4110
+ return callee === void 0 || READABLE.has(callee) ? void 0 : callee;
4111
+ }
4112
+ function checkMockResetConfig(profile, graph) {
4113
+ const findings = [];
4114
+ for (const [file, text] of graph.texts) {
4115
+ for (const configFile of unflaggedConfigFiles(text)) {
4116
+ const callee = unreadableCallee(readTextFile(resolve(profile.cwd, configFile)) ?? "");
4117
+ if (callee !== void 0) {
4118
+ findings.push({
4119
+ check: "mock-reset-config-unread",
4120
+ severity: "info",
4121
+ file,
4122
+ message: `\`no-redundant-mock-reset\` reads \`${configFile}\`, whose default export is a \`${callee}(\u2026)\` call: the rule reads the file as text, finds no \`clearMocks\` / \`mockReset\` / \`restoreMocks\`, and decides on the runner's defaults instead of what \`${callee}\` sets.`,
4123
+ fix: "Write the flags that config ends up with beside `configFile` in the rule options, e.g. `{ configFile, clearMocks: true }`; a flag written there wins over the file."
4124
+ });
4125
+ }
4126
+ }
4127
+ }
4128
+ return findings;
4129
+ }
4061
4130
  var BASE_CONFIGS2 = ["ts", "mts", "cts", "js", "mjs", "cjs"].map((extension) => `vitest-base.config.${extension}`);
4062
4131
  var SHARED_BY_DEFAULT_FROM = [21];
4063
4132
  var OPTION_WINS_FROM = [22, 1];
@@ -4760,6 +4829,7 @@ function runDoctor(profile) {
4760
4829
  ...checkHelperEntry(profile, graph),
4761
4830
  ...checkUnawaitedHelper(profile, graph),
4762
4831
  ...checkModuleMockLeak(profile, graph),
4832
+ ...checkMockResetConfig(profile, graph),
4763
4833
  ...checkVitest5Removed(profile, graph),
4764
4834
  ...checkVitest5ClearMocks(profile, graph),
4765
4835
  ...checkVitest5Available(profile),
@@ -4978,23 +5048,24 @@ function rxjsBullet(profile, facts) {
4978
5048
  }
4979
5049
  const target = facts.setupFile ?? profile.setupFiles[0] ?? "the test setup file";
4980
5050
  return `- Observable spies (\`nextWith\`, \`observablePropsToSpyOn\`) need \`import 'vitest-auto-spy/rxjs'\` once, in
4981
- \`${target}\`. Without it they throw "Observable spies require rxjs".`;
5051
+ \`${target}\` or a module / \`.d.ts\` it loads. Without it they throw "Observable spies require rxjs".`;
4982
5052
  }
4983
5053
  function companionsBullet(facts) {
4984
5054
  if (facts.companions.length === 0) {
4985
5055
  return void 0;
4986
5056
  }
4987
5057
  const list = facts.companions.map((entry) => `\`${entry}\``).join(", ");
4988
- return `- Setup helpers also come from ${list}, which since 5.21.0 are separate
4989
- entries and do not re-export the core.`;
5058
+ return `- Setup helpers also come from ${list}, separate entries since
5059
+ 5.21.0 that do not re-export the core.`;
4990
5060
  }
4991
5061
  function renderBody(profile, facts = NO_FACTS) {
4992
5062
  const lines = [
4993
5063
  "## Tests that use `vitest-auto-spy`",
4994
5064
  "",
4995
- "Read `node_modules/vitest-auto-spy/AGENTS.md` before writing or fixing a spec that uses",
4996
- "`vitest-auto-spy` \u2014 it is the authoritative reference for the API, the configuration semantics",
4997
- "and the common mistakes. Where it and the code disagree, `dist/*.d.ts` wins.",
5065
+ "For a spec that uses `vitest-auto-spy`, read the short map",
5066
+ "`node_modules/vitest-auto-spy/AGENTS.md` whole, then only the `agent-docs/*.md` files it names.",
5067
+ "On a failure: `grep -n -F '<error text>' node_modules/vitest-auto-spy/agent-docs/errors.md`.",
5068
+ "Where docs and code disagree, `dist/*.d.ts` wins.",
4998
5069
  "",
4999
5070
  `- This repository imports from \`${profile.entry}\`. Each entry registers its mock adapter on`,
5000
5071
  " import, so the wrong one leaves the wrong adapter installed and the spies fail at runtime.",
@@ -5002,8 +5073,7 @@ function renderBody(profile, facts = NO_FACTS) {
5002
5073
  companionsBullet(facts),
5003
5074
  rxjsBullet(profile, facts),
5004
5075
  "- `methodsToSpyOn` **adds** to the auto-discovered prototype methods; the exhaustive whitelist is",
5005
- " `onlyMethodsToSpyOn`. For methods that live on the instance rather than the prototype, use",
5006
- " `createAutoMock<T>()`.",
5076
+ " `onlyMethodsToSpyOn`. For methods on the instance, not the prototype, use `createAutoMock<T>()`.",
5007
5077
  "- `Spy<T>` is a mapped type and drops `#private` members, so it is not assignable to `T`. Declare",
5008
5078
  " the variable as `Spy<T>`, or pass `asInstance(spy)` where the real type is required.",
5009
5079
  "- `npx vitest-auto-spy doctor` reports suite-level defects that never fail a run."
@@ -6211,7 +6281,7 @@ function nothingOverBudgetNote(gate = GATE_DEFAULTS, runFailed = false) {
6211
6281
  return `Nothing over budget: no file over its budget and no test body over ${formatMs(gate.maxTestMs)}. ${verdict}`;
6212
6282
  }
6213
6283
  var DOMINATES = 0.3;
6214
- var CONFIG_FILE = /(?:^|\/)vite(?:st)?[\w.-]*\.config\.[cm]?[jt]s$/;
6284
+ var CONFIG_FILE2 = /(?:^|\/)vite(?:st)?[\w.-]*\.config\.[cm]?[jt]s$/;
6215
6285
  var NO_ISOLATION = /\bisolate\s*:\s*false/;
6216
6286
  var JSDOM = /\benvironment\s*:\s*["'`]jsdom["'`]/;
6217
6287
  var HAPPY_DOM = /happy-dom/;
@@ -6223,7 +6293,7 @@ var LARGE_RUN_MS = 6e4;
6223
6293
  function configCode(graph) {
6224
6294
  const configs = [];
6225
6295
  for (const [file, text] of graph.texts) {
6226
- if (!CONFIG_FILE.test(file)) {
6296
+ if (!CONFIG_FILE2.test(file)) {
6227
6297
  continue;
6228
6298
  }
6229
6299
  configs.push([
package/dist/console.js CHANGED
@@ -1,10 +1,10 @@
1
- import { useVitestAdapter } from './chunk-YWSOJH5C.js';
2
- import './chunk-GTDJ22IQ.js';
1
+ import { useVitestAdapter } from './chunk-YNXQ2WFH.js';
2
+ import './chunk-B6C3K27C.js';
3
3
  import './chunk-YHDBRBGU.js';
4
4
  import './chunk-TXDBDMIL.js';
5
5
  import './chunk-FOL5RH22.js';
6
6
  import './chunk-PC3FAZT3.js';
7
- import { createFunctionSpy } from './chunk-LA2CMIPM.js';
7
+ import { createFunctionSpy } from './chunk-RYFF55PV.js';
8
8
  import './chunk-Q7L4AWI7.js';
9
9
  import './chunk-M3NTXLZG.js';
10
10
  import './chunk-5FQZGJLJ.js';
@@ -11,6 +11,7 @@ declare const plugin: Plugin.AutoSpyEslintPlugin;
11
11
  declare namespace plugin {
12
12
  export type AutoSpyEslintPlugin = Plugin.AutoSpyEslintPlugin;
13
13
  export type FlatConfig = Plugin.FlatConfig;
14
+ export type PluginRule = Plugin.PluginRule;
14
15
  export type RuleSeverity = Plugin.RuleSeverity;
15
16
  }
16
17
 
@@ -248,12 +248,24 @@ interface RuleModule {
248
248
  type RuleSeverity = 'error' | 'off' | 'warn';
249
249
  /** A flat-config object: the plugin under its name, plus the rules it turns on. */
250
250
  interface FlatConfig {
251
- plugins: Record<string, unknown>;
251
+ plugins: Record<string, AutoSpyEslintPlugin>;
252
252
  rules: Record<string, RuleSeverity>;
253
253
  }
254
+ /**
255
+ * One visitor of a published rule. A method signature is bivariant in its parameter, which lets a
256
+ * visitor typed on its selector's node fit ESLint's `(...args: any[]) => void`.
257
+ */
258
+ type PluginRuleVisitor = {
259
+ visit(node: unknown): void;
260
+ }['visit'];
261
+ /** A rule as the plugin publishes it: ESLint calls `create`, so no caller needs its context type. */
262
+ interface PluginRule {
263
+ meta: RuleModule['meta'];
264
+ create(context: never): Record<string, PluginRuleVisitor>;
265
+ }
254
266
  /** The plugin object, as ESLint consumes it. */
255
267
  interface AutoSpyEslintPlugin {
256
- rules: Record<string, RuleModule>;
268
+ rules: Record<string, PluginRule>;
257
269
  configs: {
258
270
  recommended: FlatConfig;
259
271
  strict: FlatConfig;
@@ -262,4 +274,4 @@ interface AutoSpyEslintPlugin {
262
274
  }
263
275
  declare const plugin: AutoSpyEslintPlugin;
264
276
 
265
- export { type AutoSpyEslintPlugin, type FlatConfig, type RuleSeverity, plugin as default };
277
+ export { type AutoSpyEslintPlugin, type FlatConfig, type PluginRule, type RuleSeverity, plugin as default };
package/dist/index.js CHANGED
@@ -1609,11 +1609,15 @@ function installSettledResultsPolyfill(mockFn) {
1609
1609
  // src/lib/stack-frames.ts
1610
1610
  var FOREIGN_FRAME = /node_modules|node:internal|\(node:/;
1611
1611
  var LIBRARY_FRAME = /[/\\]vitest-auto-spy[/\\]dist[/\\]|[/\\]\.vite[/\\]deps[^/\\]*[/\\]vitest-auto-spy[^/\\]*\.js/;
1612
+ var HELPER_FRAME = /\b__VITEST_HELPER__\b/;
1613
+ function isLibraryFrame(frame) {
1614
+ return LIBRARY_FRAME.test(frame) || HELPER_FRAME.test(frame);
1615
+ }
1612
1616
  function stackFrames(stack) {
1613
1617
  return String(stack).split("\n").filter((line) => /^\s*at\s/.test(line)).map((line) => line.trim());
1614
1618
  }
1615
1619
  function ownFrames(frames, limit) {
1616
- const outside = frames.filter((frame) => !LIBRARY_FRAME.test(frame));
1620
+ const outside = frames.filter((frame) => !isLibraryFrame(frame));
1617
1621
  const own = outside.filter((frame) => !FOREIGN_FRAME.test(frame));
1618
1622
  const shown = [own, outside].find((candidates) => candidates.length > 0) ?? frames;
1619
1623
  return shown.slice(0, limit);
@@ -3243,12 +3247,15 @@ function looksLikeLiveHostObject(instance) {
3243
3247
  continue;
3244
3248
  }
3245
3249
  if (constructor.name === "EventTarget") {
3246
- return engineLevel;
3250
+ return engineLevel || isEnginePrivateEventTarget(constructor);
3247
3251
  }
3248
3252
  engineLevel ||= Reflect.get(globalThis, constructor.name) === constructor;
3249
3253
  }
3250
3254
  return false;
3251
3255
  }
3256
+ function isEnginePrivateEventTarget(constructor) {
3257
+ return typeof Node !== "undefined" && constructor !== Reflect.get(globalThis, "EventTarget") && Reflect.getPrototypeOf(Node.prototype) === Reflect.get(constructor, "prototype");
3258
+ }
3252
3259
  function warnOnUndiscriminatedHostDiscovery(label, listedAdditions) {
3253
3260
  const additions = listedAdditions ? "The methods listed (a bare array is methodsToSpyOn) are spied in addition to that discovery, not instead of it. " : "";
3254
3261
  reportMisconfiguration(
package/dist/jasmine.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { enableJasmineCompat } from './chunk-3ZBRBPOL.js';
2
2
  export { enableJasmineCompat } from './chunk-3ZBRBPOL.js';
3
- import { createSpyFromClass } from './chunk-F4URI3UE.js';
4
- import { useVitestAdapter } from './chunk-YWSOJH5C.js';
5
- import './chunk-GTDJ22IQ.js';
3
+ import { createSpyFromClass } from './chunk-6KP5QMMV.js';
4
+ import { useVitestAdapter } from './chunk-YNXQ2WFH.js';
5
+ import './chunk-B6C3K27C.js';
6
6
  import './chunk-YHDBRBGU.js';
7
7
  import './chunk-TXDBDMIL.js';
8
8
  import './chunk-FOL5RH22.js';
9
9
  import './chunk-PC3FAZT3.js';
10
- import { createFunctionSpy } from './chunk-LA2CMIPM.js';
10
+ import { createFunctionSpy } from './chunk-RYFF55PV.js';
11
11
  import { reportMisconfiguration, misconfigurationThrows } from './chunk-Q7L4AWI7.js';
12
12
  import './chunk-M3NTXLZG.js';
13
13
  import { jasmineGlobals, jasmineClock, jasmineAutoSpies, jasmineTimeout } from './chunk-5FQZGJLJ.js';
package/dist/nestjs.js CHANGED
@@ -1,13 +1,13 @@
1
- import { createSpyForToken } from './chunk-464WKNEQ.js';
2
- export { trackInjections } from './chunk-464WKNEQ.js';
3
- import { createSpyFromClass } from './chunk-F4URI3UE.js';
4
- import { useVitestAdapter } from './chunk-YWSOJH5C.js';
5
- import './chunk-GTDJ22IQ.js';
1
+ import { createSpyForToken } from './chunk-2TZCJ3MM.js';
2
+ export { trackInjections } from './chunk-2TZCJ3MM.js';
3
+ import { createSpyFromClass } from './chunk-6KP5QMMV.js';
4
+ import { useVitestAdapter } from './chunk-YNXQ2WFH.js';
5
+ import './chunk-B6C3K27C.js';
6
6
  import './chunk-YHDBRBGU.js';
7
7
  import './chunk-TXDBDMIL.js';
8
8
  import './chunk-FOL5RH22.js';
9
9
  import './chunk-PC3FAZT3.js';
10
- import './chunk-LA2CMIPM.js';
10
+ import './chunk-RYFF55PV.js';
11
11
  import './chunk-Q7L4AWI7.js';
12
12
  import './chunk-M3NTXLZG.js';
13
13
  import { nestUnitRefusals } from './chunk-5FQZGJLJ.js';
package/dist/node.cjs CHANGED
@@ -1240,11 +1240,15 @@ function installSettledResultsPolyfill(mockFn) {
1240
1240
  // src/lib/stack-frames.ts
1241
1241
  var FOREIGN_FRAME = /node_modules|node:internal|\(node:/;
1242
1242
  var LIBRARY_FRAME = /[/\\]vitest-auto-spy[/\\]dist[/\\]|[/\\]\.vite[/\\]deps[^/\\]*[/\\]vitest-auto-spy[^/\\]*\.js/;
1243
+ var HELPER_FRAME = /\b__VITEST_HELPER__\b/;
1244
+ function isLibraryFrame(frame) {
1245
+ return LIBRARY_FRAME.test(frame) || HELPER_FRAME.test(frame);
1246
+ }
1243
1247
  function stackFrames(stack) {
1244
1248
  return String(stack).split("\n").filter((line) => /^\s*at\s/.test(line)).map((line) => line.trim());
1245
1249
  }
1246
1250
  function ownFrames(frames, limit) {
1247
- const outside = frames.filter((frame) => !LIBRARY_FRAME.test(frame));
1251
+ const outside = frames.filter((frame) => !isLibraryFrame(frame));
1248
1252
  const own = outside.filter((frame) => !FOREIGN_FRAME.test(frame));
1249
1253
  const shown = [own, outside].find((candidates) => candidates.length > 0) ?? frames;
1250
1254
  return shown.slice(0, limit);
@@ -2863,12 +2867,15 @@ function looksLikeLiveHostObject(instance) {
2863
2867
  continue;
2864
2868
  }
2865
2869
  if (constructor.name === "EventTarget") {
2866
- return engineLevel;
2870
+ return engineLevel || isEnginePrivateEventTarget(constructor);
2867
2871
  }
2868
2872
  engineLevel ||= Reflect.get(globalThis, constructor.name) === constructor;
2869
2873
  }
2870
2874
  return false;
2871
2875
  }
2876
+ function isEnginePrivateEventTarget(constructor) {
2877
+ return typeof Node !== "undefined" && constructor !== Reflect.get(globalThis, "EventTarget") && Reflect.getPrototypeOf(Node.prototype) === Reflect.get(constructor, "prototype");
2878
+ }
2872
2879
  function warnOnUndiscriminatedHostDiscovery(label, listedAdditions) {
2873
2880
  const additions = listedAdditions ? "The methods listed (a bare array is methodsToSpyOn) are spied in addition to that discovery, not instead of it. " : "";
2874
2881
  reportMisconfiguration(
package/dist/node.js CHANGED
@@ -1129,11 +1129,15 @@ function installSettledResultsPolyfill(mockFn) {
1129
1129
  // src/lib/stack-frames.ts
1130
1130
  var FOREIGN_FRAME = /node_modules|node:internal|\(node:/;
1131
1131
  var LIBRARY_FRAME = /[/\\]vitest-auto-spy[/\\]dist[/\\]|[/\\]\.vite[/\\]deps[^/\\]*[/\\]vitest-auto-spy[^/\\]*\.js/;
1132
+ var HELPER_FRAME = /\b__VITEST_HELPER__\b/;
1133
+ function isLibraryFrame(frame) {
1134
+ return LIBRARY_FRAME.test(frame) || HELPER_FRAME.test(frame);
1135
+ }
1132
1136
  function stackFrames(stack) {
1133
1137
  return String(stack).split("\n").filter((line) => /^\s*at\s/.test(line)).map((line) => line.trim());
1134
1138
  }
1135
1139
  function ownFrames(frames, limit) {
1136
- const outside = frames.filter((frame) => !LIBRARY_FRAME.test(frame));
1140
+ const outside = frames.filter((frame) => !isLibraryFrame(frame));
1137
1141
  const own = outside.filter((frame) => !FOREIGN_FRAME.test(frame));
1138
1142
  const shown = [own, outside].find((candidates) => candidates.length > 0) ?? frames;
1139
1143
  return shown.slice(0, limit);
@@ -2757,12 +2761,15 @@ function looksLikeLiveHostObject(instance) {
2757
2761
  continue;
2758
2762
  }
2759
2763
  if (constructor.name === "EventTarget") {
2760
- return engineLevel;
2764
+ return engineLevel || isEnginePrivateEventTarget(constructor);
2761
2765
  }
2762
2766
  engineLevel ||= Reflect.get(globalThis, constructor.name) === constructor;
2763
2767
  }
2764
2768
  return false;
2765
2769
  }
2770
+ function isEnginePrivateEventTarget(constructor) {
2771
+ return typeof Node !== "undefined" && constructor !== Reflect.get(globalThis, "EventTarget") && Reflect.getPrototypeOf(Node.prototype) === Reflect.get(constructor, "prototype");
2772
+ }
2766
2773
  function warnOnUndiscriminatedHostDiscovery(label, listedAdditions) {
2767
2774
  const additions = listedAdditions ? "The methods listed (a bare array is methodsToSpyOn) are spied in addition to that discovery, not instead of it. " : "";
2768
2775
  reportMisconfiguration(
package/dist/rstest.js CHANGED
@@ -1,12 +1,12 @@
1
- export { adoptMock, asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createLog, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectAllEmissions, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, outOfType, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, spyOnOwnMethod, spyOnVoidMethod, stubConstructor, withOverrides } from './chunk-GAE5EGO7.js';
1
+ export { adoptMock, asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createLog, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectAllEmissions, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, outOfType, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, spyOnOwnMethod, spyOnVoidMethod, stubConstructor, withOverrides } from './chunk-5TYNQH5Q.js';
2
2
  export { countMockedProps, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockValueProp, reportPropsOutsideHooks, restoreMockedProps } from './chunk-FWXDSPZD.js';
3
- export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-F4URI3UE.js';
3
+ export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-6KP5QMMV.js';
4
4
  import './chunk-W5EXSI22.js';
5
- import { createRunnerMockAdapter } from './chunk-GTDJ22IQ.js';
5
+ import { createRunnerMockAdapter } from './chunk-B6C3K27C.js';
6
6
  import './chunk-TXDBDMIL.js';
7
7
  import './chunk-FOL5RH22.js';
8
8
  import './chunk-PC3FAZT3.js';
9
- export { createFunctionSpy, errorHandler } from './chunk-LA2CMIPM.js';
9
+ export { createFunctionSpy, errorHandler } from './chunk-RYFF55PV.js';
10
10
  import './chunk-Q7L4AWI7.js';
11
11
  import './chunk-M3NTXLZG.js';
12
12
  import './chunk-5FQZGJLJ.js';
package/dist/setup.js CHANGED
@@ -1245,11 +1245,15 @@ function storagePrototype() {
1245
1245
  // src/lib/stack-frames.ts
1246
1246
  var FOREIGN_FRAME = /node_modules|node:internal|\(node:/;
1247
1247
  var LIBRARY_FRAME = /[/\\]vitest-auto-spy[/\\]dist[/\\]|[/\\]\.vite[/\\]deps[^/\\]*[/\\]vitest-auto-spy[^/\\]*\.js/;
1248
+ var HELPER_FRAME = /\b__VITEST_HELPER__\b/;
1249
+ function isLibraryFrame(frame) {
1250
+ return LIBRARY_FRAME.test(frame) || HELPER_FRAME.test(frame);
1251
+ }
1248
1252
  function stackFrames(stack) {
1249
1253
  return String(stack).split("\n").filter((line) => /^\s*at\s/.test(line)).map((line) => line.trim());
1250
1254
  }
1251
1255
  function ownFrames(frames, limit) {
1252
- const outside = frames.filter((frame) => !LIBRARY_FRAME.test(frame));
1256
+ const outside = frames.filter((frame) => !isLibraryFrame(frame));
1253
1257
  const own = outside.filter((frame) => !FOREIGN_FRAME.test(frame));
1254
1258
  const shown = [own, outside].find((candidates) => candidates.length > 0) ?? frames;
1255
1259
  return shown.slice(0, limit);
@@ -1442,12 +1446,24 @@ function recordEntry(tracking, type, listener, options) {
1442
1446
  function forgetEntries(tracking, type, listener, capture) {
1443
1447
  tracking.entries = tracking.entries.filter((entry) => entry.type !== type || entry.listener !== listener || entry.capture !== capture);
1444
1448
  }
1449
+ var OWN_MODULE_FRAME = /stray-listeners\.[jt]s/;
1450
+ var ENVIRONMENT_FRAME = /[/\\]node_modules[/\\](?:jsdom|happy-dom|@asamuzakjp[/\\][^/\\]+)[/\\]/;
1451
+ var environmentVerdicts = /* @__PURE__ */ new WeakMap();
1452
+ function isEnvironmentEntry(entry) {
1453
+ let verdict = environmentVerdicts.get(entry);
1454
+ if (verdict === void 0) {
1455
+ const caller = stackFrames(entry.trace.stack).find((frame) => !OWN_MODULE_FRAME.test(frame) && !isLibraryFrame(frame));
1456
+ verdict = caller !== void 0 && ENVIRONMENT_FRAME.test(caller);
1457
+ environmentVerdicts.set(entry, verdict);
1458
+ }
1459
+ return verdict;
1460
+ }
1445
1461
  function strayEntries(tracking) {
1446
1462
  const { baseline } = tracking;
1447
1463
  if (!baseline) {
1448
1464
  return [];
1449
1465
  }
1450
- return tracking.entries.filter((entry) => !baseline.has(entry));
1466
+ return tracking.entries.filter((entry) => !baseline.has(entry) && !isEnvironmentEntry(entry));
1451
1467
  }
1452
1468
  function installTracking({ name, target }) {
1453
1469
  const host = target;
@@ -1549,7 +1565,6 @@ function countStrayListeners(targets) {
1549
1565
  }
1550
1566
  return total;
1551
1567
  }
1552
- var OWN_MODULE_FRAME = /stray-listeners\.[jt]s/;
1553
1568
  function describeEntry(name, entry) {
1554
1569
  return { target: name, type: entry.type, ...describeOriginOf(entry, OWN_MODULE_FRAME) };
1555
1570
  }
@@ -456,8 +456,10 @@ interface StableOptions {
456
456
  * one component. On expiry this throws the cause instead, and names the two things that produce it.
457
457
  *
458
458
  * Pass `{ label }` when a spec awaits more than one fixture, so the failure says which.
459
+ *
460
+ * Any fixture with `whenStable()` is taken — Angular 22.2's `TestBed.createDirective` fixture too.
459
461
  */
460
- declare function stable(fixture: ComponentFixture<unknown>, options?: StableOptions): Promise<void>;
462
+ declare function stable(fixture: Pick<ComponentFixture<unknown>, 'whenStable'>, options?: StableOptions): Promise<void>;
461
463
 
462
464
  /**
463
465
  * `setInputs` — change a component's inputs after it is rendered, and let Angular catch up.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitest-auto-spy",
3
- "version": "5.36.0",
3
+ "version": "5.38.0",
4
4
  "description": "Auto-generate fully-typed test spies from a class — across Vitest, Bun, node:test and Rstest, with Angular/NestJS/React/Vue/Svelte recipes. Drop-in replacement for jest-auto-spies and jasmine-auto-spies.",
5
5
  "keywords": [
6
6
  "auto-mock",
@@ -110,6 +110,12 @@ describe('TaskService', () => {
110
110
  });
111
111
  ```
112
112
 
113
+ `type Spy`, `provideAutoSpy` and `injectSpy` come from `vitest-auto-spy/angular`. Of the core it
114
+ re-exports only the `Spy<T>` type, the `mock*Prop` helpers and the `expectEmission` family;
115
+ `createSpyFromClass`, `createMock`, `createAutoMock`, `spyOnVoidMethod`, `spyOnOwnMethod`,
116
+ `stubConstructor` and `asInstance` stay on `vitest-auto-spy` — keep that second import line instead
117
+ of folding the names into the `/angular` one.
118
+
113
119
  One `configureTestingModule` per `describe` — reconfiguring per `it()` recompiles the module every
114
120
  test. Use `mockSignalProp(service, 'count', 0)` for a signal on a collaborator — a member that already is
115
121
  a `signal()`, `model()`, `linkedSignal()` **or a `signal().asReadonly()` view** is written through rather
@@ -281,7 +287,7 @@ it('loads', async () => {
281
287
  | the same 100-line model literal copied into eight specs (`TS1117`) | one `createFixtureFactory<T>(defaults)`; specs call it with what they change |
282
288
  | `'params' in link` ladders, or a cast, to pick a union branch | `narrow.byKey(link, 'params')` / `narrow.observable(x)` |
283
289
  | `{ ...modelInstance, flag: true }` losing every getter | `withOverrides(modelInstance, { flag: true })` |
284
- | `NG0303` / `NG0304` / silence from a directive spec | `createDirectiveHost({ template, scope: [Module] })` |
290
+ | `NG0303` / `NG0304` / silence from a directive spec | `createDirectiveHost({ template, scope: [Module] })`; Angular ≥ 22.2, no host markup needed: `TestBed.createDirective(Dir, { tagName, bindings })`, which `stable` takes |
285
291
  | a hand-written `class MockChartComponent` restating a child's selector | `createComponentStub(ChartComponent)` (`/angular`) — selector, inputs, outputs read from `ɵcmp` |
286
292
  | a `TestingStorage` class for `localStorage` / `sessionStorage` | `stubWebStorage('localStorage', { items })` from `/dom-stubs` — `snapshot()` to assert |
287
293
  | a hand-written `MockWorker` whose `addEventListener` sets `onmessage` | `stubWorker({ respond })` from `/dom-stubs` — `last.emit(data)`, `last.messages` |
@@ -544,7 +550,7 @@ from `vitest-auto-spy/angular-router` rather than at `provideAutoSpy`.
544
550
 
545
551
  **`no-self-called-spy` reports a test that calls the spied method itself and then asserts that it was called** — `vi.spyOn(obj, 'm')`, then `obj.m(…)` written by the test body after it, then a positive `toHaveBeenCalled*`. Such a test proves that `emit` calls `emit`: delete the template binding or subscription its title names and it stays green. Drive the real trigger instead and keep the assertion. Order decides it, so a call written **before** the spy is arrangement and is never reported; neither is a negated matcher or `toHaveBeenCalledTimes(0)`, a call whose record a `mockClear` / `vi.clearAllMocks()` drops before the assertion reads it, an assertion whose arguments the call did not pass, a spy installed in a hook, or a call made from inside a callback.
546
552
 
547
- **`no-redundant-mock-reset` reports a mock reset in a hook that the runner already performs** — `vi.clearAllMocks()` / `vi.resetAllMocks()` / `vi.restoreAllMocks()` and the per-mock `mockClear()` / `mockReset()` / `mockRestore()` — as the first statement of a `beforeEach` that no other `beforeEach` precedes, or a clear as the last statement of an `afterEach`. Vitest resets in `onBeforeTryTask`, ahead of every `beforeEach` chain and never after a test, so a reset that anything ran before, or a restore in `afterEach` / `afterAll` (the only thing that takes the last test's spies off before the `afterAll` hooks), is left alone. **It is silent until it knows the configuration**: options first — `['error', { clearMocks: true, restoreMocks: true }]`, or `{ configFile: 'path/to/runner.config.ts' }` for a runner config the search cannot find — then a `vitest.config.*` / `vite.config.*` searched upwards from the file and read as text; with neither, it reports nothing. The flag has to match the call, not the family: `restoreMocks` walks only the spies `vi.spyOn` installed, so it makes a `vi.clearAllMocks()` dead only where the receiver is such a spy. `--fix` deletes the line only in a file whose only before-hook it is, and offers a suggestion everywhere else; a reset in the middle of a test body is never reported, because there it separates two arrangements inside one test.
553
+ **`no-redundant-mock-reset` reports a mock reset in a hook that the runner already performs** — `vi.clearAllMocks()` / `vi.resetAllMocks()` / `vi.restoreAllMocks()` and the per-mock `mockClear()` / `mockReset()` / `mockRestore()` — as the first statement of a `beforeEach` that no other `beforeEach` precedes, or a clear as the last statement of an `afterEach`. Vitest resets in `onBeforeTryTask`, ahead of every `beforeEach` chain and never after a test, so a reset that anything ran before, or a restore in `afterEach` / `afterAll` (the only thing that takes the last test's spies off before the `afterAll` hooks), is left alone. **It is silent until it knows the configuration**: options first — `['error', { clearMocks: true, restoreMocks: true }]`, or `{ configFile: 'path/to/runner.config.ts' }` for a runner config the search cannot find — then a `vitest.config.*` / `vite.config.*` searched upwards from the file and read as text; with neither, it reports nothing. A config that leaves `clearMocks` out counts it on from Vitest 5, whose default it is; a config a factory or `mergeConfig` assembles in another module is not read through, so write its flags beside it — `{ configFile: 'vitest.config.ts', clearMocks: true }`. Pass only what the runner config sets: `setupAutoSpy({ restoreMocks: true })` restores in an `afterEach`, after the test, which is not the runner's `restoreMocks`. The flag has to match the call, not the family: `restoreMocks` walks only the spies `vi.spyOn` installed, so it makes a `vi.clearAllMocks()` dead only where the receiver is such a spy. `--fix` deletes the line only in a file whose only before-hook it is, and offers a suggestion everywhere else; a reset in the middle of a test body is never reported, because there it separates two arrangements inside one test.
548
554
 
549
555
  **`no-unasserted-argument` reports a bare `expect(spy).toHaveBeenCalled()` where the file itself says the arguments are the point** — either the same subject is pinned with `toHaveBeenCalledWith(…)` in another test of the file, or the test's title says `with` and its body asserts nothing but bare calls. Narrower than `vitest/prefer-called-with` on purpose: on one 2032-file suite that reports 1941 times in 360 files and this one 175 in 90. `not.toHaveBeenCalled()` and the counting matchers are never reported. `warn` rather than `error`, because the repair is the argument list the test should have named — `toHaveBeenCalledWith(…)`, `toHaveBeenCalledExactlyOnceWith(…)`, or `mustBeCalledWith(…)` where the double is configured — and that is the one thing the rule cannot write.
550
556