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 +44 -6
- package/README.md +416 -142
- package/dist/angular.d.ts +1 -1
- package/dist/angular.js +4 -4
- package/dist/bun-angular.d.ts +1 -1
- package/dist/bun-angular.js +5 -5
- package/dist/bun.d.ts +17 -10
- package/dist/bun.js +4 -4
- package/dist/{chunk-QGZRH5XG.js → chunk-2PFOBMTZ.js} +1 -1
- package/dist/{chunk-RJJJTLQ3.js → chunk-DNHLQG45.js} +1 -1
- package/dist/{chunk-LSDPJMNS.js → chunk-R5MFTE64.js} +55 -28
- package/dist/{chunk-F3NMY5KU.js → chunk-TMO2UFLD.js} +95 -49
- package/dist/{chunk-OEQTH7RA.js → chunk-WT75WGQZ.js} +72 -31
- package/dist/console.js +1 -1
- package/dist/eslint-plugin.cjs +70 -18
- package/dist/eslint-plugin.js +70 -18
- package/dist/index.js +4 -4
- package/dist/nestjs.js +2 -2
- package/dist/node.cjs +124 -56
- package/dist/node.d.cts +17 -10
- package/dist/node.js +4 -4
- package/dist/react.js +4 -4
- package/dist/svelte.js +4 -4
- package/dist/vue.js +5 -5
- package/dist/{run-effect-C_7mFldc.d.ts → zoneless-DbVZ96iR.d.ts} +142 -75
- package/package.json +3 -3
- package/skills/vitest-auto-spy/SKILL.md +5 -2
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
|
|
1086
|
-
|
|
1087
|
-
|
|
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
|
-
|
|
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
|
|
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 |
|