-
-
Notifications
You must be signed in to change notification settings - Fork 9.5k
/
Copy pathspy.ts
126 lines (112 loc) · 4.47 KB
/
spy.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/* eslint-disable @typescript-eslint/no-shadow */
import type { Mock as MockV2, MockInstance } from '@vitest/spy';
import {
spyOn as vitestSpyOn,
isMockFunction,
fn as vitestFn,
mocks,
type MaybeMocked,
type MaybeMockedDeep,
type MaybePartiallyMocked,
type MaybePartiallyMockedDeep,
} from '@vitest/spy';
import type { SpyInternalImpl } from 'tinyspy';
import * as tinyspy from 'tinyspy';
export type * from '@vitest/spy';
export { isMockFunction, mocks };
type Listener = (mock: MockInstance, args: unknown[]) => void;
const listeners = new Set<Listener>();
export function onMockCall(callback: Listener): () => void {
listeners.add(callback);
return () => void listeners.delete(callback);
}
// @ts-expect-error Make sure we export the exact same type as @vitest/spy
export const spyOn: typeof vitestSpyOn = (...args) => {
const mock = vitestSpyOn(...(args as Parameters<typeof vitestSpyOn>));
return reactiveMock(mock);
};
type Procedure = (...args: any[]) => any;
// TODO: Remove in 9.0
export type Mock<T extends Procedure | any[], R = any> = T extends Procedure
? MockV2<T>
: T extends any[]
? MockV2<(...args: T) => R>
: never;
// V2
export function fn<T extends Procedure = Procedure>(implementation?: T): Mock<T>;
// TODO: Remove in 9.0
// V1
export function fn<TArgs extends any[] = any, R = any>(): Mock<(...args: TArgs) => R>;
export function fn<TArgs extends any[] = any[], R = any>(
implementation: (...args: TArgs) => R
): Mock<(...args: TArgs) => R>;
export function fn<TArgs extends any[] = any[], R = any>(
implementation?: (...args: TArgs) => R
): Mock<(...args: TArgs) => R>;
export function fn(implementation?: Procedure) {
const mock = implementation ? vitestFn(implementation) : vitestFn();
return reactiveMock(mock);
}
function reactiveMock(mock: MockInstance) {
const reactive = listenWhenCalled(mock);
const originalMockImplementation = reactive.mockImplementation.bind(null);
reactive.mockImplementation = (fn) => listenWhenCalled(originalMockImplementation(fn));
return reactive;
}
function listenWhenCalled(mock: MockInstance) {
const state = tinyspy.getInternalState(mock as unknown as SpyInternalImpl);
const impl = state.impl;
state.willCall(function (this: unknown, ...args) {
listeners.forEach((listener) => listener(mock, args));
return impl?.apply(this, args);
});
return mock;
}
/**
* Calls [`.mockClear()`](https://vitest.dev/api/mock#mockclear) on every mocked function. This will only empty `.mock` state, it will not reset implementation.
*
* It is useful if you need to clean up mock between different assertions.
*/
export function clearAllMocks() {
mocks.forEach((spy) => spy.mockClear());
}
/**
* Calls [`.mockReset()`](https://vitest.dev/api/mock#mockreset) on every mocked function. This will empty `.mock` state, reset "once" implementations and force the base implementation to return `undefined` when invoked.
*
* This is useful when you want to completely reset a mock to the default state.
*/
export function resetAllMocks() {
mocks.forEach((spy) => spy.mockReset());
}
/**
* Calls [`.mockRestore()`](https://vitest.dev/api/mock#mockrestore) on every mocked function. This will restore all original implementations.
*/
export function restoreAllMocks() {
mocks.forEach((spy) => spy.mockRestore());
}
/**
* Type helper for TypeScript. Just returns the object that was passed.
*
* When `partial` is `true` it will expect a `Partial<T>` as a return value. By default, this will only make TypeScript believe that
* the first level values are mocked. You can pass down `{ deep: true }` as a second argument to tell TypeScript that the whole object is mocked, if it actually is.
*
* @param item Anything that can be mocked
* @param deep If the object is deeply mocked
* @param options If the object is partially or deeply mocked
*/
export function mocked<T>(item: T, deep?: false): MaybeMocked<T>;
export function mocked<T>(item: T, deep: true): MaybeMockedDeep<T>;
export function mocked<T>(item: T, options: { partial?: false; deep?: false }): MaybeMocked<T>;
export function mocked<T>(item: T, options: { partial?: false; deep: true }): MaybeMockedDeep<T>;
export function mocked<T>(
item: T,
options: { partial: true; deep?: false }
): MaybePartiallyMocked<T>;
export function mocked<T>(
item: T,
options: { partial: true; deep: true }
): MaybePartiallyMockedDeep<T>;
export function mocked<T>(item: T): MaybeMocked<T>;
export function mocked<T>(item: T, _options = {}): MaybeMocked<T> {
return item as any;
}