vitest-auto-spy 4.2.0 → 4.3.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 +89 -2
- package/README.md +135 -6
- package/dist/angular.d.ts +2 -2
- package/dist/angular.js +276 -7
- package/dist/bun-angular.d.ts +3 -3
- package/dist/bun-angular.js +6 -6
- package/dist/bun.d.ts +72 -2
- package/dist/bun.js +4 -4
- package/dist/{chunk-DSIYS6Y5.js → chunk-D5LDT3W4.js} +1 -1
- package/dist/{chunk-2ZG27FVL.js → chunk-EMASXQDZ.js} +1 -1
- package/dist/{chunk-4KK3ES4P.js → chunk-FFKN6QKI.js} +22 -0
- package/dist/{chunk-4KCYFAJX.js → chunk-FYFF67EN.js} +23 -7
- package/dist/{chunk-5URFSYDT.js → chunk-GAY6LNAM.js} +300 -6
- package/dist/{chunk-WQZ2RKS7.js → chunk-KYAJCFUA.js} +1 -1
- package/dist/{chunk-KEKKEIRG.js → chunk-Z4VRMCW2.js} +1 -1
- package/dist/cli.js +606 -16
- package/dist/console.js +2 -2
- package/dist/diagnostics.d.ts +31 -1
- package/dist/diagnostics.js +350 -1
- package/dist/{expect-emission-BDuFuqTb.d.ts → expect-emission-Dy5CvsF2.d.ts} +27 -25
- package/dist/index.d.ts +2 -2
- package/dist/index.js +444 -111
- package/dist/jasmine.js +4 -4
- package/dist/nestjs.js +5 -5
- package/dist/node.cjs +272 -111
- package/dist/node.d.cts +2 -0
- package/dist/node.d.ts +2 -2
- package/dist/node.js +17 -5
- package/dist/react.d.ts +2 -2
- package/dist/react.js +5 -5
- package/dist/setup.js +4 -4
- package/dist/shared-state.js +4 -275
- package/dist/svelte.d.ts +2 -2
- package/dist/svelte.js +5 -5
- package/dist/vue.d.ts +2 -2
- package/dist/vue.js +6 -6
- package/dist/{zoneless-DGwkbWKm.d.ts → zoneless-LsBDcSNZ.d.ts} +1 -1
- package/package.json +6 -2
- package/skills/vitest-auto-spy/SKILL.md +4 -1
|
@@ -1,11 +1,65 @@
|
|
|
1
|
-
import { mockValueProp } from './chunk-
|
|
2
|
-
import { createSpyFromClass, createProxyPropStore, describeStoredProp, dropStoredProp, storeDefinedProp, writeStoredAccessor, writeStoredValue, readStoredAccessor, NOT_STORED, disposeAutoSpy, isProtocolKey, isDeletedProp } from './chunk-
|
|
3
|
-
import { defineHelper, serializeValue, createFunctionSpy } from './chunk-
|
|
1
|
+
import { redefineFailure, mockValueProp, mockAccessorsProp } from './chunk-D5LDT3W4.js';
|
|
2
|
+
import { resolveConfiguration, mergeMethodNames, getCallableMemberNames, applyReturns, createSpyFromClass, resolveAccessors, createAccessorsSpies, createProxyPropStore, describeStoredProp, dropStoredProp, storeDefinedProp, writeStoredAccessor, writeStoredValue, readStoredAccessor, NOT_STORED, disposeAutoSpy, isProtocolKey, isDeletedProp } from './chunk-FYFF67EN.js';
|
|
3
|
+
import { defineHelper, serializeValue, resolveUnstubbedGuard, createFunctionSpy } from './chunk-FFKN6QKI.js';
|
|
4
4
|
import { DISPOSE } from './chunk-PC3FAZT3.js';
|
|
5
5
|
import { DEEP_CHILDREN } from './chunk-XRVVZXOM.js';
|
|
6
6
|
import { withDocs, DOCS_LINKS } from './chunk-B45HLBA4.js';
|
|
7
|
-
import { getMockAdapter } from './shared-state.js';
|
|
8
|
-
export { describeDuplicateCopies,
|
|
7
|
+
import { requireObservableSupport, getMockAdapter, emissionTimeout } from './shared-state.js';
|
|
8
|
+
export { describeDuplicateCopies, getPackageCopies, setEmissionTimeout } from './shared-state.js';
|
|
9
|
+
|
|
10
|
+
var installedSpies = /* @__PURE__ */ new WeakMap();
|
|
11
|
+
function constructorName(instance) {
|
|
12
|
+
const constructor = Reflect.get(instance, "constructor");
|
|
13
|
+
return typeof constructor === "function" ? constructor.name : void 0;
|
|
14
|
+
}
|
|
15
|
+
function installMember(instance, name, value, restores) {
|
|
16
|
+
restores.push(mockValueProp(instance, name, value));
|
|
17
|
+
Object.defineProperty(instance, name, { enumerable: true });
|
|
18
|
+
}
|
|
19
|
+
function installAccessorSpies(instance, config, restores) {
|
|
20
|
+
const { getters, setters } = resolveAccessors(instance, config);
|
|
21
|
+
[.../* @__PURE__ */ new Set([...getters, ...setters])].forEach((name) => restores.push(mockAccessorsProp(instance, name)));
|
|
22
|
+
restores.push(mockValueProp(instance, "accessorSpies", void 0));
|
|
23
|
+
createAccessorsSpies(instance, getters, setters);
|
|
24
|
+
}
|
|
25
|
+
function disposeSpiedInstance() {
|
|
26
|
+
restoreSpiedInstance(this);
|
|
27
|
+
}
|
|
28
|
+
function restoreSpiedInstance(instance) {
|
|
29
|
+
const restores = installedSpies.get(instance);
|
|
30
|
+
if (!restores) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
installedSpies.delete(instance);
|
|
34
|
+
[...restores].reverse().forEach((restore) => restore());
|
|
35
|
+
}
|
|
36
|
+
function createSpyFromInstance(instance, methodsToSpyOnOrConfig) {
|
|
37
|
+
if (!Object.isExtensible(instance)) {
|
|
38
|
+
throw redefineFailure(
|
|
39
|
+
"Cannot spy on this instance in place: it is not extensible, so its members cannot be replaced.",
|
|
40
|
+
instance,
|
|
41
|
+
void 0
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const config = resolveConfiguration(methodsToSpyOnOrConfig);
|
|
45
|
+
const className = constructorName(instance);
|
|
46
|
+
const unstubbed = resolveUnstubbedGuard(className, config);
|
|
47
|
+
const restores = installedSpies.get(instance) ?? [];
|
|
48
|
+
installedSpies.set(instance, restores);
|
|
49
|
+
const methodNames = mergeMethodNames(
|
|
50
|
+
config.onlyMethodsToSpyOn.length > 0 ? config.onlyMethodsToSpyOn : getCallableMemberNames(instance),
|
|
51
|
+
config
|
|
52
|
+
);
|
|
53
|
+
methodNames.forEach((name) => installMember(instance, name, createFunctionSpy(name, unstubbed), restores));
|
|
54
|
+
config.observablePropsToSpyOn.forEach((name) => installMember(instance, name, requireObservableSupport().createPropSpy(), restores));
|
|
55
|
+
installAccessorSpies(instance, config, restores);
|
|
56
|
+
applyReturns(instance, `createSpyFromInstance(${className ?? "object"})`, config.returns);
|
|
57
|
+
for (const key of Reflect.ownKeys(config.overrides)) {
|
|
58
|
+
installMember(instance, key, Reflect.get(config.overrides, key), restores);
|
|
59
|
+
}
|
|
60
|
+
restores.push(mockValueProp(instance, DISPOSE, disposeSpiedInstance));
|
|
61
|
+
return instance;
|
|
62
|
+
}
|
|
9
63
|
|
|
10
64
|
// src/lib/create-mock.ts
|
|
11
65
|
function createMock(partial) {
|
|
@@ -202,6 +256,246 @@ function captureArg() {
|
|
|
202
256
|
return new Captor();
|
|
203
257
|
}
|
|
204
258
|
|
|
259
|
+
// src/lib/error-anchor.ts
|
|
260
|
+
var OWN_FAILURE = /* @__PURE__ */ Symbol("vitest-auto-spy.own-failure");
|
|
261
|
+
function ownFailure(message, options) {
|
|
262
|
+
const error = new Error(message, options);
|
|
263
|
+
Object.defineProperty(error, OWN_FAILURE, { value: true });
|
|
264
|
+
return error;
|
|
265
|
+
}
|
|
266
|
+
function anchorOf(stack, boundaryName) {
|
|
267
|
+
const frames = framesAfter(frameLines(stack), boundaryName);
|
|
268
|
+
return (error) => anchor(frames, error);
|
|
269
|
+
}
|
|
270
|
+
function captureAnchor(boundary, host = Error) {
|
|
271
|
+
const holder = new Error();
|
|
272
|
+
const capture = host.captureStackTrace;
|
|
273
|
+
if (capture) {
|
|
274
|
+
capture(holder, boundary);
|
|
275
|
+
return anchorOf(holder.stack, "");
|
|
276
|
+
}
|
|
277
|
+
return anchorOf(holder.stack, boundary.name);
|
|
278
|
+
}
|
|
279
|
+
function anchor(frames, error) {
|
|
280
|
+
if (frames.length === 0 || Reflect.get(error, OWN_FAILURE) !== true) {
|
|
281
|
+
return error;
|
|
282
|
+
}
|
|
283
|
+
error.stack = [`${error.name}: ${error.message}`, ...frames].join("\n");
|
|
284
|
+
return error;
|
|
285
|
+
}
|
|
286
|
+
function frameLines(stack) {
|
|
287
|
+
const lines = String(stack).split("\n");
|
|
288
|
+
const first = lines.findIndex(isFrame);
|
|
289
|
+
return first < 0 ? [] : lines.slice(first);
|
|
290
|
+
}
|
|
291
|
+
function isFrame(line) {
|
|
292
|
+
return /^\s*at\s/.test(line) || /^\S*@/.test(line);
|
|
293
|
+
}
|
|
294
|
+
function framesAfter(frames, boundaryName) {
|
|
295
|
+
const boundary = boundaryName === "" ? -1 : frames.findIndex((frame) => frame.includes(boundaryName));
|
|
296
|
+
return boundary < 0 ? frames : frames.slice(boundary + 1);
|
|
297
|
+
}
|
|
298
|
+
function subscribeToSource(source$, observer) {
|
|
299
|
+
if (isRxjsSource(source$)) {
|
|
300
|
+
return source$.subscribe(observer);
|
|
301
|
+
}
|
|
302
|
+
return source$.subscribe(asHybridObserver(observer));
|
|
303
|
+
}
|
|
304
|
+
function isRxjsSource(source$) {
|
|
305
|
+
return typeof Reflect.get(source$, "pipe") === "function";
|
|
306
|
+
}
|
|
307
|
+
function asHybridObserver(observer) {
|
|
308
|
+
const listener = (value) => observer.next(value);
|
|
309
|
+
return Object.assign(listener, observer);
|
|
310
|
+
}
|
|
311
|
+
var setTimer = globalThis.setTimeout.bind(globalThis);
|
|
312
|
+
var clearTimer = globalThis.clearTimeout.bind(globalThis);
|
|
313
|
+
function describeSource(options) {
|
|
314
|
+
return options?.label ?? "the observable";
|
|
315
|
+
}
|
|
316
|
+
function accepted(values, options) {
|
|
317
|
+
const past = options?.skip ? values.slice(options.skip) : values;
|
|
318
|
+
const until = options?.until;
|
|
319
|
+
return until ? past.filter((value) => until(value)) : past;
|
|
320
|
+
}
|
|
321
|
+
function describeExpectation(count, options) {
|
|
322
|
+
return options?.until ? `${count} matching` : String(count);
|
|
323
|
+
}
|
|
324
|
+
function subscribeAndCollect(source$, options, settle, handlers) {
|
|
325
|
+
const values = [];
|
|
326
|
+
let subscription = void 0;
|
|
327
|
+
let stopped = false;
|
|
328
|
+
const stop = () => {
|
|
329
|
+
stopped = true;
|
|
330
|
+
clearTimer(timer);
|
|
331
|
+
subscription?.unsubscribe();
|
|
332
|
+
};
|
|
333
|
+
const timeout = options?.timeout ?? emissionTimeout();
|
|
334
|
+
const timer = timeout > 0 ? setTimer(() => {
|
|
335
|
+
stop();
|
|
336
|
+
settle.reject(handlers.onTimeout(values, options, timeout));
|
|
337
|
+
}, timeout) : void 0;
|
|
338
|
+
subscription = subscribeToSource(source$, {
|
|
339
|
+
next: (value) => {
|
|
340
|
+
values.push(value);
|
|
341
|
+
if (handlers.isDone(values)) {
|
|
342
|
+
stop();
|
|
343
|
+
settle.resolve(values);
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
error: (error) => {
|
|
347
|
+
stop();
|
|
348
|
+
handlers.onError(error, options, settle);
|
|
349
|
+
},
|
|
350
|
+
complete: () => {
|
|
351
|
+
stop();
|
|
352
|
+
handlers.onComplete(values);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
if (stopped) {
|
|
356
|
+
subscription.unsubscribe();
|
|
357
|
+
}
|
|
358
|
+
if (!stopped) {
|
|
359
|
+
options?.advance?.();
|
|
360
|
+
}
|
|
361
|
+
return { values, stop };
|
|
362
|
+
}
|
|
363
|
+
function anchoredRejecter(reject, anchor2) {
|
|
364
|
+
return (error) => reject(anchor2(error));
|
|
365
|
+
}
|
|
366
|
+
function timeoutError(values, options, waited) {
|
|
367
|
+
return ownFailure(
|
|
368
|
+
`${describeSource(options)} did not emit within ${waited} ms (${values.length} emission(s) received). Either the stream never fired \u2014 check the trigger and any provider spy feeding it \u2014 or it is slower than the timeout; raise it with \`{ timeout: \u2026 }\`. This wait is real time even under fake timers, on purpose: a virtual watchdog would race the timers your spec advances. Lower it with \`setEmissionTimeout(100)\` in the setup file rather than disabling it with \`{ timeout: 0 }\`, which leaves the next silent stream with no message at all.`
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
function rejectAsSourceError(error, options, settle) {
|
|
372
|
+
settle.reject(ownFailure(`${describeSource(options)} errored instead of emitting: ${String(error)}`, { cause: error }));
|
|
373
|
+
}
|
|
374
|
+
function completedError(values, expected, options) {
|
|
375
|
+
return ownFailure(
|
|
376
|
+
`${describeSource(options)} completed after ${values.length} emission(s), expected ${describeExpectation(expected, options)}. A completed-but-empty stream is the usual sign that the value was produced before the subscription.`
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
function expectEmission(source$, options) {
|
|
380
|
+
return collectEmissions(source$, 1, options, captureAnchor(expectEmission)).then((values) => firstOf(values));
|
|
381
|
+
}
|
|
382
|
+
function firstOf(values) {
|
|
383
|
+
return values[0];
|
|
384
|
+
}
|
|
385
|
+
function expectEmissions(source$, count, options) {
|
|
386
|
+
return collectEmissions(source$, count, options, captureAnchor(expectEmissions));
|
|
387
|
+
}
|
|
388
|
+
function collectEmissions(source$, count, options, anchor2) {
|
|
389
|
+
return new Promise((resolve, reject) => {
|
|
390
|
+
const fail = anchoredRejecter(reject, anchor2);
|
|
391
|
+
subscribeAndCollect(
|
|
392
|
+
source$,
|
|
393
|
+
options,
|
|
394
|
+
// Resolved with everything that arrived, then narrowed to the accepted ones: the collector's
|
|
395
|
+
// job is to record, and `skip` / `until` decide what the caller is handed.
|
|
396
|
+
{ resolve: (values) => resolve(accepted(values, options).slice(0, count)), reject: fail },
|
|
397
|
+
{
|
|
398
|
+
isDone: (values) => accepted(values, options).length >= count,
|
|
399
|
+
onComplete: (values) => fail(completedError(values, count, options)),
|
|
400
|
+
onTimeout: timeoutError,
|
|
401
|
+
onError: rejectAsSourceError
|
|
402
|
+
}
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
function expectNoEmission(source$, options) {
|
|
407
|
+
const quietFor = options?.timeout ?? 0;
|
|
408
|
+
const anchor2 = captureAnchor(expectNoEmission);
|
|
409
|
+
let quietWindow = void 0;
|
|
410
|
+
return new Promise((resolve, reject) => {
|
|
411
|
+
const fail = anchoredRejecter(reject, anchor2);
|
|
412
|
+
const collector = subscribeAndCollect(
|
|
413
|
+
source$,
|
|
414
|
+
{ ...options, timeout: 0 },
|
|
415
|
+
{
|
|
416
|
+
resolve: (emitted) => fail(unexpectedEmissionError(accepted(emitted, options), options)),
|
|
417
|
+
reject: fail
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
isDone: (values) => accepted(values, options).length > 0,
|
|
421
|
+
onComplete: () => void 0,
|
|
422
|
+
// Never reached — the watchdog is off (`timeout: 0`) and the quiet window below is this
|
|
423
|
+
// helper's own timer. Named rather than inlined so it is not an uncalled arrow.
|
|
424
|
+
onTimeout: timeoutError,
|
|
425
|
+
onError: rejectAsSourceError
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
quietWindow = setTimer(() => {
|
|
429
|
+
collector.stop();
|
|
430
|
+
resolve();
|
|
431
|
+
}, quietFor);
|
|
432
|
+
}).finally(() => {
|
|
433
|
+
clearTimer(quietWindow);
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
function expectCompletion(source$, options) {
|
|
437
|
+
const anchor2 = captureAnchor(expectCompletion);
|
|
438
|
+
return new Promise((resolve, reject) => {
|
|
439
|
+
subscribeAndCollect(
|
|
440
|
+
source$,
|
|
441
|
+
options,
|
|
442
|
+
{ resolve, reject: anchoredRejecter(reject, anchor2) },
|
|
443
|
+
{
|
|
444
|
+
// Emissions are collected for the failure message, but only completion settles this.
|
|
445
|
+
isDone: staysOpen,
|
|
446
|
+
onComplete: resolve,
|
|
447
|
+
onTimeout: notCompletedError,
|
|
448
|
+
onError: rejectAsNotCompleted
|
|
449
|
+
}
|
|
450
|
+
);
|
|
451
|
+
}).then(() => void 0);
|
|
452
|
+
}
|
|
453
|
+
function staysOpen() {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
function notCompletedError(values, options, waited) {
|
|
457
|
+
return ownFailure(
|
|
458
|
+
`${describeSource(options)} did not complete within ${waited} ms (${values.length} emission(s) received). A stream that keeps running usually means the completing operator never ran \u2014 check \`take\`, \`first\`, \`takeUntil\`, or a Subject nobody calls \`complete()\` on. The wait is real time even under fake timers; raise it with \`{ timeout: \u2026 }\`, or lower the suite default with \`setEmissionTimeout(\u2026)\`.`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
function rejectAsNotCompleted(error, options, settle) {
|
|
462
|
+
settle.reject(ownFailure(`${describeSource(options)} errored instead of completing: ${String(error)}`, { cause: error }));
|
|
463
|
+
}
|
|
464
|
+
function expectError(source$, options) {
|
|
465
|
+
const anchor2 = captureAnchor(expectError);
|
|
466
|
+
return new Promise((resolve, reject) => {
|
|
467
|
+
const fail = anchoredRejecter(reject, anchor2);
|
|
468
|
+
subscribeAndCollect(
|
|
469
|
+
source$,
|
|
470
|
+
options,
|
|
471
|
+
{ resolve, reject: fail },
|
|
472
|
+
{
|
|
473
|
+
// Only `error` settles this one: an emission is not the answer, and neither is completion.
|
|
474
|
+
isDone: staysOpen,
|
|
475
|
+
onComplete: (values) => fail(completedWithoutErrorError(values, options)),
|
|
476
|
+
onTimeout: notErroredError,
|
|
477
|
+
onError: resolveWithError
|
|
478
|
+
}
|
|
479
|
+
);
|
|
480
|
+
}).then((values) => firstOf(values));
|
|
481
|
+
}
|
|
482
|
+
function completedWithoutErrorError(values, options) {
|
|
483
|
+
return ownFailure(
|
|
484
|
+
`${describeSource(options)} completed after ${values.length} emission(s) without erroring, but an error was expected. Check that the failure path is the one the spec set up \u2014 a spy configured with \`resolveWith\`/\`nextWith\` rather than \`rejectWith\`/\`throwWith\` produces exactly this.`
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
function notErroredError(values, options, waited) {
|
|
488
|
+
return ownFailure(
|
|
489
|
+
`${describeSource(options)} did not error within ${waited} ms (${values.length} emission(s) received). The stream is still running: nothing failed, and nothing completed either.`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
function resolveWithError(error, _options, settle) {
|
|
493
|
+
settle.resolve([error]);
|
|
494
|
+
}
|
|
495
|
+
function unexpectedEmissionError(values, options) {
|
|
496
|
+
return ownFailure(`${describeSource(options)} emitted ${serializeValue(values[0])} but was expected to stay silent.`);
|
|
497
|
+
}
|
|
498
|
+
|
|
205
499
|
// src/lib/spy-typing.ts
|
|
206
500
|
function asInstance(spy) {
|
|
207
501
|
return spy;
|
|
@@ -428,4 +722,4 @@ function isLenientKey(property) {
|
|
|
428
722
|
return typeof property === "string" && property !== "then";
|
|
429
723
|
}
|
|
430
724
|
|
|
431
|
-
export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, flushEventLoop, flushEventLoopUntil, mockConstructor, mockDeep, moduleNamespace, narrow, settleDynamicImport, stubConstructor, withOverrides };
|
|
725
|
+
export { asInstance, asInstances, asSpy, assertMocked, captureArg, createFixture, createFixtureFactory, createMock, createSpyClass, createSpyFromInstance, expectCompletion, expectEmission, expectEmissions, expectError, expectNoEmission, flushEventLoop, flushEventLoopUntil, mockConstructor, mockDeep, moduleNamespace, narrow, restoreSpiedInstance, settleDynamicImport, stubConstructor, withOverrides };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SWEEP_SENTINEL } from './chunk-YHDBRBGU.js';
|
|
2
|
-
import { isThenable, FAST_SPY_BRAND } from './chunk-
|
|
2
|
+
import { isThenable, FAST_SPY_BRAND } from './chunk-FFKN6QKI.js';
|
|
3
3
|
import { DISPOSE } from './chunk-PC3FAZT3.js';
|
|
4
4
|
import { vi } from 'vitest';
|
|
5
5
|
import { guardAccessorSpies } from './shared-state.js';
|