@gjsify/unit 0.0.2
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/README.md +96 -0
- package/lib/cjs/index.js +390 -0
- package/lib/cjs/spy.js +158 -0
- package/lib/esm/index.js +360 -0
- package/lib/esm/spy.js +139 -0
- package/lib/types/index.d.ts +61 -0
- package/lib/types/spy.d.ts +54 -0
- package/package.json +59 -0
- package/src/index.spec.ts +262 -0
- package/src/index.ts +425 -0
- package/src/spy.spec.ts +641 -0
- package/src/spy.ts +246 -0
- package/src/test.mts +5 -0
- package/test.gjs.mjs +35632 -0
- package/test.node.mjs +1219 -0
- package/tsconfig.json +15 -0
- package/tsconfig.types.json +8 -0
package/src/spy.ts
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// https://github.com/mysticatea/spy
|
|
2
|
+
|
|
3
|
+
/** Spy. */
|
|
4
|
+
export type Spy<T extends (...args: any[]) => any> = T & Spy.CallInformation<T>
|
|
5
|
+
|
|
6
|
+
export namespace Spy {
|
|
7
|
+
/** Information for calls on a spy. */
|
|
8
|
+
export interface CallInformation<T extends (...args: any[]) => any> {
|
|
9
|
+
/** Information for each call. */
|
|
10
|
+
readonly calls: ReadonlyArray<Call<T>>
|
|
11
|
+
|
|
12
|
+
/** Information for each returned call. */
|
|
13
|
+
readonly returnedCalls: ReadonlyArray<ReturnedCall<T>>
|
|
14
|
+
|
|
15
|
+
/** Information for each thrown call. */
|
|
16
|
+
readonly thrownCalls: ReadonlyArray<ThrownCall<T>>
|
|
17
|
+
|
|
18
|
+
/** Information of the first call. */
|
|
19
|
+
readonly firstCall: Call<T> | null
|
|
20
|
+
|
|
21
|
+
/** Information of the last call. */
|
|
22
|
+
readonly lastCall: Call<T> | null
|
|
23
|
+
|
|
24
|
+
/** Information of the first returned call. */
|
|
25
|
+
readonly firstReturnedCall: ReturnedCall<T> | null
|
|
26
|
+
|
|
27
|
+
/** Information of the last returned call. */
|
|
28
|
+
readonly lastReturnedCall: ReturnedCall<T> | null
|
|
29
|
+
|
|
30
|
+
/** Information of the first thrown call. */
|
|
31
|
+
readonly firstThrownCall: ThrownCall<T> | null
|
|
32
|
+
|
|
33
|
+
/** Information of the last thrown call. */
|
|
34
|
+
readonly lastThrownCall: ThrownCall<T> | null
|
|
35
|
+
|
|
36
|
+
/** Reset calls. */
|
|
37
|
+
reset(): void
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Information for each call. */
|
|
41
|
+
export type Call<T extends (...args: any[]) => any> =
|
|
42
|
+
| ReturnedCall<T>
|
|
43
|
+
| ThrownCall<T>
|
|
44
|
+
|
|
45
|
+
/** Information for each returned call. */
|
|
46
|
+
export interface ReturnedCall<T extends (...args: any[]) => any> {
|
|
47
|
+
type: "return"
|
|
48
|
+
this: This<T>
|
|
49
|
+
arguments: Parameters<T>
|
|
50
|
+
return: ReturnType<T>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Information for each thrown call. */
|
|
54
|
+
export interface ThrownCall<T extends (...args: any[]) => any> {
|
|
55
|
+
type: "throw"
|
|
56
|
+
this: This<T>
|
|
57
|
+
arguments: Parameters<T>
|
|
58
|
+
throw: any
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type This<T> = T extends (this: infer TT, ...args: any[]) => any
|
|
63
|
+
? TT
|
|
64
|
+
: undefined
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create a spy.
|
|
68
|
+
*/
|
|
69
|
+
export function spy(): Spy<() => void>
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create a spy with a certain behavior.
|
|
73
|
+
* @param f The function of the spy's behavior.
|
|
74
|
+
*/
|
|
75
|
+
export function spy<T extends (...args: any[]) => any>(f: T): Spy<T>
|
|
76
|
+
|
|
77
|
+
// Implementation
|
|
78
|
+
export function spy<T extends (...args: any[]) => any>(f?: T): Spy<T> {
|
|
79
|
+
const calls = [] as Spy.Call<T>[]
|
|
80
|
+
|
|
81
|
+
function spy(this: This<T>, ...args: Parameters<T>): ReturnType<T> {
|
|
82
|
+
let retv: ReturnType<T>
|
|
83
|
+
try {
|
|
84
|
+
retv = f ? f.apply(this, args) : undefined
|
|
85
|
+
} catch (error) {
|
|
86
|
+
calls.push({
|
|
87
|
+
type: "throw",
|
|
88
|
+
this: this,
|
|
89
|
+
arguments: args,
|
|
90
|
+
throw: error,
|
|
91
|
+
})
|
|
92
|
+
throw error
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
calls.push({
|
|
96
|
+
type: "return",
|
|
97
|
+
this: this,
|
|
98
|
+
arguments: args,
|
|
99
|
+
return: retv,
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
return retv
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
Object.defineProperties(spy, {
|
|
106
|
+
calls: descriptors.calls(calls),
|
|
107
|
+
returnedCalls: descriptors.returnedCalls,
|
|
108
|
+
thrownCalls: descriptors.thrownCalls,
|
|
109
|
+
firstCall: descriptors.firstCall,
|
|
110
|
+
lastCall: descriptors.lastCall,
|
|
111
|
+
firstReturnedCall: descriptors.firstReturnedCall,
|
|
112
|
+
lastReturnedCall: descriptors.lastReturnedCall,
|
|
113
|
+
firstThrownCall: descriptors.firstThrownCall,
|
|
114
|
+
lastThrownCall: descriptors.lastThrownCall,
|
|
115
|
+
reset: descriptors.reset,
|
|
116
|
+
toString: descriptors.toString(f),
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
return spy as Spy<T>
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const descriptors = {
|
|
123
|
+
calls(value: ReadonlyArray<Spy.Call<any>>): PropertyDescriptor {
|
|
124
|
+
return { value, configurable: true }
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
returnedCalls: {
|
|
128
|
+
get: function returnedCalls(
|
|
129
|
+
this: Spy.CallInformation<any>,
|
|
130
|
+
): ReadonlyArray<Spy.ReturnedCall<any>> {
|
|
131
|
+
return this.calls.filter(isReturned)
|
|
132
|
+
},
|
|
133
|
+
configurable: true,
|
|
134
|
+
} as PropertyDescriptor,
|
|
135
|
+
|
|
136
|
+
thrownCalls: {
|
|
137
|
+
get: function thrownCalls(
|
|
138
|
+
this: Spy.CallInformation<any>,
|
|
139
|
+
): ReadonlyArray<Spy.ThrownCall<any>> {
|
|
140
|
+
return this.calls.filter(isThrown)
|
|
141
|
+
},
|
|
142
|
+
configurable: true,
|
|
143
|
+
} as PropertyDescriptor,
|
|
144
|
+
|
|
145
|
+
firstCall: {
|
|
146
|
+
get: function firstCall(
|
|
147
|
+
this: Spy.CallInformation<any>,
|
|
148
|
+
): Spy.Call<any> | null {
|
|
149
|
+
return this.calls[0] || null
|
|
150
|
+
},
|
|
151
|
+
configurable: true,
|
|
152
|
+
} as PropertyDescriptor,
|
|
153
|
+
|
|
154
|
+
lastCall: {
|
|
155
|
+
get: function lastCall(
|
|
156
|
+
this: Spy.CallInformation<any>,
|
|
157
|
+
): Spy.Call<any> | null {
|
|
158
|
+
return this.calls[this.calls.length - 1] || null
|
|
159
|
+
},
|
|
160
|
+
configurable: true,
|
|
161
|
+
} as PropertyDescriptor,
|
|
162
|
+
|
|
163
|
+
firstReturnedCall: {
|
|
164
|
+
get: function firstReturnedCall(
|
|
165
|
+
this: Spy.CallInformation<any>,
|
|
166
|
+
): Spy.ReturnedCall<any> | null {
|
|
167
|
+
for (let i = 0; i < this.calls.length; ++i) {
|
|
168
|
+
const call = this.calls[i]
|
|
169
|
+
if (isReturned(call)) {
|
|
170
|
+
return call
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null
|
|
174
|
+
},
|
|
175
|
+
configurable: true,
|
|
176
|
+
} as PropertyDescriptor,
|
|
177
|
+
|
|
178
|
+
lastReturnedCall: {
|
|
179
|
+
get: function lastReturnedCall(
|
|
180
|
+
this: Spy.CallInformation<any>,
|
|
181
|
+
): Spy.ReturnedCall<any> | null {
|
|
182
|
+
for (let i = this.calls.length - 1; i >= 0; --i) {
|
|
183
|
+
const call = this.calls[i]
|
|
184
|
+
if (isReturned(call)) {
|
|
185
|
+
return call
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return null
|
|
189
|
+
},
|
|
190
|
+
configurable: true,
|
|
191
|
+
} as PropertyDescriptor,
|
|
192
|
+
|
|
193
|
+
firstThrownCall: {
|
|
194
|
+
get: function firstThrownCall(
|
|
195
|
+
this: Spy.CallInformation<any>,
|
|
196
|
+
): Spy.ThrownCall<any> | null {
|
|
197
|
+
for (let i = 0; i < this.calls.length; ++i) {
|
|
198
|
+
const call = this.calls[i]
|
|
199
|
+
if (isThrown(call)) {
|
|
200
|
+
return call
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return null
|
|
204
|
+
},
|
|
205
|
+
configurable: true,
|
|
206
|
+
} as PropertyDescriptor,
|
|
207
|
+
|
|
208
|
+
lastThrownCall: {
|
|
209
|
+
get: function lastThrownCall(
|
|
210
|
+
this: Spy.CallInformation<any>,
|
|
211
|
+
): Spy.ThrownCall<any> | null {
|
|
212
|
+
for (let i = this.calls.length - 1; i >= 0; --i) {
|
|
213
|
+
const call = this.calls[i]
|
|
214
|
+
if (isThrown(call)) {
|
|
215
|
+
return call
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return null
|
|
219
|
+
},
|
|
220
|
+
configurable: true,
|
|
221
|
+
} as PropertyDescriptor,
|
|
222
|
+
|
|
223
|
+
reset: {
|
|
224
|
+
value: function reset(this: Spy.CallInformation<any>): void {
|
|
225
|
+
;(this.calls as any[]).length = 0
|
|
226
|
+
},
|
|
227
|
+
configurable: true,
|
|
228
|
+
} as PropertyDescriptor,
|
|
229
|
+
|
|
230
|
+
toString(f: ((...args: any[]) => any) | undefined): PropertyDescriptor {
|
|
231
|
+
return {
|
|
232
|
+
value: function toString() {
|
|
233
|
+
return `/* The spy of */ ${f ? f.toString() : "function(){}"}`
|
|
234
|
+
},
|
|
235
|
+
configurable: true,
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function isReturned(call: Spy.Call<any>): call is Spy.ReturnedCall<any> {
|
|
241
|
+
return call.type === "return"
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function isThrown(call: Spy.Call<any>): call is Spy.ThrownCall<any> {
|
|
245
|
+
return call.type === "throw"
|
|
246
|
+
}
|