vitest-auto-spy 5.14.0 → 5.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -201,6 +201,23 @@ asSpy<AppLogger>(logger.channel('app')).info.mockReturnValue(undefined); // →
201
201
  `asInstance` did not take a deep mock before 3.5.0, which left it with nowhere to go: this tree
202
202
  sends you to `mockDeep` when the calls chain, and the result then fitted nothing that expected `T`.
203
203
 
204
+ **`selfReturning: true` chains a factory, not a `return this` builder**, and the difference decides
205
+ where the calls are recorded. A called node answers _itself_, not the object the method was read
206
+ off, so every hop moves one level deeper: `editor.chain().focus().insertContent('text')` records
207
+ `insertContent` on `chain.focus`, while the `chain` handle the spec is holding still has none — and
208
+ `expect(chain.insertContent).toHaveBeenCalled()` reports nothing although the chain ran. Either walk
209
+ the same path in the assertion, or, for an API where every command answers the **same** object
210
+ (tiptap's `ChainedCommands`, a query builder, a `mockReturnThis()` chain), build that object with
211
+ `createAutoMock` instead, where `selfReturning` names methods that answer one double:
212
+
213
+ ```ts
214
+ const chain = createAutoMock<ChainedCommands>(undefined, { selfReturning: ['focus', 'insertContent'] });
215
+ const editor = createAutoMock<Editor>(undefined, { returns: { chain: asInstance(chain) } });
216
+
217
+ editor.chain().focus().insertContent('text').run();
218
+ expect(chain.insertContent).toHaveBeenCalledWith('text'); // one double, so the assertion is the obvious one
219
+ ```
220
+
204
221
  **An `abstract class` is a class.** `abstract class LocalStorage extends AbstractStorage {}`,
205
222
  provided in production as `{ provide: LocalStorage, useClass: BrowserLocalStorage }`, is the
206
223
  standard Angular DI-token idiom, and `provideAutoSpy(LocalStorage)` / `createSpyFromClass(LocalStorage)`
@@ -231,6 +248,19 @@ createAutoMock<NavigationService>({ currentFocus: undefined, navRoot: undefined,
231
248
  This is the way to say "the member exists and is empty", and it is worth writing even when it looks
232
249
  redundant.
233
250
 
251
+ **A getter in `overrides` stays a getter.** The seed is kept as a descriptor rather than read once
252
+ while the double is assembled, so it runs at every read, with the double as `this` — and a seed
253
+ written to **throw** ("this global is missing on this platform") fails where the code under test
254
+ reads the member, not where the provider literal is evaluated. A `{ set }` seed is kept the same way
255
+ and takes the write. Both used to be flattened as the double was built, which turned a throwing
256
+ seed into a failure during `TestBed.configureTestingModule`, three frames from the branch under test.
257
+
258
+ A **registration** is no exception, and it was the half that stayed broken one release longer: the
259
+ merge that puts `registerAutoSpyDefaults` under a call site copied both sides with a spread, so a
260
+ seeded getter was flattened on any class or token the registry knows — whether or not the
261
+ registration names that key — while the same seed stayed live on one it does not. The merge copies
262
+ descriptors now, and the seed behaves the same either way.
263
+
234
264
  ### What a Proxy-backed double cannot do
235
265
 
236
266
  `createAutoMock` and `mockDeep` build a Proxy, not an object, and there is one place where the
@@ -3148,7 +3178,7 @@ blanket downgrade so those keep their severity; do not copy the two names into a
3148
3178
  | `prefer-create-spy-from-class` | `error` | — | an object literal of 2+ `vi.fn()`s → `createSpyFromClass` (a factory's own seed is exempt) |
3149
3179
  | `no-stub-class-double` | `warn` | — | a class whose fields are `vi.fn()`s → `createSpyFromClass` / `provideAutoSpy` and delete the stub; a decorated, `implements`-ing, `extends`-ing or unnamed class is exempt — option: `{ minRunnerFns }` |
3150
3180
  | `no-structural-double` | `warn` | — | an object of `vi.fn()`s bound to a name declared `{ load: Mock }` → `createAutoMock<T>()`; a bare `let fn: Mock` is a callback and never reported — option: `{ minRunnerFns }` |
3151
- | `prefer-inject-spy` | `error` | suggest | `vi.spyOn(TestBed.inject(X), 'm')`, inline or via a `const` → `injectSpy(X).m` |
3181
+ | `prefer-inject-spy` | `error` | suggest | `vi.spyOn(TestBed.inject(X), 'm')`, inline or via a `const` → `injectSpy(X).m`; `ApplicationRef`, `DestroyRef`, `EnvironmentInjector`, `HttpClient` and `Injector` keep their real instance — option: `{ ignoreTokens }` |
3152
3182
  | `no-shared-module-level-mock` | `error` | — | an **exported** value holding `vi.fn()`s → export a factory instead |
3153
3183
  | `no-mocked-for-spy` | `error` | `--fix` / suggest | `Mocked<T>` in any type position → `Spy<T>`, import and all — a suggestion where the assigned value is not from a factory of this library |
3154
3184
  | `prefer-as-spy` | `error` | `--fix` | `TestBed.inject(X) as Spy<X>` → `asSpy<X>(TestBed.inject(X))`, import and all |
@@ -3311,6 +3341,15 @@ The variable is resolved through the scope manager, so it has to be a `const`/`l
3311
3341
  from `TestBed.inject(...)` and never assigned again — a name bound by an import, a parameter, or a
3312
3342
  `let` that is reassigned is left alone.
3313
3343
 
3344
+ Five tokens are left alone whatever is spied on them: `ApplicationRef`, `DestroyRef`,
3345
+ `EnvironmentInjector`, `HttpClient`, `Injector`. Each is a case where this rule's advice is
3346
+ impossible or self-defeating — `{ provide: DestroyRef, useValue }` is silently ignored, because
3347
+ `R3Injector.get()` short-circuits on `__NG_ENV_ID__` before it reads its providers; a spied
3348
+ `ApplicationRef` has no `injector` to build a component with; `HttpClient` under
3349
+ `provideHttpClientTesting()` is supposed to be real. A project adds its own with
3350
+ `['error', { ignoreTokens: ['MapRendererService'] }]`, compared as source text, which extends the
3351
+ five rather than replacing them.
3352
+
3314
3353
  `no-inject-before-override` catches the trap this plugin's own advice sets. `TestBed.inject()` in a
3315
3354
  `beforeEach` — the line you write once `provideAutoSpy(X)` has taken away the literal you used to
3316
3355
  configure — instantiates the module, and every `TestBed.override*` afterwards throws, including one
package/README.md CHANGED
@@ -23,7 +23,7 @@ faster at suite scale ([benchmarks](#benchmarks)) — and for
23
23
  [![downloads per month](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.npmjs.org%2Fdownloads%2Fpoint%2Flast-month%2Fvitest-auto-spy&query=%24.downloads&color=brightgreen&logo=npm&label=downloads%2Fmonth)](https://www.npmjs.com/package/vitest-auto-spy)
24
24
  [![downloads over 18 months](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.npmjs.org%2Fdownloads%2Fpoint%2F2026-06-21%3A2030-01-01%2Fvitest-auto-spy&query=%24.downloads&color=brightgreen&logo=npm&label=downloads%2F18mo)](https://www.npmjs.com/package/vitest-auto-spy)
25
25
  [![CI](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml/badge.svg)](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
26
- [![minzipped size](https://img.shields.io/badge/minzip-17.5%20kB-brightgreen)](#install)
26
+ [![minzipped size](https://img.shields.io/badge/minzip-17.6%20kB-brightgreen)](#install)
27
27
  [![types](https://img.shields.io/npm/types/vitest-auto-spy?logo=typescript&logoColor=white)](https://www.npmjs.com/package/vitest-auto-spy)
28
28
  [![coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)](https://github.com/ASDAlexey/vitest-auto-spy/actions/workflows/ci.yml)
29
29
  [![license](https://img.shields.io/npm/l/vitest-auto-spy?color=blue)](./LICENSE)
@@ -2027,6 +2027,26 @@ expect(svc.apiUrl).toBe('https://api.test'); // or assign: svc.apiUrl = '...'
2027
2027
  > access — an un-seeded property read returns a spy. Seed real property values via `overrides`
2028
2028
  > (or assignment) to get them back verbatim.
2029
2029
 
2030
+ **A getter in `overrides` stays a getter.** The seed keeps its descriptor, so an accessor is
2031
+ installed as one and runs at the read — once per read, with the double as `this` — and a `{ set }`
2032
+ seed takes the write. That is what a seed written to _throw_ needs: "this global is missing on this
2033
+ platform" fails where the code under test reads the member, not while the provider literal is being
2034
+ evaluated.
2035
+
2036
+ ```ts
2037
+ const platform = createAutoMock<PlatformSupport>({
2038
+ get transceiver(): never {
2039
+ throw new TypeError('RTCRtpTransceiver is not defined');
2040
+ },
2041
+ });
2042
+
2043
+ expect(() => platform.transceiver).toThrow(); // at the read, not at `TestBed.configureTestingModule`
2044
+ ```
2045
+
2046
+ A class or token registered with `registerAutoSpyDefaults` behaves the same: the merge that puts a
2047
+ registration under the call site copies descriptors rather than spreading them, so a seeded getter
2048
+ is not flattened by the mere existence of a registration.
2049
+
2030
2050
  For a double the code under test only **reads** — a DTO, a route snapshot, a config object — that
2031
2051
  caveat is the wrong trade, and [`createMock<T>()`](#utilities) is the other half of the pair: it
2032
2052
  returns a plain `T` built from the fields you seed, with no spies anywhere.
@@ -3427,7 +3447,7 @@ export can never be.
3427
3447
  | `prefer-create-spy-from-class` | `error` | — | an object literal of two or more `vi.fn()`s → `createSpyFromClass` / `createAutoMock`, unless it is a factory's own seed |
3428
3448
  | `no-stub-class-double` | `warn` | — | a class whose fields are `vi.fn()`s → `createSpyFromClass` / `provideAutoSpy`, the stub class deleted |
3429
3449
  | `no-structural-double` | `warn` | — | an object of `vi.fn()`s bound to a name declared `{ load: Mock }` → `createAutoMock<T>()` |
3430
- | `prefer-inject-spy` | `error` | suggest | `vi.spyOn(TestBed.inject(X), 'm')`, in one step or two → `injectSpy(X).m` |
3450
+ | `prefer-inject-spy` | `error` | suggest | `vi.spyOn(TestBed.inject(X), 'm')`, in one step or two → `injectSpy(X).m`; `ApplicationRef`, `DestroyRef`, `EnvironmentInjector`, `HttpClient` and `Injector` keep their real instance — option: `{ ignoreTokens }` |
3431
3451
  | `no-object-define-property` | `error` | suggest | `Object.defineProperty` in a spec → `mockReadonlyProp` / `mockValueProp` |
3432
3452
  | `no-expect-in-subscribe` | `error` | suggest | `expect()` inside a `subscribe()` callback → `expectEmission` / `firstValueFrom` |
3433
3453
  | `no-shared-module-level-mock` | `error` | — | an **exported** value holding `vi.fn()`s → export a factory that returns it |
package/dist/angular.js CHANGED
@@ -729,6 +729,12 @@ function baseList(base) {
729
729
  function baseObject(base) {
730
730
  return isMergeable(base) ? base : {};
731
731
  }
732
+ function mergeObjects(base, written) {
733
+ const merged = {};
734
+ Object.defineProperties(merged, Object.getOwnPropertyDescriptors(base));
735
+ Object.defineProperties(merged, Object.getOwnPropertyDescriptors(written));
736
+ return merged;
737
+ }
732
738
  function mergeAutoSpyDefaults(ObjectClass, local) {
733
739
  const defaults = registry().get(ObjectClass);
734
740
  if (!defaults) {
@@ -749,7 +755,7 @@ function mergeInto(defaults, written) {
749
755
  if (Array.isArray(value)) {
750
756
  merged[key] = [.../* @__PURE__ */ new Set([...baseList(base), ...value])];
751
757
  } else if (isMergeable(value)) {
752
- merged[key] = { ...baseObject(base), ...value };
758
+ merged[key] = mergeObjects(baseObject(base), value);
753
759
  } else {
754
760
  merged[key] = value;
755
761
  }
@@ -1521,7 +1527,12 @@ function exposeAsSpy(spy) {
1521
1527
  function createProxyPropStore(seed) {
1522
1528
  const store = { values: /* @__PURE__ */ new Map(), accessors: /* @__PURE__ */ new Map(), deleted: /* @__PURE__ */ new Set() };
1523
1529
  for (const key of Reflect.ownKeys(seed)) {
1524
- store.values.set(key, Reflect.get(seed, key));
1530
+ const descriptor = { ...Reflect.getOwnPropertyDescriptor(seed, key) };
1531
+ if ((descriptor.get ?? descriptor.set) !== void 0) {
1532
+ store.accessors.set(key, descriptor);
1533
+ continue;
1534
+ }
1535
+ store.values.set(key, descriptor.value);
1525
1536
  }
1526
1537
  return store;
1527
1538
  }
@@ -1,12 +1,12 @@
1
1
  import { stable, flushEffects } from './chunk-M5BULP7X.js';
2
2
  export { flushEffects, stable } from './chunk-M5BULP7X.js';
3
3
  import './chunk-M6VMOOZ2.js';
4
- export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, stubConstructor, withOverrides } from './chunk-ADBB2HQG.js';
4
+ export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, stubConstructor, withOverrides } from './chunk-5XLI5JYC.js';
5
5
  import { currentSpecFile, mockValueProp } from './chunk-N3T7KWWA.js';
6
6
  export { countMockedProps, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockValueProp, reportPropsOutsideHooks, restoreMockedProps } from './chunk-N3T7KWWA.js';
7
- import { createSpyForToken } from './chunk-RPEYIZPL.js';
8
- import { createSpyFromClass } from './chunk-BWT7U4V4.js';
9
- export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-BWT7U4V4.js';
7
+ import { createSpyForToken } from './chunk-DUFMKOKF.js';
8
+ import { createSpyFromClass } from './chunk-WAMFS3UI.js';
9
+ export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-WAMFS3UI.js';
10
10
  import './chunk-OS4PTU6D.js';
11
11
  import './chunk-DTZCPCMA.js';
12
12
  import './chunk-PC3FAZT3.js';
package/dist/bun.d.ts CHANGED
@@ -248,6 +248,13 @@ interface MockDeepOptions {
248
248
  * ```ts
249
249
  * asSpy<QueryBuilder>(query.where('id')).limit.mockReturnValue(query);
250
250
  * ```
251
+ *
252
+ * **A called node answers itself, not its receiver**, which is the difference between a factory
253
+ * and a `return this` builder. `editor.chain().focus().insertContent('x')` therefore records
254
+ * `insertContent` on `chain.focus`, while the `chain` handle the spec holds has no calls at all —
255
+ * the obvious assertion reports nothing although the chain ran. Assert down the path the chain
256
+ * walked, or build the object with `createAutoMock<T>(undefined, { selfReturning: ['focus', …] })`,
257
+ * where the named methods answer one double, which is what an API of this shape does.
251
258
  */
252
259
  selfReturning?: boolean;
253
260
  }
package/dist/bun.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import './chunk-M6VMOOZ2.js';
2
- export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, stubConstructor, withOverrides } from './chunk-ADBB2HQG.js';
2
+ export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, stubConstructor, withOverrides } from './chunk-5XLI5JYC.js';
3
3
  export { countMockedProps, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockValueProp, reportPropsOutsideHooks, restoreMockedProps } from './chunk-N3T7KWWA.js';
4
- export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-BWT7U4V4.js';
4
+ export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-WAMFS3UI.js';
5
5
  import './chunk-OS4PTU6D.js';
6
6
  import './chunk-DTZCPCMA.js';
7
7
  import './chunk-PC3FAZT3.js';
@@ -1,5 +1,5 @@
1
1
  import { mockValueProp, mockAccessorsProp } from './chunk-N3T7KWWA.js';
2
- import { resolveConfiguration, mergeMethodNames, getCallableMemberNames, applyConfiguredReturns, createSpyFromClass, resolveAccessors, createAccessorsSpies, createProxyPropStore, storeWriteTraps, describeStoredProp, readStoredAccessor, NOT_STORED, disposeAutoSpy, isProtocolKey, isDeletedProp } from './chunk-BWT7U4V4.js';
2
+ import { resolveConfiguration, mergeMethodNames, getCallableMemberNames, applyConfiguredReturns, createSpyFromClass, resolveAccessors, createAccessorsSpies, createProxyPropStore, storeWriteTraps, describeStoredProp, readStoredAccessor, NOT_STORED, disposeAutoSpy, isProtocolKey, isDeletedProp } from './chunk-WAMFS3UI.js';
3
3
  import { resolveReadGuard, createTrackedPropSpy } from './chunk-OS4PTU6D.js';
4
4
  import { redefineFailure } from './chunk-DTZCPCMA.js';
5
5
  import { DISPOSE } from './chunk-PC3FAZT3.js';
@@ -1,4 +1,4 @@
1
- import { createSpyFromClass, createAutoMock } from './chunk-BWT7U4V4.js';
1
+ import { createSpyFromClass, createAutoMock } from './chunk-WAMFS3UI.js';
2
2
  import { withDocs, DOCS_LINKS } from './chunk-Q477VHHF.js';
3
3
 
4
4
  // src/lib/track-injections.ts
@@ -72,7 +72,12 @@ function attachDispose(spy) {
72
72
  function createProxyPropStore(seed) {
73
73
  const store = { values: /* @__PURE__ */ new Map(), accessors: /* @__PURE__ */ new Map(), deleted: /* @__PURE__ */ new Set() };
74
74
  for (const key of Reflect.ownKeys(seed)) {
75
- store.values.set(key, Reflect.get(seed, key));
75
+ const descriptor = { ...Reflect.getOwnPropertyDescriptor(seed, key) };
76
+ if ((descriptor.get ?? descriptor.set) !== void 0) {
77
+ store.accessors.set(key, descriptor);
78
+ continue;
79
+ }
80
+ store.values.set(key, descriptor.value);
76
81
  }
77
82
  return store;
78
83
  }
@@ -296,6 +301,12 @@ function baseList(base) {
296
301
  function baseObject(base) {
297
302
  return isMergeable(base) ? base : {};
298
303
  }
304
+ function mergeObjects(base, written) {
305
+ const merged = {};
306
+ Object.defineProperties(merged, Object.getOwnPropertyDescriptors(base));
307
+ Object.defineProperties(merged, Object.getOwnPropertyDescriptors(written));
308
+ return merged;
309
+ }
299
310
  function mergeAutoSpyDefaults(ObjectClass, local) {
300
311
  const defaults = registry().get(ObjectClass);
301
312
  if (!defaults) {
@@ -312,7 +323,7 @@ function mergeInto(defaults, written) {
312
323
  if (Array.isArray(value)) {
313
324
  merged[key] = [.../* @__PURE__ */ new Set([...baseList(base), ...value])];
314
325
  } else if (isMergeable(value)) {
315
- merged[key] = { ...baseObject(base), ...value };
326
+ merged[key] = mergeObjects(baseObject(base), value);
316
327
  } else {
317
328
  merged[key] = value;
318
329
  }
package/dist/cli.js CHANGED
@@ -2455,7 +2455,7 @@ function checkForeignPragma(graph) {
2455
2455
  }
2456
2456
 
2457
2457
  // src/cli/checks/export-map.generated.ts
2458
- var EXPORT_MAP_VERSION = "5.14.0";
2458
+ var EXPORT_MAP_VERSION = "5.15.1";
2459
2459
  var ENTRY_SPECIFIERS = "vitest-auto-spy vitest-auto-spy/bun vitest-auto-spy/bun-angular vitest-auto-spy/node vitest-auto-spy/rstest vitest-auto-spy/rxjs vitest-auto-spy/console vitest-auto-spy/dom-stubs vitest-auto-spy/diagnostics vitest-auto-spy/jasmine vitest-auto-spy/jasmine-compat vitest-auto-spy/observer-spy vitest-auto-spy/angular vitest-auto-spy/angular-http vitest-auto-spy/angular-router vitest-auto-spy/signal-forms vitest-auto-spy/nestjs vitest-auto-spy/react vitest-auto-spy/vue vitest-auto-spy/svelte vitest-auto-spy/setup vitest-auto-spy/zone vitest-auto-spy/eslint-plugin";
2460
2460
  var EXPORTED_BY = {
2461
2461
  AccessorImplementations: "0 1 2 3 4 12 17 18 19",
@@ -952,6 +952,23 @@ function injectedFromVariable(context, target) {
952
952
  const initializer = initializerOf(context.sourceCode.getScope(target), target);
953
953
  return initializer && isTestBedInject(initializer) ? initializer : void 0;
954
954
  }
955
+ var KEPT_REAL_TOKENS = ["ApplicationRef", "DestroyRef", "EnvironmentInjector", "HttpClient", "Injector"];
956
+ var INJECTED_SPY_SCHEMA = [
957
+ {
958
+ type: "object",
959
+ properties: { ignoreTokens: { type: "array", items: { type: "string" }, uniqueItems: true } },
960
+ additionalProperties: false
961
+ }
962
+ ];
963
+ function keepsTheRealInstance(context, injectCall) {
964
+ const [token] = injectCall.arguments;
965
+ if (!token) {
966
+ return false;
967
+ }
968
+ const text = context.sourceCode.getText(token);
969
+ const configured = Reflect.get(Object(context.options[0]), "ignoreTokens");
970
+ return KEPT_REAL_TOKENS.includes(text) || Array.isArray(configured) && configured.includes(text);
971
+ }
955
972
  function memberName2(node) {
956
973
  const value = node?.type === "Literal" ? Reflect.get(node, "value") : void 0;
957
974
  return typeof value === "string" && /^[$A-Z_a-z][\w$]*$/.test(value) ? value : void 0;
@@ -3007,8 +3024,9 @@ var preferInjectSpy = defineRule({
3007
3024
  anchor: "-reading-a-spy-back-from-di",
3008
3025
  description: "Read an already-spied dependency with injectSpy() instead of re-spying a TestBed.inject() result",
3009
3026
  hasSuggestions: true,
3027
+ schema: INJECTED_SPY_SCHEMA,
3010
3028
  messages: {
3011
- preferInjectSpy: "Spying the instance DI just handed you replaces one method and leaves the rest real. Provide it with `provideAutoSpy(X)` and read it back with `injectSpy(X)`."
3029
+ preferInjectSpy: "Spying the instance DI just handed you replaces one method and leaves the rest real. Provide it with `provideAutoSpy(X)` and read it back with `injectSpy(X)`. When the real instance is what the spec came for \u2014 the spy holds one method of an object whose other half has to keep working, or the token cannot be substituted at all, as `DestroyRef` cannot (it carries `__NG_ENV_ID__`, so `R3Injector.get()` answers before it reads its own providers and `{ provide: DestroyRef, useValue }` is silently ignored) \u2014 that token belongs in `{ ignoreTokens: [...] }`, which is a statement about the token and survives the next edit, rather than behind a per-line disable."
3012
3030
  },
3013
3031
  create: (context) => ({
3014
3032
  'CallExpression[callee.object.name="vi"][callee.property.name="spyOn"]': (node) => {
@@ -3017,7 +3035,7 @@ var preferInjectSpy = defineRule({
3017
3035
  return;
3018
3036
  }
3019
3037
  const injectCall = isTestBedInject(target) ? target : injectedFromVariable(context, target);
3020
- if (!injectCall) {
3038
+ if (!injectCall || keepsTheRealInstance(context, injectCall)) {
3021
3039
  return;
3022
3040
  }
3023
3041
  const suggestion = injectSpySuggestion(context, node, injectCall);
package/dist/index.js CHANGED
@@ -1502,7 +1502,12 @@ function createAccessorsSpies(autoSpy, gettersToSpyOn, settersToSpyOn, reads) {
1502
1502
  function createProxyPropStore(seed) {
1503
1503
  const store = { values: /* @__PURE__ */ new Map(), accessors: /* @__PURE__ */ new Map(), deleted: /* @__PURE__ */ new Set() };
1504
1504
  for (const key of Reflect.ownKeys(seed)) {
1505
- store.values.set(key, Reflect.get(seed, key));
1505
+ const descriptor = { ...Reflect.getOwnPropertyDescriptor(seed, key) };
1506
+ if ((descriptor.get ?? descriptor.set) !== void 0) {
1507
+ store.accessors.set(key, descriptor);
1508
+ continue;
1509
+ }
1510
+ store.values.set(key, descriptor.value);
1506
1511
  }
1507
1512
  return store;
1508
1513
  }
@@ -1887,6 +1892,12 @@ function baseList(base) {
1887
1892
  function baseObject(base) {
1888
1893
  return isMergeable(base) ? base : {};
1889
1894
  }
1895
+ function mergeObjects(base, written) {
1896
+ const merged = {};
1897
+ Object.defineProperties(merged, Object.getOwnPropertyDescriptors(base));
1898
+ Object.defineProperties(merged, Object.getOwnPropertyDescriptors(written));
1899
+ return merged;
1900
+ }
1890
1901
  function mergeAutoSpyDefaults(ObjectClass, local) {
1891
1902
  const defaults = registry().get(ObjectClass);
1892
1903
  if (!defaults) {
@@ -1903,7 +1914,7 @@ function mergeInto(defaults, written) {
1903
1914
  if (Array.isArray(value)) {
1904
1915
  merged[key] = [.../* @__PURE__ */ new Set([...baseList(base), ...value])];
1905
1916
  } else if (isMergeable(value)) {
1906
- merged[key] = { ...baseObject(base), ...value };
1917
+ merged[key] = mergeObjects(baseObject(base), value);
1907
1918
  } else {
1908
1919
  merged[key] = value;
1909
1920
  }
package/dist/jasmine.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { useVitestAdapter } from './chunk-PA4ZNLNZ.js';
2
2
  import { enableJasmineCompat } from './chunk-MAKQSO2R.js';
3
3
  export { enableJasmineCompat } from './chunk-MAKQSO2R.js';
4
- import { createSpyFromClass } from './chunk-BWT7U4V4.js';
4
+ import { createSpyFromClass } from './chunk-WAMFS3UI.js';
5
5
  import './chunk-OS4PTU6D.js';
6
6
  import './chunk-ZU55UY2M.js';
7
7
  import './chunk-33NJHIS2.js';
package/dist/nestjs.js CHANGED
@@ -1,6 +1,6 @@
1
- import { createSpyForToken } from './chunk-RPEYIZPL.js';
2
- export { trackInjections } from './chunk-RPEYIZPL.js';
3
- import { createSpyFromClass } from './chunk-BWT7U4V4.js';
1
+ import { createSpyForToken } from './chunk-DUFMKOKF.js';
2
+ export { trackInjections } from './chunk-DUFMKOKF.js';
3
+ import { createSpyFromClass } from './chunk-WAMFS3UI.js';
4
4
  import './chunk-OS4PTU6D.js';
5
5
  import { vitestMockAdapter } from './chunk-ZU55UY2M.js';
6
6
  import './chunk-33NJHIS2.js';
package/dist/node.cjs CHANGED
@@ -1222,7 +1222,12 @@ var DISPOSE = installDisposeSymbol(Symbol);
1222
1222
  function createProxyPropStore(seed) {
1223
1223
  const store = { values: /* @__PURE__ */ new Map(), accessors: /* @__PURE__ */ new Map(), deleted: /* @__PURE__ */ new Set() };
1224
1224
  for (const key of Reflect.ownKeys(seed)) {
1225
- store.values.set(key, Reflect.get(seed, key));
1225
+ const descriptor = { ...Reflect.getOwnPropertyDescriptor(seed, key) };
1226
+ if ((descriptor.get ?? descriptor.set) !== void 0) {
1227
+ store.accessors.set(key, descriptor);
1228
+ continue;
1229
+ }
1230
+ store.values.set(key, descriptor.value);
1226
1231
  }
1227
1232
  return store;
1228
1233
  }
@@ -1609,6 +1614,12 @@ function baseList(base) {
1609
1614
  function baseObject(base) {
1610
1615
  return isMergeable(base) ? base : {};
1611
1616
  }
1617
+ function mergeObjects(base, written) {
1618
+ const merged = {};
1619
+ Object.defineProperties(merged, Object.getOwnPropertyDescriptors(base));
1620
+ Object.defineProperties(merged, Object.getOwnPropertyDescriptors(written));
1621
+ return merged;
1622
+ }
1612
1623
  function mergeAutoSpyDefaults(ObjectClass, local) {
1613
1624
  const defaults = registry().get(ObjectClass);
1614
1625
  if (!defaults) {
@@ -1625,7 +1636,7 @@ function mergeInto(defaults, written) {
1625
1636
  if (Array.isArray(value)) {
1626
1637
  merged[key] = [.../* @__PURE__ */ new Set([...baseList(base), ...value])];
1627
1638
  } else if (isMergeable(value)) {
1628
- merged[key] = { ...baseObject(base), ...value };
1639
+ merged[key] = mergeObjects(baseObject(base), value);
1629
1640
  } else {
1630
1641
  merged[key] = value;
1631
1642
  }
package/dist/node.js CHANGED
@@ -1,6 +1,6 @@
1
- export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, stubConstructor, withOverrides } from './chunk-ADBB2HQG.js';
1
+ export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, stubConstructor, withOverrides } from './chunk-5XLI5JYC.js';
2
2
  export { countMockedProps, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockValueProp, reportPropsOutsideHooks, restoreMockedProps } from './chunk-N3T7KWWA.js';
3
- export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-BWT7U4V4.js';
3
+ export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-WAMFS3UI.js';
4
4
  import './chunk-OS4PTU6D.js';
5
5
  import { guardAccessorSpies, createRedefineMockAdapter } from './chunk-DTZCPCMA.js';
6
6
  import './chunk-PC3FAZT3.js';
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
- export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, stubConstructor, withOverrides } from './chunk-ADBB2HQG.js';
1
+ export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, stubConstructor, withOverrides } from './chunk-5XLI5JYC.js';
2
2
  export { countMockedProps, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockValueProp, reportPropsOutsideHooks, restoreMockedProps } from './chunk-N3T7KWWA.js';
3
- export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-BWT7U4V4.js';
3
+ export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-WAMFS3UI.js';
4
4
  import './chunk-OS4PTU6D.js';
5
5
  import { vitestMockAdapter } from './chunk-ZU55UY2M.js';
6
6
  import './chunk-33NJHIS2.js';
package/dist/rstest.js CHANGED
@@ -1,6 +1,6 @@
1
- export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, stubConstructor, withOverrides } from './chunk-ADBB2HQG.js';
1
+ export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, stubConstructor, withOverrides } from './chunk-5XLI5JYC.js';
2
2
  export { countMockedProps, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockValueProp, reportPropsOutsideHooks, restoreMockedProps } from './chunk-N3T7KWWA.js';
3
- export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-BWT7U4V4.js';
3
+ export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-WAMFS3UI.js';
4
4
  import './chunk-OS4PTU6D.js';
5
5
  import { clearAllFastSpies, resetAllFastSpies, getSpyEngine, createFastSpy } from './chunk-33NJHIS2.js';
6
6
  import { guardAccessorSpies, spyOnAccessorByRedefine } from './chunk-DTZCPCMA.js';
package/dist/svelte.js CHANGED
@@ -1,6 +1,6 @@
1
- export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, stubConstructor, withOverrides } from './chunk-ADBB2HQG.js';
1
+ export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, stubConstructor, withOverrides } from './chunk-5XLI5JYC.js';
2
2
  export { countMockedProps, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockValueProp, reportPropsOutsideHooks, restoreMockedProps } from './chunk-N3T7KWWA.js';
3
- export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-BWT7U4V4.js';
3
+ export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-WAMFS3UI.js';
4
4
  import './chunk-OS4PTU6D.js';
5
5
  import { vitestMockAdapter } from './chunk-ZU55UY2M.js';
6
6
  import './chunk-33NJHIS2.js';
package/dist/vue.js CHANGED
@@ -1,7 +1,7 @@
1
- export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, stubConstructor, withOverrides } from './chunk-ADBB2HQG.js';
1
+ export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, createSpyFromInstance, describeDuplicateCopies, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, getPackageCopies, mockConstructor, mockDeep, moduleNamespace, narrow, restoreSpiedInstance, setEmissionTimeout, settleDynamicImport, stubConstructor, withOverrides } from './chunk-5XLI5JYC.js';
2
2
  export { countMockedProps, mockAccessorsProp, mockReadonlyProp, mockReadonlyPropGetter, mockValueProp, reportPropsOutsideHooks, restoreMockedProps } from './chunk-N3T7KWWA.js';
3
- import { createSpyFromClass } from './chunk-BWT7U4V4.js';
4
- export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-BWT7U4V4.js';
3
+ import { createSpyFromClass } from './chunk-WAMFS3UI.js';
4
+ export { autoMocked, clearAutoSpy, clearAutoSpyDefaults, createAutoMock, createSpyFromClass, registerAutoSpyDefaults, resetAutoSpy } from './chunk-WAMFS3UI.js';
5
5
  import './chunk-OS4PTU6D.js';
6
6
  import { vitestMockAdapter } from './chunk-ZU55UY2M.js';
7
7
  import './chunk-33NJHIS2.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitest-auto-spy",
3
- "version": "5.14.0",
3
+ "version": "5.15.1",
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",