@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/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
+ }
package/src/test.mts ADDED
@@ -0,0 +1,5 @@
1
+ import { run } from '@gjsify/unit';
2
+ import indexTestSuite from './index.spec.js';
3
+ import spyTestSuite from './spy.spec.js';
4
+
5
+ run({indexTestSuite, spyTestSuite});