vitest-auto-spy 5.37.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,69 @@ 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
+
13
76
  ## [5.37.0] - 2026-09-26
14
77
 
15
78
  ### Added
@@ -5420,7 +5483,8 @@ moved under 1 %: `/angular` +192 B (+1.0 %) for the override queue and the HTTP
5420
5483
  This copy ships with the package and carries 5.x only. Releases before 5.0.0 are in the
5421
5484
  [full changelog](https://github.com/ASDAlexey/vitest-auto-spy/blob/master/CHANGELOG.md).
5422
5485
 
5423
- [Unreleased]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.37.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
5424
5488
  [5.37.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.36.0...v5.37.0
5425
5489
  [5.36.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.35.0...v5.36.0
5426
5490
  [5.35.0]: https://github.com/ASDAlexey/vitest-auto-spy/compare/v5.34.0...v5.35.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.
@@ -1178,6 +1186,22 @@ _is_ declared; `NG0304` calls a missing directive a missing component; a bare at
1178
1186
  nothing at all). `schemas: [NO_ERRORS_SCHEMA]` next to a standalone component is a dead entry —
1179
1187
  schemas apply to a testing module's `declarations` only.
1180
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
+
1181
1205
  Angular ≥ 22.2: when the directive needs no static host attribute, no `TemplateRef` (structural) and
1182
1206
  no sibling markup, `TestBed.createDirective(Dir, { tagName, bindings })` builds the host itself.
1183
1207
  `stable(fixture)`, `hostElement(fixture)` and `toHaveDirectiveApplied` take its `DirectiveFixture`;
@@ -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) |
@@ -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; 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`. 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 |
@@ -4,7 +4,7 @@ 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-6VPSS4BA.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
10
  import { closestName } from './chunk-6KP5QMMV.js';
package/dist/bun.js CHANGED
@@ -1,5 +1,5 @@
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-6VPSS4BA.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
4
  export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-6KP5QMMV.js';
5
5
  import './chunk-W5EXSI22.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(
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.36.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",
@@ -4059,6 +4060,73 @@ function checkJasmineEra(profile) {
4059
4060
  }
4060
4061
  ];
4061
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
+ }
4062
4130
  var BASE_CONFIGS2 = ["ts", "mts", "cts", "js", "mjs", "cjs"].map((extension) => `vitest-base.config.${extension}`);
4063
4131
  var SHARED_BY_DEFAULT_FROM = [21];
4064
4132
  var OPTION_WINS_FROM = [22, 1];
@@ -4761,6 +4829,7 @@ function runDoctor(profile) {
4761
4829
  ...checkHelperEntry(profile, graph),
4762
4830
  ...checkUnawaitedHelper(profile, graph),
4763
4831
  ...checkModuleMockLeak(profile, graph),
4832
+ ...checkMockResetConfig(profile, graph),
4764
4833
  ...checkVitest5Removed(profile, graph),
4765
4834
  ...checkVitest5ClearMocks(profile, graph),
4766
4835
  ...checkVitest5Available(profile),
@@ -4979,23 +5048,24 @@ function rxjsBullet(profile, facts) {
4979
5048
  }
4980
5049
  const target = facts.setupFile ?? profile.setupFiles[0] ?? "the test setup file";
4981
5050
  return `- Observable spies (\`nextWith\`, \`observablePropsToSpyOn\`) need \`import 'vitest-auto-spy/rxjs'\` once, in
4982
- \`${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".`;
4983
5052
  }
4984
5053
  function companionsBullet(facts) {
4985
5054
  if (facts.companions.length === 0) {
4986
5055
  return void 0;
4987
5056
  }
4988
5057
  const list = facts.companions.map((entry) => `\`${entry}\``).join(", ");
4989
- return `- Setup helpers also come from ${list}, which since 5.21.0 are separate
4990
- 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.`;
4991
5060
  }
4992
5061
  function renderBody(profile, facts = NO_FACTS) {
4993
5062
  const lines = [
4994
5063
  "## Tests that use `vitest-auto-spy`",
4995
5064
  "",
4996
- "Read `node_modules/vitest-auto-spy/AGENTS.md` before writing or fixing a spec that uses",
4997
- "`vitest-auto-spy` \u2014 it is the authoritative reference for the API, the configuration semantics",
4998
- "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.",
4999
5069
  "",
5000
5070
  `- This repository imports from \`${profile.entry}\`. Each entry registers its mock adapter on`,
5001
5071
  " import, so the wrong one leaves the wrong adapter installed and the spies fail at runtime.",
@@ -5003,8 +5073,7 @@ function renderBody(profile, facts = NO_FACTS) {
5003
5073
  companionsBullet(facts),
5004
5074
  rxjsBullet(profile, facts),
5005
5075
  "- `methodsToSpyOn` **adds** to the auto-discovered prototype methods; the exhaustive whitelist is",
5006
- " `onlyMethodsToSpyOn`. For methods that live on the instance rather than the prototype, use",
5007
- " `createAutoMock<T>()`.",
5076
+ " `onlyMethodsToSpyOn`. For methods on the instance, not the prototype, use `createAutoMock<T>()`.",
5008
5077
  "- `Spy<T>` is a mapped type and drops `#private` members, so it is not assignable to `T`. Declare",
5009
5078
  " the variable as `Spy<T>`, or pass `asInstance(spy)` where the real type is required.",
5010
5079
  "- `npx vitest-auto-spy doctor` reports suite-level defects that never fail a run."
@@ -6212,7 +6281,7 @@ function nothingOverBudgetNote(gate = GATE_DEFAULTS, runFailed = false) {
6212
6281
  return `Nothing over budget: no file over its budget and no test body over ${formatMs(gate.maxTestMs)}. ${verdict}`;
6213
6282
  }
6214
6283
  var DOMINATES = 0.3;
6215
- var CONFIG_FILE = /(?:^|\/)vite(?:st)?[\w.-]*\.config\.[cm]?[jt]s$/;
6284
+ var CONFIG_FILE2 = /(?:^|\/)vite(?:st)?[\w.-]*\.config\.[cm]?[jt]s$/;
6216
6285
  var NO_ISOLATION = /\bisolate\s*:\s*false/;
6217
6286
  var JSDOM = /\benvironment\s*:\s*["'`]jsdom["'`]/;
6218
6287
  var HAPPY_DOM = /happy-dom/;
@@ -6224,7 +6293,7 @@ var LARGE_RUN_MS = 6e4;
6224
6293
  function configCode(graph) {
6225
6294
  const configs = [];
6226
6295
  for (const [file, text] of graph.texts) {
6227
- if (!CONFIG_FILE.test(file)) {
6296
+ if (!CONFIG_FILE2.test(file)) {
6228
6297
  continue;
6229
6298
  }
6230
6299
  configs.push([
package/dist/index.js CHANGED
@@ -3247,12 +3247,15 @@ function looksLikeLiveHostObject(instance) {
3247
3247
  continue;
3248
3248
  }
3249
3249
  if (constructor.name === "EventTarget") {
3250
- return engineLevel;
3250
+ return engineLevel || isEnginePrivateEventTarget(constructor);
3251
3251
  }
3252
3252
  engineLevel ||= Reflect.get(globalThis, constructor.name) === constructor;
3253
3253
  }
3254
3254
  return false;
3255
3255
  }
3256
+ function isEnginePrivateEventTarget(constructor) {
3257
+ return typeof Node !== "undefined" && constructor !== Reflect.get(globalThis, "EventTarget") && Reflect.getPrototypeOf(Node.prototype) === Reflect.get(constructor, "prototype");
3258
+ }
3256
3259
  function warnOnUndiscriminatedHostDiscovery(label, listedAdditions) {
3257
3260
  const additions = listedAdditions ? "The methods listed (a bare array is methodsToSpyOn) are spied in addition to that discovery, not instead of it. " : "";
3258
3261
  reportMisconfiguration(
package/dist/node.cjs CHANGED
@@ -2867,12 +2867,15 @@ function looksLikeLiveHostObject(instance) {
2867
2867
  continue;
2868
2868
  }
2869
2869
  if (constructor.name === "EventTarget") {
2870
- return engineLevel;
2870
+ return engineLevel || isEnginePrivateEventTarget(constructor);
2871
2871
  }
2872
2872
  engineLevel ||= Reflect.get(globalThis, constructor.name) === constructor;
2873
2873
  }
2874
2874
  return false;
2875
2875
  }
2876
+ function isEnginePrivateEventTarget(constructor) {
2877
+ return typeof Node !== "undefined" && constructor !== Reflect.get(globalThis, "EventTarget") && Reflect.getPrototypeOf(Node.prototype) === Reflect.get(constructor, "prototype");
2878
+ }
2876
2879
  function warnOnUndiscriminatedHostDiscovery(label, listedAdditions) {
2877
2880
  const additions = listedAdditions ? "The methods listed (a bare array is methodsToSpyOn) are spied in addition to that discovery, not instead of it. " : "";
2878
2881
  reportMisconfiguration(
package/dist/node.js CHANGED
@@ -2761,12 +2761,15 @@ function looksLikeLiveHostObject(instance) {
2761
2761
  continue;
2762
2762
  }
2763
2763
  if (constructor.name === "EventTarget") {
2764
- return engineLevel;
2764
+ return engineLevel || isEnginePrivateEventTarget(constructor);
2765
2765
  }
2766
2766
  engineLevel ||= Reflect.get(globalThis, constructor.name) === constructor;
2767
2767
  }
2768
2768
  return false;
2769
2769
  }
2770
+ function isEnginePrivateEventTarget(constructor) {
2771
+ return typeof Node !== "undefined" && constructor !== Reflect.get(globalThis, "EventTarget") && Reflect.getPrototypeOf(Node.prototype) === Reflect.get(constructor, "prototype");
2772
+ }
2770
2773
  function warnOnUndiscriminatedHostDiscovery(label, listedAdditions) {
2771
2774
  const additions = listedAdditions ? "The methods listed (a bare array is methodsToSpyOn) are spied in addition to that discovery, not instead of it. " : "";
2772
2775
  reportMisconfiguration(
package/dist/rstest.js CHANGED
@@ -1,4 +1,4 @@
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-6VPSS4BA.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
3
  export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-6KP5QMMV.js';
4
4
  import './chunk-W5EXSI22.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitest-auto-spy",
3
- "version": "5.37.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
@@ -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. 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 }`. 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