vitest-auto-spy 4.3.0 → 4.4.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/AGENTS.md +12 -0
- package/README.md +12 -0
- package/dist/cli.js +3 -1
- package/dist/setup.d.ts +69 -11
- package/dist/setup.js +87 -9
- package/package.json +1 -1
- package/skills/vitest-auto-spy/SKILL.md +65 -64
package/AGENTS.md
CHANGED
|
@@ -1183,6 +1183,17 @@ the realm, not owned by `globalThis`), and with `isolate: false` the next file d
|
|
|
1183
1183
|
own `useFakeTimers` with `Cannot read properties of undefined (reading 'now')`. If you see that,
|
|
1184
1184
|
the file in the stack is not the cause.
|
|
1185
1185
|
|
|
1186
|
+
`restoreWebStorage` is the other repair that is on by default. Vitest copies a DOM environment's
|
|
1187
|
+
globals onto `globalThis` behind `if (k in global) return KEYS.includes(k)`, and neither
|
|
1188
|
+
`localStorage` nor `sessionStorage` is in `KEYS` — both used to arrive only because Node put neither
|
|
1189
|
+
on `globalThis`, so the first half of that condition was false. Node's own Web Storage made the key
|
|
1190
|
+
exist, and the environment's storage stopped arriving: `setItem is not a function` on Node 25,
|
|
1191
|
+
`undefined` on Node 26, under jsdom and happy-dom alike, because the filter runs before either. The
|
|
1192
|
+
suite stays green until a spec touches storage, so this lands as "CI moved to a new Node and eleven
|
|
1193
|
+
unrelated specs died". The repair writes a key, reads it back and removes it — a storage that
|
|
1194
|
+
survives that is left alone, whoever implemented it — and it installs nothing in a `node`
|
|
1195
|
+
environment, which is supposed to have no Web Storage at all.
|
|
1196
|
+
|
|
1186
1197
|
`hookTimeoutHint` is on by default too, and it is the one that pays off on the day a suite lands in
|
|
1187
1198
|
CI. Jest resolves **one** budget — `hook.timeout || getState().testTimeout` for a hook,
|
|
1188
1199
|
`test.timeout || getState().testTimeout` for a body — while Vitest resolves `hookTimeout` on its
|
|
@@ -2484,6 +2495,7 @@ packages, which a subpath export can never be.
|
|
|
2484
2495
|
| a spy is never called, no warning | the method is an instance field, not on the prototype | `instanceMethodsToSpyOn`, or `createAutoMock<T>()` |
|
|
2485
2496
|
| `Cannot access '__vi_import_N__' before initialization` | `vi.mock()` on `@angular/core` or a relative path | you cannot mock it — the specs are bundled. Assert the result instead |
|
|
2486
2497
|
| `AggregateError at Object.dispatchError`, for a request nothing asserts on | jsdom really served an `XMLHttpRequest` — `blockNetwork` used to cover only `fetch` | `setupAutoSpy({ blockNetwork: true })`, or `{ xhr: 'empty' }` for tracker pings (§10) |
|
|
2498
|
+
| `localStorage.setItem is not a function`, or `localStorage` undefined | Node's own Web Storage made Vitest's global filter skip the environment's — Node 25 and up, jsdom and happy-dom alike | on by default in `setupAutoSpy()`; `restoreWebStorage()` on its own (§10) |
|
|
2487
2499
|
| `Schedulers cannot synchronously execute watches while scheduling` | a timer from a **previous** file, under `isolate: false` | track and cancel pending timers/frames in the setup file (§10) |
|
|
2488
2500
|
| `signal read during notification phase` | same — a stray `requestAnimationFrame` callback | same |
|
|
2489
2501
|
| an assertion error printed to stderr, every test green and the run exiting 0 | zone.js swallowed a rejection nobody handled | `setupAutoSpy({ strayRejections: true })` fails the test it surfaced in (§10) |
|
package/README.md
CHANGED
|
@@ -2492,6 +2492,7 @@ single-purpose utility you can pick up independently — they all ride on the sa
|
|
|
2492
2492
|
| `installPerTest(install)` | `/setup` | Re-install a stub before every test of the block — a `describe`-level stub is restored away after the first |
|
|
2493
2493
|
| `setupAngularTestEnv(opts)` | `/angular` | Zone and zoneless spec files in one worker, switching platforms per file |
|
|
2494
2494
|
| `restoreTimerGlobals()` | `/setup` | Put back timer globals that uninstalling the fakes deleted rather than restored |
|
|
2495
|
+
| `restoreWebStorage(options?)` | `/setup` | Give `globalThis` a `localStorage` / `sessionStorage` that work when the runner's copy never arrived ([details](#test-run-hygiene)) |
|
|
2495
2496
|
| `trackMockRegistry()` / `keepMockRegistered(mock)` / `restoreLongLivedImplementations()` | `/setup` | Keep @vitest/spy's mock registry to the mocks that outlive a file; mark one the split would miss; put back an implementation a cross-file `vi.resetAllMocks()` dropped ([details](#test-run-hygiene)) |
|
|
2496
2497
|
| `trackNodeMocks()` / `pruneNodeMocks()` / `countNodeMocks()` | `/node` | Give this library its own `node:test` `MockTracker` so a dropped spy is freed — 21× less retained heap; sweep by hand, and read the count back |
|
|
2497
2498
|
| `setSpyEngine(engine)` / `getSpyEngine()` | `/setup` | Build method spies from this library's own mock (`'auto-spy'`, the default) or from `vi.fn()` (`'runner'`) ([details](#the-spy-engine)) |
|
|
@@ -2733,6 +2734,16 @@ expensive to diagnose when it is missing. The first three are on by default:
|
|
|
2733
2734
|
which is a fact rather than a guess, and names the `setImmediate` case that reaches this with no
|
|
2734
2735
|
timer in sight: a request matching no Express route is ended by `finalhandler` on `setImmediate`,
|
|
2735
2736
|
so the 404 is never written and a routing mistake is reported as a slow test.
|
|
2737
|
+
11. **Web Storage the runner never handed over.** On by default, because it only ever repairs.
|
|
2738
|
+
Vitest copies a DOM environment's globals onto `globalThis` behind `if (k in global) return
|
|
2739
|
+
KEYS.includes(k)`, and neither `localStorage` nor `sessionStorage` is in `KEYS` — they used to
|
|
2740
|
+
arrive only because Node put neither on `globalThis`, so the first half was false. Node's own
|
|
2741
|
+
Web Storage made the key exist, and now the environment's storage never arrives: `setItem is
|
|
2742
|
+
not a function` on Node 25, `undefined` on Node 26, under jsdom and happy-dom alike, since the
|
|
2743
|
+
filter runs before either. A suite stays green with this broken — only the specs that touch
|
|
2744
|
+
storage fail — so it arrives as "CI moved to a new Node and eleven unrelated specs died". The
|
|
2745
|
+
repair decides by using the storage rather than looking at it, replaces only one that cannot
|
|
2746
|
+
keep a value, and installs nothing at all in a `node` environment.
|
|
2736
2747
|
|
|
2737
2748
|
| Option | Default | Notes |
|
|
2738
2749
|
| --------------------- | --------- | ------------------------------------------------------------------------------------- |
|
|
@@ -2746,6 +2757,7 @@ expensive to diagnose when it is missing. The first three are on by default:
|
|
|
2746
2757
|
| `guardGlobals` | `'off'` | Report a test that redefines a global property as non-configurable |
|
|
2747
2758
|
| `globalFakeTimers` | `false` | Fake timers for every test **and between them** — Jest's `enableGlobally` |
|
|
2748
2759
|
| `restoreTimerGlobals` | `true` | Put back timer globals that uninstalling the fakes deleted |
|
|
2760
|
+
| `restoreWebStorage` | `true` | Give the run a `localStorage` / `sessionStorage` that work |
|
|
2749
2761
|
| `pruneMockRegistry` | `false` | Keep @vitest/spy's ever-growing mock registry to the mocks that outlive a file |
|
|
2750
2762
|
| `hookTimeoutHint` | `true` | Explain a hook that ran out of `hookTimeout` while `testTimeout` is larger |
|
|
2751
2763
|
| `frozenClockHint` | `true` | Explain a timeout that happened because nothing advanced the fake clock |
|
package/dist/cli.js
CHANGED
|
@@ -2356,7 +2356,7 @@ function checkForeignPragma(graph) {
|
|
|
2356
2356
|
}
|
|
2357
2357
|
|
|
2358
2358
|
// src/cli/checks/export-map.generated.ts
|
|
2359
|
-
var EXPORT_MAP_VERSION = "4.
|
|
2359
|
+
var EXPORT_MAP_VERSION = "4.3.0";
|
|
2360
2360
|
var ENTRY_SPECIFIERS = "vitest-auto-spy vitest-auto-spy/bun vitest-auto-spy/bun-angular vitest-auto-spy/node 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/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";
|
|
2361
2361
|
var EXPORTED_BY = {
|
|
2362
2362
|
AccessorImplementations: "0 1 2 3 11 14 15 16",
|
|
@@ -2604,6 +2604,8 @@ var EXPORTED_BY = {
|
|
|
2604
2604
|
RestoreProp: "0 1 2 3 11 14 15 16",
|
|
2605
2605
|
restoreSpiedInstance: "0 1 2 3 14 15 16",
|
|
2606
2606
|
restoreTimerGlobals: "17",
|
|
2607
|
+
restoreWebStorage: "17",
|
|
2608
|
+
RestoreWebStorageOptions: "17",
|
|
2607
2609
|
RuleSeverity: "19",
|
|
2608
2610
|
runEffect: "2 11",
|
|
2609
2611
|
SchedulerHost: "17",
|
package/dist/setup.d.ts
CHANGED
|
@@ -356,6 +356,18 @@ interface SetupAutoSpyOptions {
|
|
|
356
356
|
* installed on purpose. See {@link restoreTimerGlobals}.
|
|
357
357
|
*/
|
|
358
358
|
restoreTimerGlobals?: boolean;
|
|
359
|
+
/**
|
|
360
|
+
* Give the run a `localStorage` and a `sessionStorage` that work. Default `true`, for the same
|
|
361
|
+
* reason as {@link restoreTimerGlobals}: a storage that survives a write and a read back is left
|
|
362
|
+
* untouched, and a `node` environment — which is supposed to have neither — gets nothing.
|
|
363
|
+
*
|
|
364
|
+
* Vitest's `populateGlobal` copies a DOM environment's globals behind `if (k in global) return
|
|
365
|
+
* KEYS.includes(k)`, and neither storage is in `KEYS`. Node's own Web Storage put the key on
|
|
366
|
+
* `globalThis`, so the filter now answers "no" and the environment's storage never arrives:
|
|
367
|
+
* `setItem is not a function` on Node 25, `localStorage` undefined on Node 26, under jsdom and
|
|
368
|
+
* happy-dom alike. See {@link restoreWebStorage}.
|
|
369
|
+
*/
|
|
370
|
+
restoreWebStorage?: boolean;
|
|
359
371
|
/**
|
|
360
372
|
* Keep `@vitest/spy`'s registry of every mock ever created down to the mocks that outlive a file.
|
|
361
373
|
* Default `false`, because it reaches into a set the runner does not expose.
|
|
@@ -458,16 +470,6 @@ interface SetupAutoSpyOptions {
|
|
|
458
470
|
*/
|
|
459
471
|
onUnstubbedCall?: UnstubbedCallHandler;
|
|
460
472
|
}
|
|
461
|
-
/**
|
|
462
|
-
* Install the library's test-run hygiene.
|
|
463
|
-
*
|
|
464
|
-
* ```ts
|
|
465
|
-
* // vitest.setup.ts
|
|
466
|
-
* import { setupAutoSpy } from 'vitest-auto-spy/setup';
|
|
467
|
-
*
|
|
468
|
-
* setupAutoSpy();
|
|
469
|
-
* ```
|
|
470
|
-
*/
|
|
471
473
|
declare function setupAutoSpy(options?: SetupAutoSpyOptions): void;
|
|
472
474
|
|
|
473
475
|
/**
|
|
@@ -817,4 +819,60 @@ declare function restoreTimerGlobals(): void;
|
|
|
817
819
|
/** The names this module watches — exported for the diagnostics that report what went missing. */
|
|
818
820
|
declare function getWatchedTimerGlobals(): string[];
|
|
819
821
|
|
|
820
|
-
|
|
822
|
+
/**
|
|
823
|
+
* Putting `localStorage` / `sessionStorage` back after the runner drops them.
|
|
824
|
+
*
|
|
825
|
+
* Vitest copies a DOM environment's globals onto `globalThis` behind one filter — `if (k in global)
|
|
826
|
+
* return KEYS.includes(k)` — and neither storage is in `KEYS`. While Node put neither on
|
|
827
|
+
* `globalThis` the first half was false and both were copied; Node's own Web Storage made the key
|
|
828
|
+
* exist, so the filter now answers "no" and the environment's storage never arrives. Measured with
|
|
829
|
+
* one spec, under jsdom and happy-dom alike, because the filter runs before either:
|
|
830
|
+
*
|
|
831
|
+
* ```text
|
|
832
|
+
* Node 24.19 localStorage works
|
|
833
|
+
* Node 25.9 localStorage.setItem is not a function
|
|
834
|
+
* Node 26.7 localStorage is undefined
|
|
835
|
+
* ```
|
|
836
|
+
*
|
|
837
|
+
* A suite stays green with this broken — only the specs that touch storage fail — so it usually
|
|
838
|
+
* arrives as "CI moved to a new Node and eleven unrelated specs died".
|
|
839
|
+
*/
|
|
840
|
+
/** The two globals this module repairs. */
|
|
841
|
+
declare const STORAGE_KEYS: readonly ["localStorage", "sessionStorage"];
|
|
842
|
+
/** One of the two. */
|
|
843
|
+
type StorageKey = (typeof STORAGE_KEYS)[number];
|
|
844
|
+
/** How {@link restoreWebStorage} finds the window whose storage it trusts. */
|
|
845
|
+
interface RestoreWebStorageOptions {
|
|
846
|
+
/**
|
|
847
|
+
* The window to take a working storage from, and to repair alongside `globalThis`.
|
|
848
|
+
*
|
|
849
|
+
* Defaults to `document.defaultView`, and to nothing where there is no document. Pass `null` to
|
|
850
|
+
* say there is no window: the repair then does nothing, which is what a DOM-less runtime wants.
|
|
851
|
+
*/
|
|
852
|
+
view?: object | null;
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Give `globalThis` a `localStorage` and a `sessionStorage` that work.
|
|
856
|
+
*
|
|
857
|
+
* Safe to call at any point and as often as you like, and a no-op in the ordinary case: a storage
|
|
858
|
+
* that survives a write and a read back is left exactly as it is, so a spec's own stub survives
|
|
859
|
+
* this. It is also a no-op where there is no window — a `node` environment is *supposed* to have no
|
|
860
|
+
* Web Storage, and inventing one would hand the code under test an API the real runtime lacks.
|
|
861
|
+
*
|
|
862
|
+
* The window's own storage is preferred over the stand-in: the environment implements the real
|
|
863
|
+
* thing, and under a runner whose window is a separate object (a Bun preload, say) only the copy to
|
|
864
|
+
* `globalThis` failed. The `Map`-backed one is for when nothing else works — which, under Vitest,
|
|
865
|
+
* is every time, because there `document.defaultView` *is* `globalThis`.
|
|
866
|
+
*
|
|
867
|
+
* ```ts
|
|
868
|
+
* // vitest.setup.ts — or leave it to setupAutoSpy(), which calls this by default
|
|
869
|
+
* import { restoreWebStorage } from 'vitest-auto-spy/setup';
|
|
870
|
+
*
|
|
871
|
+
* restoreWebStorage();
|
|
872
|
+
* ```
|
|
873
|
+
*
|
|
874
|
+
* @returns The keys it had to repair — `[]` when the environment was already sound.
|
|
875
|
+
*/
|
|
876
|
+
declare function restoreWebStorage(options?: RestoreWebStorageOptions): StorageKey[];
|
|
877
|
+
|
|
878
|
+
export { BLOCKED_FETCH_MESSAGE, BLOCKED_XHR_MESSAGE, type BlockNetworkOptions, type CountingClock, type CountingClockOptions, type DuplicateCopiesReaction, type FakeTimersConfig, type GlobalPatchReaction, type PerTestHandle, type RejectionHost, type RestoreWebStorageOptions, type SchedulerHost, type SetupAutoSpyOptions, type SpyEngine, type StopTrackingRejections, type StopTrackingTimers, type StrayRejection, type SystemTime, type XhrBlockMode, advanceTimers, blockNetwork, cancelStrayTimers, captureMockRegistry, countStrayRejections, countStrayTimers, flushStrayRejections, getMockRegistrySize, getSpyEngine, getWatchedTimerGlobals, guardGlobalPatches, installPerTest, keepMockRegistered, keepRegisteredMocks, mockNow, mockSystemTime, pruneMockRegistry, registerFocusMatchers, resetMockRegistryTracking, restoreLongLivedImplementations, restoreTimerGlobals, restoreWebStorage, setSpyEngine, setupAutoSpy, setupFakeTimers, trackMockRegistry, trackStrayRejections, trackStrayTimers, useCountingClock, withSystemTime };
|
package/dist/setup.js
CHANGED
|
@@ -140,7 +140,7 @@ function getWatchedTimerGlobals() {
|
|
|
140
140
|
|
|
141
141
|
// src/lib/fake-timers.ts
|
|
142
142
|
function setupFakeTimers(config, { betweenTests = false } = {}) {
|
|
143
|
-
const
|
|
143
|
+
const install2 = () => {
|
|
144
144
|
if (!vi.isFakeTimers()) {
|
|
145
145
|
vi.useFakeTimers(config);
|
|
146
146
|
}
|
|
@@ -152,13 +152,13 @@ function setupFakeTimers(config, { betweenTests = false } = {}) {
|
|
|
152
152
|
restoreTimerGlobals();
|
|
153
153
|
};
|
|
154
154
|
if (betweenTests) {
|
|
155
|
-
beforeAll(
|
|
155
|
+
beforeAll(install2);
|
|
156
156
|
}
|
|
157
|
-
beforeEach(
|
|
157
|
+
beforeEach(install2);
|
|
158
158
|
afterEach(() => {
|
|
159
159
|
uninstall();
|
|
160
160
|
if (betweenTests) {
|
|
161
|
-
|
|
161
|
+
install2();
|
|
162
162
|
}
|
|
163
163
|
});
|
|
164
164
|
if (betweenTests) {
|
|
@@ -681,6 +681,78 @@ function detectsAsyncLeaks(host = globalThis) {
|
|
|
681
681
|
return Reflect.get(Object(Reflect.get(Object(Reflect.get(host, "__vitest_worker__")), "config")), "detectAsyncLeaks") === true;
|
|
682
682
|
}
|
|
683
683
|
|
|
684
|
+
// src/lib/web-storage.ts
|
|
685
|
+
var STORAGE_KEYS = ["localStorage", "sessionStorage"];
|
|
686
|
+
var PROBE_KEY = "__vitest_auto_spy_probe__";
|
|
687
|
+
var PROBE_METHODS = ["setItem", "getItem", "removeItem"];
|
|
688
|
+
var MapBackedStorage = class {
|
|
689
|
+
#items = /* @__PURE__ */ new Map();
|
|
690
|
+
get length() {
|
|
691
|
+
return this.#items.size;
|
|
692
|
+
}
|
|
693
|
+
clear() {
|
|
694
|
+
this.#items.clear();
|
|
695
|
+
}
|
|
696
|
+
getItem(key) {
|
|
697
|
+
return this.#items.get(key) ?? null;
|
|
698
|
+
}
|
|
699
|
+
key(index) {
|
|
700
|
+
return [...this.#items.keys()][index] ?? null;
|
|
701
|
+
}
|
|
702
|
+
removeItem(key) {
|
|
703
|
+
this.#items.delete(key);
|
|
704
|
+
}
|
|
705
|
+
setItem(key, value) {
|
|
706
|
+
this.#items.set(key, String(value));
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
function restoreWebStorage(options = {}) {
|
|
710
|
+
const view = options.view === void 0 ? currentView() : options.view;
|
|
711
|
+
if (!view) {
|
|
712
|
+
return [];
|
|
713
|
+
}
|
|
714
|
+
const repaired = [];
|
|
715
|
+
for (const key of STORAGE_KEYS) {
|
|
716
|
+
if (works(read(globalThis, key))) {
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
const fromView = read(view, key);
|
|
720
|
+
install(view, key, works(fromView) ? fromView : new MapBackedStorage());
|
|
721
|
+
repaired.push(key);
|
|
722
|
+
}
|
|
723
|
+
return repaired;
|
|
724
|
+
}
|
|
725
|
+
function currentView() {
|
|
726
|
+
return typeof document === "undefined" ? null : document.defaultView;
|
|
727
|
+
}
|
|
728
|
+
function read(host, key) {
|
|
729
|
+
return Reflect.get(host, key);
|
|
730
|
+
}
|
|
731
|
+
function isStorageShaped(candidate) {
|
|
732
|
+
const host = Object(candidate);
|
|
733
|
+
return PROBE_METHODS.every((method) => typeof Reflect.get(host, method) === "function");
|
|
734
|
+
}
|
|
735
|
+
function works(candidate) {
|
|
736
|
+
if (!isStorageShaped(candidate)) {
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
try {
|
|
740
|
+
candidate.setItem(PROBE_KEY, PROBE_KEY);
|
|
741
|
+
const stored = candidate.getItem(PROBE_KEY);
|
|
742
|
+
candidate.removeItem(PROBE_KEY);
|
|
743
|
+
return stored === PROBE_KEY;
|
|
744
|
+
} catch {
|
|
745
|
+
return false;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
function install(view, key, storage) {
|
|
749
|
+
const descriptor = { value: storage, writable: true, configurable: true };
|
|
750
|
+
Object.defineProperty(globalThis, key, descriptor);
|
|
751
|
+
if (view !== globalThis) {
|
|
752
|
+
Object.defineProperty(view, key, descriptor);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
684
756
|
// src/lib/setup-auto-spy.ts
|
|
685
757
|
function warnAboutSuppressedLeaks(cancelled, write = writeWarning) {
|
|
686
758
|
write(
|
|
@@ -811,11 +883,17 @@ function watchStrayRejections(enabled) {
|
|
|
811
883
|
afterAll(trackStrayRejections());
|
|
812
884
|
return [reportStrayRejections];
|
|
813
885
|
}
|
|
814
|
-
function
|
|
815
|
-
reportDuplicateCopies(options.duplicateCopies ?? "throw");
|
|
886
|
+
function prepareEnvironment(options) {
|
|
816
887
|
if (options.angularBuildHint ?? true) {
|
|
817
888
|
noticeAngularBuildSplitting();
|
|
818
889
|
}
|
|
890
|
+
if (options.restoreWebStorage ?? true) {
|
|
891
|
+
restoreWebStorage();
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
function setupAutoSpy(options = {}) {
|
|
895
|
+
reportDuplicateCopies(options.duplicateCopies ?? "throw");
|
|
896
|
+
prepareEnvironment(options);
|
|
819
897
|
if (options.strayTimers ?? false) {
|
|
820
898
|
trackStrayTimers();
|
|
821
899
|
afterAll(() => {
|
|
@@ -886,10 +964,10 @@ function describeSkippedTeardown(leaked) {
|
|
|
886
964
|
DOCS_LINKS.setup
|
|
887
965
|
);
|
|
888
966
|
}
|
|
889
|
-
function installPerTest(
|
|
967
|
+
function installPerTest(install2) {
|
|
890
968
|
let current;
|
|
891
969
|
beforeEach(() => {
|
|
892
|
-
current = { value:
|
|
970
|
+
current = { value: install2() };
|
|
893
971
|
});
|
|
894
972
|
afterEach(() => {
|
|
895
973
|
current = void 0;
|
|
@@ -1000,4 +1078,4 @@ function registerFocusMatchers() {
|
|
|
1000
1078
|
}
|
|
1001
1079
|
useVitestAdapter();
|
|
1002
1080
|
|
|
1003
|
-
export { BLOCKED_FETCH_MESSAGE, BLOCKED_XHR_MESSAGE, advanceTimers, blockNetwork, cancelStrayTimers, captureMockRegistry, countStrayRejections, countStrayTimers, flushStrayRejections, getMockRegistrySize, getWatchedTimerGlobals, guardGlobalPatches, installPerTest, keepMockRegistered, keepRegisteredMocks, mockNow, mockSystemTime, pruneMockRegistry, registerFocusMatchers, resetMockRegistryTracking, restoreLongLivedImplementations, restoreTimerGlobals, setupAutoSpy, setupFakeTimers, trackMockRegistry, trackStrayRejections, trackStrayTimers, useCountingClock, withSystemTime };
|
|
1081
|
+
export { BLOCKED_FETCH_MESSAGE, BLOCKED_XHR_MESSAGE, advanceTimers, blockNetwork, cancelStrayTimers, captureMockRegistry, countStrayRejections, countStrayTimers, flushStrayRejections, getMockRegistrySize, getWatchedTimerGlobals, guardGlobalPatches, installPerTest, keepMockRegistered, keepRegisteredMocks, mockNow, mockSystemTime, pruneMockRegistry, registerFocusMatchers, resetMockRegistryTracking, restoreLongLivedImplementations, restoreTimerGlobals, restoreWebStorage, setupAutoSpy, setupFakeTimers, trackMockRegistry, trackStrayRejections, trackStrayTimers, useCountingClock, withSystemTime };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-auto-spy",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"description": "Auto-generate fully-typed test spies from a class — across Vitest, Bun and node:test, 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",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: vitest-auto-spy
|
|
3
|
-
description: Write or fix tests that use vitest-auto-spy — typed spies from a class or a type on Vitest, bun:test and node:test. Use when a spec imports `vitest-auto-spy` or
|
|
3
|
+
description: Write or fix tests that use vitest-auto-spy — typed spies from a class or a type on Vitest, bun:test and node:test. Use when a spec imports `vitest-auto-spy` or a subpath (`/angular`, `/angular-http`, `/bun-angular`, `/bun`, `/node`, `/rxjs`, `/nestjs`, `/jasmine`, `/observer-spy`, `/setup`, `/zone`, `/diagnostics`, `/eslint-plugin`), the user mentions createSpyFromClass, createAutoMock, createMock, mockDeep, createFunctionSpy, createSpyObj, enableJasmineCompat, subscribeSpyTo, provideAutoSpy, provideHttpTesting, expectRequest, injectSpy, createNestUnit, extendWithAutoSpies, renderShallow, createWithAutoSpies, createDirectiveHost, overrideComponentProvider, enableAngularDiagnostics, trackInjections, runEffect, settleResource, httpResource, mockResourceProp, mockSignalProp, mockReadonlyProp, captureArg, createSpyFromInstance, explainSpy, expectEmission, expectError, setupAutoSpy, setSpyEngine, restoreWebStorage, assertMocked, Spy<T>, calledWith, mustBeCalledWith, onlyMethodsToSpyOn, strict, onUnstubbedCall, resolveWith or nextWith, migrating off jest-auto-spies, jasmine-auto-spies or @ngneat/spectator (createSpyObject, mockProvider, SpectatorService), or a test fails with "No mock adapter registered", "Observable spies require rxjs", "not found on the class prototype", "strict mode is on", "the override did not apply", "is not a constructor", "Expected to be running in 'ProxyZone'", "jasmine is not defined", "no HttpTestingController", "localStorage.setItem is not a function" or "Spy<T> is not assignable".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# vitest-auto-spy
|
|
@@ -126,69 +126,70 @@ it('loads', async () => {
|
|
|
126
126
|
|
|
127
127
|
## Reach for these before hand-rolling
|
|
128
128
|
|
|
129
|
-
| Situation
|
|
130
|
-
|
|
|
131
|
-
| a `signal()` / `computed()` field on the class under test
|
|
132
|
-
| a resource field, when the HTTP round trip is not the point
|
|
133
|
-
| the HTTP round trip _is_ the point
|
|
134
|
-
| a node:test suite whose heap grows all run
|
|
135
|
-
| a Nest provider whose constructor keeps changing
|
|
136
|
-
| asserting a resource's value _and_ status together
|
|
137
|
-
| a callback or config object the code under test built
|
|
138
|
-
| an object the test already holds — a real service, a client, `TestBed.inject(X)` | `createSpyFromInstance(obj, config?)` — patches it in place, `restoreSpiedInstance` undoes it
|
|
139
|
-
| a `calledWith` that is not matching and you cannot see why
|
|
140
|
-
| a method that has to throw — for all calls, or for some arguments
|
|
141
|
-
| a `TestBed` spec on Vitest 4.1+, to drop `let` + `beforeEach`
|
|
142
|
-
| an `effect()` whose trigger is now a static signal
|
|
143
|
-
| the component constructs its own `IntersectionObserver`
|
|
144
|
-
| a green run exiting 1 with `AbortError` under happy-dom
|
|
145
|
-
| a real request going out of a unit run (`fetch`, XHR, `sendBeacon`)
|
|
146
|
-
| timers or frames from a previous file failing this one
|
|
147
|
-
| an assertion error in stderr, every test green and the run at 0
|
|
148
|
-
| a run getting slower the longer it goes, on `isolate: false`
|
|
149
|
-
| `JavaScript heap out of memory` on a suite of wide generated clients
|
|
150
|
-
| `Cannot read properties of undefined (reading 'now')`
|
|
151
|
-
|
|
|
152
|
-
|
|
|
153
|
-
|
|
|
154
|
-
|
|
|
155
|
-
|
|
|
156
|
-
|
|
|
157
|
-
|
|
|
158
|
-
| a
|
|
159
|
-
|
|
|
160
|
-
|
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
| a
|
|
164
|
-
| a
|
|
165
|
-
|
|
|
166
|
-
|
|
|
167
|
-
|
|
|
168
|
-
|
|
|
169
|
-
| `
|
|
170
|
-
|
|
|
171
|
-
| a
|
|
172
|
-
| a
|
|
173
|
-
| a `
|
|
174
|
-
| `
|
|
175
|
-
|
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
| a
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
182
|
-
| `
|
|
183
|
-
|
|
|
184
|
-
|
|
|
185
|
-
| `
|
|
186
|
-
|
|
|
187
|
-
|
|
|
188
|
-
|
|
|
189
|
-
| `
|
|
190
|
-
| `
|
|
191
|
-
|
|
|
129
|
+
| Situation | Helper |
|
|
130
|
+
| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
131
|
+
| a `signal()` / `computed()` field on the class under test | `mockSignalProp(obj, prop, initial)` — returns the writable |
|
|
132
|
+
| a resource field, when the HTTP round trip is not the point | `mockResourceProp(obj, prop, initial)` — `set` / `fail` / `loading` |
|
|
133
|
+
| the HTTP round trip _is_ the point | `expectRequest(url).flush(body)` — `/angular-http`, settling included |
|
|
134
|
+
| a node:test suite whose heap grows all run | `trackNodeMocks()` — `/node`, a private MockTracker |
|
|
135
|
+
| a Nest provider whose constructor keeps changing | `createNestUnit(Target, { expose })` — built from its DI metadata |
|
|
136
|
+
| asserting a resource's value _and_ status together | `registerResourceMatchers()` → `toHaveResourceValue` / `toBeLoading` |
|
|
137
|
+
| a callback or config object the code under test built | `captureArg<T>()` in the assertion, then read `.value` |
|
|
138
|
+
| an object the test already holds — a real service, a client, `TestBed.inject(X)` | `createSpyFromInstance(obj, config?)` — patches it in place, `restoreSpiedInstance` undoes it |
|
|
139
|
+
| a `calledWith` that is not matching and you cannot see why | `explainSpy(spy, 'method')` from `/diagnostics` — configs, calls, which hit which |
|
|
140
|
+
| a method that has to throw — for all calls, or for some arguments | `spy.m.failWith(err)` / `spy.m.calledWith(x).failWith(err)` — **not** `throwWith` |
|
|
141
|
+
| a `TestBed` spec on Vitest 4.1+, to drop `let` + `beforeEach` | `extendWithAutoSpies(test, { cart: CartService })` (`/angular`) |
|
|
142
|
+
| an `effect()` whose trigger is now a static signal | `runEffect(effectRef)` |
|
|
143
|
+
| the component constructs its own `IntersectionObserver` | `stubIntersectionObserver()` (+ `Resize` / `Mutation`) |
|
|
144
|
+
| a green run exiting 1 with `AbortError` under happy-dom | `setupAutoSpy({ blockNetwork: true })` |
|
|
145
|
+
| a real request going out of a unit run (`fetch`, XHR, `sendBeacon`) | `setupAutoSpy({ blockNetwork: true })` — `{ xhr: 'empty' }` for pings |
|
|
146
|
+
| timers or frames from a previous file failing this one | `setupAutoSpy({ strayTimers: true })` — mutes `--detect-async-leaks`, see below |
|
|
147
|
+
| an assertion error in stderr, every test green and the run at 0 | `setupAutoSpy({ strayRejections: true })` — zone.js swallowed it |
|
|
148
|
+
| a run getting slower the longer it goes, on `isolate: false` | `setupAutoSpy({ pruneMockRegistry: true })` — the mock registry |
|
|
149
|
+
| `JavaScript heap out of memory` on a suite of wide generated clients | `createSpyFromClass(X, { lazySpies: 'proxy' })` — one trap object, not a placeholder per method |
|
|
150
|
+
| `Cannot read properties of undefined (reading 'now')` | `restoreTimerGlobals` — on by default |
|
|
151
|
+
| `localStorage.setItem is not a function`, or it is `undefined` | `restoreWebStorage` — on by default; Vitest's global filter, Node 25+ |
|
|
152
|
+
| a spy handed to an API typed against the real class | `asInstance()` / `asSpy()` |
|
|
153
|
+
| the code under test does `new X()` (a global, a vendor SDK) | `mockConstructor(factory)` / `stubConstructor(obj, key, factory)` |
|
|
154
|
+
| `X is not a constructor`, with a stack in production code | same — a `vi.fn(() => …)` cannot serve `new` |
|
|
155
|
+
| waiting for a dynamic `import()` under fake timers | `settleDynamicImport(() => import('…'))` / `flushEventLoop()` |
|
|
156
|
+
| `addEventListener(…, { signal })` throwing about `EventTarget` | `stubAbortController()` |
|
|
157
|
+
| a suite ported from Jest's `fakeTimers.enableGlobally` | `setupAutoSpy({ globalFakeTimers: true })` |
|
|
158
|
+
| `toHaveBeenCalledBefore` across an auto-spy and a hand-written `vi.fn()` | `setSpyEngine('runner')` / `getSpyEngine()` — `/setup`, Vitest only |
|
|
159
|
+
| a nested `describe`'s `beforeAll` landing on real timers | `setupFakeTimers(cfg, { betweenTests: true })` |
|
|
160
|
+
| setup hooks applying to the first spec file of a worker only | the setup module is cached — run coverage with `--isolate` |
|
|
161
|
+
| `fakeAsync` inside `test.concurrent` | `installProxyZonePatch({ scope: 'callback' })` |
|
|
162
|
+
| an assertion containing a date | `mockSystemTime(iso)` — never `vi.spyOn(globalThis, 'Date')` |
|
|
163
|
+
| a spec asserting on tick _order_ under a frozen clock | `useCountingClock()` |
|
|
164
|
+
| a dependency declared in the component's own `providers` | `overrideComponentProvider(Cmp, Token)` — it verifies on the first fixture that the override applied |
|
|
165
|
+
| a double answering `undefined` for a method nobody configured | `{ strict: true }`, or `setupAutoSpy({ strict: true })` suite-wide |
|
|
166
|
+
| an `afterEach` that exists only to reset one spy | `using spy = createSpyFromClass(X)` — `[Symbol.dispose]` resets it |
|
|
167
|
+
| a dead NgModule import, dead `schemas`, an unflushed HTTP request | `enableAngularDiagnostics()` in the setup file, after `initTestEnvironment` |
|
|
168
|
+
| "which collaborators did this actually inject?" | `trackInjections([A, TOKEN])` — providers plus the record, not `vi.mock` |
|
|
169
|
+
| `NG0303` / `NG0301` / `NG0304` from an imported NgModule | `assertNgModuleScopes(Module)` — an AOT bundle stripped its scope |
|
|
170
|
+
| `Cannot read properties of undefined (reading 'provide')` in `di_setup` | `assertComponentDefIntact(Cmp)` — a barrel chunk left a hole in `ɵcmp` |
|
|
171
|
+
| a focus assertion failing as `expected false to deeply equal true` | `registerFocusMatchers()` + `expect(el).toHaveFocus()` |
|
|
172
|
+
| a collaborator passed as an argument, then asserted on | `autoMocked<T>()` — typed `T & Spy<T>` |
|
|
173
|
+
| a `<video>` / `<audio>`: `play()` throws, `duration` is `NaN` | `stubMediaElement({ duration })`, then `media.set(el, …)` |
|
|
174
|
+
| a `vi.mock()` that silently did nothing under a bundler | `assertMocked(ns, { specifier, exports })` |
|
|
175
|
+
| `No "default" export is defined on the mock` | `vi.mock('x', () => moduleNamespace({ … }))` |
|
|
176
|
+
| waiting for a `resource()` / an SDK to become ready | `flushEventLoopUntil(() => …, { label })` — budgeted, not tuned |
|
|
177
|
+
| `expected [ { at: 1, …(5) }, …(8) ] to deeply equal …` | `expect(diffByField(actual, expected)).toBeUndefined()` |
|
|
178
|
+
| a stub that works only in the first test of the file | `installPerTest(() => stub…())` — or install it in `beforeEach` |
|
|
179
|
+
| a library failing every other run after a `defineProperty` on DOM | `setupAutoSpy({ guardGlobals: 'throw' })` names the file |
|
|
180
|
+
| `Cannot set base providers because it has already been called` | `setupAngularTestEnv({ zoneless, initZone, initZoneless })` |
|
|
181
|
+
| a dependency behind an `InjectionToken`, with no class to spy | `provideAutoSpyForToken(TOKEN)` + `injectSpy(TOKEN)` |
|
|
182
|
+
| `Expected to be running in 'ProxyZone', but it was not found` | `import 'vitest-auto-spy/zone'` (needs `globals: true`) |
|
|
183
|
+
| `Property 'mockReturnValue' does not exist on type 'never'` | upgrade — the spy no longer collapses on an unreadable return type |
|
|
184
|
+
| a signal-valued getter that `gettersToSpyOn` will not accept | it accepts any key now; for a signal prefer `mockSignalProp` |
|
|
185
|
+
| five `asInstance(…)` in one call, found one per `tsc` run | `...asInstances(a, b, c, d, e)` |
|
|
186
|
+
| `nextWith` demanding `HttpEvent<T>` on a generated client | `asSpy<Client, { overload: 'first' }>(…)` / `Overload<M, 0>` |
|
|
187
|
+
| a fixture that needs a nested object built by its own call | `createMock<T>({ a: { b: 1 } })` — deep partial, still exact |
|
|
188
|
+
| the same 100-line model literal copied into eight specs (`TS1117`) | one `createFixtureFactory<T>(defaults)`; specs call it with what they change |
|
|
189
|
+
| `'params' in link` ladders, or a cast, to pick a union branch | `narrow.byKey(link, 'params')` / `narrow.observable(x)` |
|
|
190
|
+
| `{ ...modelInstance, flag: true }` losing every getter | `withOverrides(modelInstance, { flag: true })` |
|
|
191
|
+
| `NG0303` / `NG0304` / silence from a directive spec | `createDirectiveHost({ template, scope: [Module] })` |
|
|
192
|
+
| "did the migration lose a test?" with matching counters | `compareTestRuns(before, after)` |
|
|
192
193
|
|
|
193
194
|
## Rules that prevent most of the mistakes
|
|
194
195
|
|