vitest-auto-spy 3.5.0 → 3.7.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.

Potentially problematic release.


This version of vitest-auto-spy might be problematic. Click here for more details.

package/AGENTS.md CHANGED
@@ -12,6 +12,12 @@ node_modules/vitest-auto-spy/AGENTS.md
12
12
 
13
13
  Working on the library's own source instead? Read `CONTRIBUTING.md` in the repository.
14
14
 
15
+ Setting this up for a team? The README section
16
+ [Using this library with an AI agent](https://github.com/ASDAlexey/vitest-auto-spy#using-this-library-with-an-ai-agent)
17
+ names the instruction file each agent reads — `AGENTS.md` for OpenAI Codex, Cursor, Copilot and most
18
+ of the field, `CLAUDE.md` for Claude Code and for GLM (z.ai) or Kimi running inside it, `GEMINI.md`
19
+ for Gemini CLI — and gives the two commands that install this pointer for all of them.
20
+
15
21
  | Resource | Where |
16
22
  | ----------------------- | -------------------------------------------------------------------- |
17
23
  | Spec patterns at scale | <https://asdalexey.github.io/vitest-auto-spy/recipes> |
@@ -1061,6 +1067,7 @@ mechanisms, and a test that waits on the wrong one fails with a message that nam
1061
1067
  | effects + `afterNextRender` + CD | `await stable(fixture)` (`…/angular`) | `detectChanges()` alone |
1062
1068
  | timers, debounces, polling | `await advanceTimers(ms)` (`…/setup`) | `await Promise.resolve()` |
1063
1069
  | a dynamic `import()`, native `async` in a dep | `await flushEventLoop()` / `settleDynamicImport()` | `tick()`, `flushMicrotasks()`, microtasks |
1070
+ | an `httpResource()` / `resource()` / `rxResource` | `await settleResource(r)` (`…/angular`) | `flushEventLoopUntil` — it never ticks |
1064
1071
 
1065
1072
  ```ts
1066
1073
  import { flushEventLoop, settleDynamicImport } from 'vitest-auto-spy';
@@ -1082,9 +1089,14 @@ Three rules worth stating outright, because each of them cost a day somewhere:
1082
1089
  and a non-zero exit code.
1083
1090
 
1084
1091
  `flushEventLoopUntil(isDone, { turns, label })` is the same thing with a condition and a budget —
1085
- for a `resource()` leaving `loading`, a chunk becoming reachable, an SDK reporting itself ready. Use
1086
- it instead of a hand-tuned turn count: the count depends on the dependency, not on the spec, and a
1087
- condition that never holds fails naming the `label` rather than hanging until the runner's timeout.
1092
+ for a chunk becoming reachable, an SDK reporting itself ready, a queue draining. Use it instead of a
1093
+ hand-tuned turn count: the count depends on the dependency, not on the spec, and a condition that
1094
+ never holds fails naming the `label` rather than hanging until the runner's timeout.
1095
+
1096
+ **Not for an Angular `resource()` / `httpResource()`.** Those need a change-detection _tick_, and
1097
+ this helper only takes event-loop turns — a resource awaited through it finishes the whole budget
1098
+ having issued zero requests. `settleResource(resource, { turns, label })` from
1099
+ `vitest-auto-spy/angular` is that wait.
1088
1100
 
1089
1101
  `flushEventLoop(turns?)` takes real event-loop turns even while the timers are faked, without
1090
1102
  touching the clock. It is the honest name for the `await vi.advanceTimersByTimeAsync(0)` trick,
@@ -1468,9 +1480,15 @@ spies.get(PricingService).total.mockReturnValue(100);
1468
1480
  // NOTE: Injector.create() — it does NOT accept EnvironmentProviders (provideHttpClient() etc.)
1469
1481
 
1470
1482
  // zoneless waiting
1471
- await stable(fixture); // flush effects, then await the fixture
1483
+ await stable(fixture); // flush effects, then await the fixture; fails at 2000 ms naming the cause
1484
+ await stable(fixture, { timeout: 5000, label: 'the products fixture' });
1472
1485
  flushEffects(); // the no-fixture half: services, stores, runInInjectionContext
1473
1486
 
1487
+ // resources — one wait for httpResource(), resource() and rxResource()
1488
+ flushEffects(); // an httpResource issues NO request until something ticks
1489
+ httpTesting.expectOne('/api/products').flush([product]);
1490
+ await settleResource(products, { label: 'the product resource' });
1491
+
1474
1492
  // signal assertions
1475
1493
  registerSignalMatchers(); // once, in the setup file
1476
1494
  expect(component.total).toHaveSignalValue(3);
@@ -1483,6 +1501,12 @@ Two zoneless traps:
1483
1501
  - `expect(someSignal).toBeTruthy()` passes for **every** signal ever created — a signal is a
1484
1502
  function. Use `toHaveSignalValue`, which also rejects the missing-parentheses mistake.
1485
1503
 
1504
+ And one resource trap, which is the same shape one level up: an `httpResource()` reports `loading`
1505
+ with its **default** value until a tick _and_ a microtask after its response is flushed, so a spec
1506
+ that asserts too early asserts the default and passes. `settleResource` fails instead of passing
1507
+ emptily. Note the order — `flushEffects()` first (the request is issued there, not on creation),
1508
+ then the flush, then the wait.
1509
+
1486
1510
  Per-file timing, to find which specs actually pay for `TestBed`:
1487
1511
 
1488
1512
  ```ts
@@ -1663,12 +1687,13 @@ export default [{ files: ['**/*.spec.ts'], ...autoSpy.configs.recommended }];
1663
1687
  | `prefer-inject-spy` | `warn` | suggest | `vi.spyOn(TestBed.inject(X), 'm')`, inline or via a `const` → `injectSpy(X).m` |
1664
1688
  | `no-shared-module-level-mock` | `error` | — | an **exported** value holding `vi.fn()`s → export a factory instead |
1665
1689
  | `no-mocked-for-spy` | `warn` | `--fix` | `Mocked<T>` in any type position → `Spy<T>`, import and all |
1690
+ | `prefer-as-spy` | `warn` | `--fix` | `TestBed.inject(X) as Spy<X>` → `asSpy<X>(TestBed.inject(X))`, import and all |
1666
1691
  | `no-done-callback` | `error` | — | `it('x', (done) => …)` → `async` + an awaited assertion |
1667
1692
  | `no-floating-assertion` | `error` | — | `expect()` in a `.then()` nobody awaits → `expect(await promise)` |
1668
1693
  | `no-overridden-provider` | `error` | — | two providers for one token in one array → the earlier one never runs |
1669
1694
  | `no-inject-before-override` | `warn` | — | `TestBed.inject()` in a hook, in a suite that still calls `override*` |
1670
1695
 
1671
- Eleven rules; one fixes on its own, three offer suggestions. `no-mocked-for-spy` only ever touches a
1696
+ Twelve rules; two fix on their own, three offer suggestions. `no-mocked-for-spy` only ever touches a
1672
1697
  **type position**, where a wrong rewrite is a compile error rather than a test that quietly changed
1673
1698
  meaning — so `--fix` renames the type, adds `import type { Spy } from 'vitest-auto-spy'` and drops
1674
1699
  the orphaned `Mocked` import. Every type position, not only a `let`: a factory's return type, a
@@ -1677,6 +1702,17 @@ in all eight reports — fix one and leave the other and the file says both. It
1677
1702
  cannot prove the rename (a `Mocked` the file declares itself, a `Spy` that is already something
1678
1703
  else, `Mocked<{ a: Mock }>` rather than a named type) and reports without a fix.
1679
1704
 
1705
+ `prefer-as-spy` is the same licence from the other end, and the one a migration meets in bulk: a
1706
+ `jest-auto-spies` suite writes `TestBed.inject(X) as Spy<X>` once per injected double, and that cast
1707
+ fails here with `TS2352` — `Spy<T>` adds `accessorSpies` and the per-method helpers, so neither type
1708
+ sufficiently overlaps the other. `asSpy` is a typed identity function, so `--fix` keeps the
1709
+ assertion the developer already made, carries the type arguments across (inference answers
1710
+ `Spy<Service<any>>` for a generic class), adds the `asSpy` import and removes a `Spy` import the
1711
+ rewrite orphans. A cast that hops through `unknown` is left alone — the hop says the value is not a
1712
+ `T` — except after `TestBed.inject(X)`, where the container returns `X` by construction and the hop
1713
+ was only silencing `TS2352`. Neither rule is for the object under test: a service the spec
1714
+ exercises is not a double, and typing it as the class is the repair there.
1715
+
1680
1716
  `no-expect-in-subscribe` reports one shape and **three different edits**, and says which: the
1681
1717
  subscription is the last thing the test does (invert it into `await firstValueFrom`); something
1682
1718
  after it is what makes the stream emit (hold the promise — `const p = expectEmission(src$)`, fire
@@ -1784,7 +1820,9 @@ packages, which a subpath export can never be.
1784
1820
  | a stub that works in the first test of the file and in no other | installed at `describe` level or in `beforeAll`, then restored away | install it in `beforeEach`, or `installPerTest(() => stub…())` |
1785
1821
  | a third-party library failing every other run, no test named | a test sealed a global with `Object.defineProperty` (non-configurable) | `setupAutoSpy({ guardGlobals: 'throw' })` names the file; then `mockValueProp` |
1786
1822
  | `expected [ { at: 1, …(5) }, …(8) ] to deeply equal [ { …(6) }, … ]` | one field moved in every element — usually a frozen clock or an id | `expect(diffByField(actual, expected)).toBeUndefined()` |
1787
- | a hand-tuned number of turns waiting for a `resource()` to load | the hand-off count depends on the dependency, not on the spec | `await flushEventLoopUntil(() => r.status() !== 'loading', { label })` |
1823
+ | a hand-tuned number of turns waiting for a `resource()` to load | a resource needs a change-detection **tick**, not event-loop turns; `flushEventLoopUntil` never ticks and the resource never even issues its request | `flushEffects()`, flush the request, then `await settleResource(r, { label })` |
1824
+ | a `resource()` assertion that passes but reads the **default** value | the spec asserted before the resource left `loading` | `await settleResource(r, { label })` — it fails loudly instead |
1825
+ | a spec dying on the runner's 5 s file timeout right after `await stable(fixture)` | the fixture never stabilised — an unflushed request, a real `setInterval` | `stable` now fails at 2000 ms naming the cause; raise `{ timeout }` only once neither is true |
1788
1826
  | `flushEventLoopUntil` timing out on the **first** such test only, the rest green | a cold dynamic `import()` outran the turn budget; later tests hit the module cache | `await settleDynamicImport(() => import('…'))` — await the module, do not count turns |
1789
1827
  | a template error in a `describe` that never patched anything | a spec `afterEach` threw and skipped `setupAutoSpy`'s, so a `mock*Prop` patch travelled | upgrade — an `onTestFinished` net restores it and names the cause (§10) |
1790
1828
  | `Property 'mockReturnValue' does not exist on type 'never'` | a generic method with a conditional return type; the spy collapsed | upgrade — fixed in the types; the member now keeps its sync helper bundle |