prostub 1.1.0 → 1.2.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/README.md +115 -2
- package/index.d.ts +465 -94
- package/index.js +1 -582
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,582 +1 @@
|
|
|
1
|
-
const mock$1 =
|
|
2
|
-
const stub$1 = Symbol("stub");
|
|
3
|
-
|
|
4
|
-
function spy(realObject) {
|
|
5
|
-
const stubs = {};
|
|
6
|
-
return new Proxy(realObject, {
|
|
7
|
-
set(target, propertyName, newValue, _receiver) {
|
|
8
|
-
if (!stubs.hasOwnProperty(propertyName)) {
|
|
9
|
-
throw new Error(`No stub defined for property "${String(propertyName)}".`);
|
|
10
|
-
}
|
|
11
|
-
const stubMetadata = stubs[propertyName][stub$1];
|
|
12
|
-
if (stubMetadata.type !== "property") {
|
|
13
|
-
throw new Error(`Cannot set value of non-property stub "${String(propertyName)}".`);
|
|
14
|
-
}
|
|
15
|
-
stubMetadata.set(target, propertyName, newValue); // Cast to any is needed as TObject[TKey] does not work due to TS limitations
|
|
16
|
-
return true;
|
|
17
|
-
},
|
|
18
|
-
get(target, propertyName, _receiver) {
|
|
19
|
-
if (propertyName === mock$1) {
|
|
20
|
-
return { stubs };
|
|
21
|
-
}
|
|
22
|
-
if (!stubs.hasOwnProperty(propertyName)) {
|
|
23
|
-
throw new Error(`No stub defined for property "${String(propertyName)}".`);
|
|
24
|
-
}
|
|
25
|
-
const stubMetadata = stubs[propertyName][stub$1];
|
|
26
|
-
if (stubMetadata.type === "function") {
|
|
27
|
-
const returnValue = function (...args) {
|
|
28
|
-
return stubMetadata.invoke(realObject, propertyName, ...args);
|
|
29
|
-
};
|
|
30
|
-
return returnValue;
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
return stubMetadata.get(target, propertyName);
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
has(target, propertyName) {
|
|
37
|
-
return propertyName === mock$1 || Reflect.has(target, propertyName);
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function mock(clazz) {
|
|
43
|
-
const instance = Object.create(clazz.prototype);
|
|
44
|
-
return spy(instance);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function stub(mockOrSpy) {
|
|
48
|
-
const stubs = mockOrSpy[mock$1].stubs;
|
|
49
|
-
return new Proxy({}, {
|
|
50
|
-
set(_target, propertyName, newValue, _receiver) {
|
|
51
|
-
if (typeof newValue !== "object" || newValue === null || !Reflect.has(newValue, stub$1)) {
|
|
52
|
-
throw new Error(`Stub value for property "${String(propertyName)}" must be a stub created by a stub factory function.`);
|
|
53
|
-
}
|
|
54
|
-
if (stubs.hasOwnProperty(propertyName)) {
|
|
55
|
-
throw new Error(`Stub for property "${String(propertyName)}" is already defined.`);
|
|
56
|
-
}
|
|
57
|
-
stubs[propertyName] = newValue;
|
|
58
|
-
return true;
|
|
59
|
-
},
|
|
60
|
-
get(_target, propertyName, _receiver) {
|
|
61
|
-
throw new Error(`Cannot get stub for property "${String(propertyName)}". Stubs can only be set, not retrieved.`);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function runCatching(fn, thisArg, ...args) {
|
|
67
|
-
try {
|
|
68
|
-
const result = fn.apply(thisArg, args);
|
|
69
|
-
return { result: "success", returnValue: result };
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
return { result: "error", error };
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function answer(answerFunction) {
|
|
77
|
-
const metadata = {
|
|
78
|
-
calls: []
|
|
79
|
-
};
|
|
80
|
-
return {
|
|
81
|
-
[stub$1]: {
|
|
82
|
-
invoke: function (thisArg, _propertyName, ...args) {
|
|
83
|
-
const result = runCatching(answerFunction, thisArg, ...args);
|
|
84
|
-
metadata.calls.push({
|
|
85
|
-
args,
|
|
86
|
-
...(result.result === "success" ? { returnValue: result.returnValue } : { exception: result.error }),
|
|
87
|
-
invocationTime: new Date()
|
|
88
|
-
});
|
|
89
|
-
if (result.result === "error") {
|
|
90
|
-
throw result.error;
|
|
91
|
-
}
|
|
92
|
-
return result.returnValue;
|
|
93
|
-
},
|
|
94
|
-
type: "function",
|
|
95
|
-
metadata,
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function callThrough() {
|
|
101
|
-
const metadata = {
|
|
102
|
-
calls: []
|
|
103
|
-
};
|
|
104
|
-
return {
|
|
105
|
-
[stub$1]: {
|
|
106
|
-
invoke: function (thisArg, propertyName, ...args) {
|
|
107
|
-
const result = runCatching((...args) => thisArg[propertyName](...args), thisArg, ...args);
|
|
108
|
-
metadata.calls.push({
|
|
109
|
-
args,
|
|
110
|
-
...(result.result === "success" ? { returnValue: result.returnValue } : { exception: result.error }),
|
|
111
|
-
invocationTime: new Date()
|
|
112
|
-
});
|
|
113
|
-
if (result.result === "error") {
|
|
114
|
-
throw result.error;
|
|
115
|
-
}
|
|
116
|
-
return result.returnValue;
|
|
117
|
-
},
|
|
118
|
-
type: "function",
|
|
119
|
-
metadata,
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function delegateTo(delegateFunction) {
|
|
125
|
-
const metadata = {
|
|
126
|
-
calls: []
|
|
127
|
-
};
|
|
128
|
-
return {
|
|
129
|
-
[stub$1]: {
|
|
130
|
-
invoke: function (thisArg, _propertyName, ...args) {
|
|
131
|
-
metadata.calls.push({
|
|
132
|
-
args,
|
|
133
|
-
returnValue: delegateFunction.apply(thisArg, args),
|
|
134
|
-
invocationTime: new Date()
|
|
135
|
-
});
|
|
136
|
-
return delegateFunction.apply(thisArg, args);
|
|
137
|
-
},
|
|
138
|
-
type: "function",
|
|
139
|
-
metadata,
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function returnFixed(returnValue) {
|
|
145
|
-
const metadata = {
|
|
146
|
-
calls: []
|
|
147
|
-
};
|
|
148
|
-
return {
|
|
149
|
-
[stub$1]: {
|
|
150
|
-
invoke: function (_thisArg, _propertyName, ...args) {
|
|
151
|
-
metadata.calls.push({
|
|
152
|
-
args,
|
|
153
|
-
returnValue,
|
|
154
|
-
invocationTime: new Date()
|
|
155
|
-
});
|
|
156
|
-
return returnValue;
|
|
157
|
-
},
|
|
158
|
-
type: "function",
|
|
159
|
-
metadata,
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function returnSerial(...values) {
|
|
165
|
-
const metadata = {
|
|
166
|
-
calls: []
|
|
167
|
-
};
|
|
168
|
-
return {
|
|
169
|
-
[stub$1]: {
|
|
170
|
-
type: "function",
|
|
171
|
-
metadata,
|
|
172
|
-
invoke: function (_thisArg, _propertyName, ...args) {
|
|
173
|
-
const index = metadata.calls.length;
|
|
174
|
-
if (index === values.length) {
|
|
175
|
-
const error = new Error(`No more return values left in returnSerial stub (called ${index + 1} times, but only ${values.length} values were provided)`);
|
|
176
|
-
metadata.calls.push({
|
|
177
|
-
args,
|
|
178
|
-
invocationTime: new Date(),
|
|
179
|
-
exception: error,
|
|
180
|
-
});
|
|
181
|
-
throw error;
|
|
182
|
-
}
|
|
183
|
-
const returnValue = values[index];
|
|
184
|
-
metadata.calls.push({
|
|
185
|
-
args,
|
|
186
|
-
returnValue,
|
|
187
|
-
invocationTime: new Date()
|
|
188
|
-
});
|
|
189
|
-
return returnValue;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function noop() {
|
|
196
|
-
const metadata = {
|
|
197
|
-
calls: []
|
|
198
|
-
};
|
|
199
|
-
return {
|
|
200
|
-
[stub$1]: {
|
|
201
|
-
invoke: function (_thisArg, _propertyName, ...args) {
|
|
202
|
-
metadata.calls.push({
|
|
203
|
-
args,
|
|
204
|
-
invocationTime: new Date()
|
|
205
|
-
});
|
|
206
|
-
},
|
|
207
|
-
type: "function",
|
|
208
|
-
metadata,
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function throwOnCall(errorFactory) {
|
|
214
|
-
const metadata = {
|
|
215
|
-
calls: []
|
|
216
|
-
};
|
|
217
|
-
return {
|
|
218
|
-
[stub$1]: {
|
|
219
|
-
invoke: function (_thisArg, _propertyName, ...args) {
|
|
220
|
-
const error = errorFactory();
|
|
221
|
-
metadata.calls.push({
|
|
222
|
-
args,
|
|
223
|
-
exception: error,
|
|
224
|
-
invocationTime: new Date()
|
|
225
|
-
});
|
|
226
|
-
throw error;
|
|
227
|
-
},
|
|
228
|
-
type: "function",
|
|
229
|
-
metadata,
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
class OngoingFunctionStub {
|
|
235
|
-
get [stub$1]() {
|
|
236
|
-
return {
|
|
237
|
-
invoke: (thisArg, propertyName, ...args) => this.#invoke(thisArg, propertyName, ...args),
|
|
238
|
-
type: "function",
|
|
239
|
-
metadata: {
|
|
240
|
-
calls: [
|
|
241
|
-
...(this.#parentStub ? this.#parentStub[stub$1].metadata.calls : []),
|
|
242
|
-
...this.#stub[stub$1].metadata.calls
|
|
243
|
-
]
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
#argumentPredicates;
|
|
248
|
-
#stub;
|
|
249
|
-
#parentStub;
|
|
250
|
-
constructor(argumentPredicates, stub, parentStub) {
|
|
251
|
-
this.#argumentPredicates = argumentPredicates;
|
|
252
|
-
this.#stub = stub;
|
|
253
|
-
this.#parentStub = parentStub;
|
|
254
|
-
}
|
|
255
|
-
when(...argumentPredicates) {
|
|
256
|
-
return new OngoingFunctionStubBuilder(argumentPredicates, this);
|
|
257
|
-
}
|
|
258
|
-
#invoke(thisArg, propertyName, ...args) {
|
|
259
|
-
if (this.#parentStub && this.#parentStub.#matchesArguments(args)) {
|
|
260
|
-
return this.#parentStub.#invoke(thisArg, propertyName, ...args);
|
|
261
|
-
}
|
|
262
|
-
else if (this.#matchesArguments(args)) {
|
|
263
|
-
return this.#stub[stub$1].invoke(thisArg, propertyName, ...args);
|
|
264
|
-
}
|
|
265
|
-
else {
|
|
266
|
-
throw new Error("No matching stub found for the given arguments.");
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
#matchesArguments(args) {
|
|
270
|
-
return args.every((arg, index) => this.#argumentPredicates[index](arg));
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
class OngoingFunctionStubBuilder {
|
|
274
|
-
get [stub$1]() {
|
|
275
|
-
return {
|
|
276
|
-
invoke: (_thisArg, _propertyName, ..._args) => {
|
|
277
|
-
throw new Error("Cannot invoke a stub builder. Please complete the stub definition using 'then' before invoking.");
|
|
278
|
-
},
|
|
279
|
-
type: "function",
|
|
280
|
-
metadata: {
|
|
281
|
-
calls: []
|
|
282
|
-
}
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
#argumentPredicates;
|
|
286
|
-
#parentStub;
|
|
287
|
-
constructor(argumentPredicates, parentStub) {
|
|
288
|
-
this.#argumentPredicates = argumentPredicates;
|
|
289
|
-
this.#parentStub = parentStub;
|
|
290
|
-
}
|
|
291
|
-
then(stub) {
|
|
292
|
-
return new OngoingFunctionStub(this.#argumentPredicates, stub, this.#parentStub);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
function when(...argumentPredicates) {
|
|
296
|
-
return new OngoingFunctionStubBuilder(argumentPredicates);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function defaultValue(value) {
|
|
300
|
-
const metadata = {
|
|
301
|
-
reads: [],
|
|
302
|
-
assignments: []
|
|
303
|
-
};
|
|
304
|
-
let currentValue = value;
|
|
305
|
-
return {
|
|
306
|
-
[stub$1]: {
|
|
307
|
-
type: "property",
|
|
308
|
-
get(_thisArg, _propertyName) {
|
|
309
|
-
metadata.reads.push({
|
|
310
|
-
value: currentValue,
|
|
311
|
-
readTime: new Date()
|
|
312
|
-
});
|
|
313
|
-
return currentValue;
|
|
314
|
-
},
|
|
315
|
-
set(_thisArg, _propertyName, newValue) {
|
|
316
|
-
metadata.assignments.push({
|
|
317
|
-
newValue,
|
|
318
|
-
assignmentTime: new Date()
|
|
319
|
-
});
|
|
320
|
-
currentValue = newValue;
|
|
321
|
-
},
|
|
322
|
-
metadata
|
|
323
|
-
}
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function fixedValue(value) {
|
|
328
|
-
const metadata = {
|
|
329
|
-
reads: [],
|
|
330
|
-
assignments: []
|
|
331
|
-
};
|
|
332
|
-
return {
|
|
333
|
-
[stub$1]: {
|
|
334
|
-
type: "property",
|
|
335
|
-
get(_thisArg, _propertyName) {
|
|
336
|
-
metadata.reads.push({
|
|
337
|
-
value,
|
|
338
|
-
readTime: new Date()
|
|
339
|
-
});
|
|
340
|
-
return value;
|
|
341
|
-
},
|
|
342
|
-
set(_thisArg, _propertyName, _newValue) {
|
|
343
|
-
metadata.assignments.push({
|
|
344
|
-
newValue: _newValue,
|
|
345
|
-
assignmentTime: new Date()
|
|
346
|
-
});
|
|
347
|
-
throw new Error("Cannot set value of a fixedValue property stub.");
|
|
348
|
-
},
|
|
349
|
-
metadata
|
|
350
|
-
}
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function trackValue() {
|
|
355
|
-
const metadata = {
|
|
356
|
-
reads: [],
|
|
357
|
-
assignments: [],
|
|
358
|
-
};
|
|
359
|
-
return {
|
|
360
|
-
[stub$1]: {
|
|
361
|
-
type: "property",
|
|
362
|
-
get(thisArg, propertyName) {
|
|
363
|
-
const currentValue = thisArg[propertyName];
|
|
364
|
-
metadata.reads.push({
|
|
365
|
-
value: currentValue,
|
|
366
|
-
readTime: new Date(),
|
|
367
|
-
});
|
|
368
|
-
return currentValue;
|
|
369
|
-
},
|
|
370
|
-
set(thisArg, propertyName, newValue) {
|
|
371
|
-
thisArg[propertyName] = newValue;
|
|
372
|
-
metadata.assignments.push({
|
|
373
|
-
newValue,
|
|
374
|
-
assignmentTime: new Date(),
|
|
375
|
-
});
|
|
376
|
-
},
|
|
377
|
-
metadata
|
|
378
|
-
}
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
class AssignmentVerification {
|
|
383
|
-
#assignmentRecord;
|
|
384
|
-
constructor(assignmentRecord) {
|
|
385
|
-
this.#assignmentRecord = assignmentRecord;
|
|
386
|
-
}
|
|
387
|
-
wasBefore(otherAssignment) {
|
|
388
|
-
if (this.#assignmentRecord.assignmentTime >= otherAssignment.#assignmentRecord.assignmentTime) {
|
|
389
|
-
throw new Error(`Expected assignment at ${this.#assignmentRecord.assignmentTime.toISOString()} to be before assignment at ${otherAssignment.#assignmentRecord.assignmentTime.toISOString()}, but it was not.`);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
wasAfter(otherAssignment) {
|
|
393
|
-
if (this.#assignmentRecord.assignmentTime <= otherAssignment.#assignmentRecord.assignmentTime) {
|
|
394
|
-
throw new Error(`Expected assignment at ${this.#assignmentRecord.assignmentTime.toISOString()} to be after assignment at ${otherAssignment.#assignmentRecord.assignmentTime.toISOString()}, but it was not.`);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
hasValue(validator) {
|
|
398
|
-
if (!validator(this.#assignmentRecord.newValue)) {
|
|
399
|
-
throw new Error(`The assigned value did not satisfy the provided validator.`);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
class ReadVerification {
|
|
405
|
-
#readRecord;
|
|
406
|
-
constructor(read) {
|
|
407
|
-
this.#readRecord = read;
|
|
408
|
-
}
|
|
409
|
-
wasBefore(otherRead) {
|
|
410
|
-
if (this.#readRecord.readTime >= otherRead.#readRecord.readTime) {
|
|
411
|
-
throw new Error(`Expected read at ${this.#readRecord.readTime.toISOString()} to be before read at ${otherRead.#readRecord.readTime.toISOString()}, but it was not.`);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
wasAfter(otherRead) {
|
|
415
|
-
if (this.#readRecord.readTime <= otherRead.#readRecord.readTime) {
|
|
416
|
-
throw new Error(`Expected read at ${this.#readRecord.readTime.toISOString()} to be after read at ${otherRead.#readRecord.readTime.toISOString()}, but it was not.`);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
hadValue(validator) {
|
|
420
|
-
if (!validator(this.#readRecord.value)) {
|
|
421
|
-
throw new Error(`The read value did not satisfy the provided validator.`);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
class PropertyVerification {
|
|
427
|
-
#propertyStubMetadata;
|
|
428
|
-
constructor(propertyStubMetadata) {
|
|
429
|
-
this.#propertyStubMetadata = propertyStubMetadata;
|
|
430
|
-
}
|
|
431
|
-
wasReadNTimes(expectedGetCallCount = 1) {
|
|
432
|
-
const actualGetCallCount = this.#propertyStubMetadata.reads.length;
|
|
433
|
-
if (actualGetCallCount !== expectedGetCallCount) {
|
|
434
|
-
throw new Error(`Expected property getter to be called ${expectedGetCallCount} times, but was called ${actualGetCallCount} times.`);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
wasRead() {
|
|
438
|
-
this.wasReadNTimes(1);
|
|
439
|
-
}
|
|
440
|
-
wasNotAssigned() {
|
|
441
|
-
this.wasAssignedNTimes(0);
|
|
442
|
-
}
|
|
443
|
-
wasNotRead() {
|
|
444
|
-
this.wasReadNTimes(0);
|
|
445
|
-
}
|
|
446
|
-
wasAssignedNTimes(expectedSetCallCount = 1) {
|
|
447
|
-
const actualSetCallCount = this.#propertyStubMetadata.assignments.length;
|
|
448
|
-
if (actualSetCallCount !== expectedSetCallCount) {
|
|
449
|
-
throw new Error(`Expected property setter to be called ${expectedSetCallCount} times, but was called ${actualSetCallCount} times.`);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
wasAssigned() {
|
|
453
|
-
this.wasAssignedNTimes(1);
|
|
454
|
-
}
|
|
455
|
-
nthAssignment(index) {
|
|
456
|
-
if (index < 0 || index >= this.#propertyStubMetadata.assignments.length) {
|
|
457
|
-
throw new Error(`Assignment index ${index} is out of bounds. There are only ${this.#propertyStubMetadata.assignments.length} assignments.`);
|
|
458
|
-
}
|
|
459
|
-
return new AssignmentVerification(this.#propertyStubMetadata.assignments[index]);
|
|
460
|
-
}
|
|
461
|
-
firstAssignment() {
|
|
462
|
-
return this.nthAssignment(0);
|
|
463
|
-
}
|
|
464
|
-
lastAssignment() {
|
|
465
|
-
return this.nthAssignment(this.#propertyStubMetadata.assignments.length - 1);
|
|
466
|
-
}
|
|
467
|
-
firstRead() {
|
|
468
|
-
return this.nthRead(0);
|
|
469
|
-
}
|
|
470
|
-
lastRead() {
|
|
471
|
-
return this.nthRead(this.#propertyStubMetadata.reads.length - 1);
|
|
472
|
-
}
|
|
473
|
-
nthRead(index) {
|
|
474
|
-
if (index < 0 || index >= this.#propertyStubMetadata.reads.length) {
|
|
475
|
-
throw new Error(`Read index ${index} is out of bounds. There are only ${this.#propertyStubMetadata.reads.length} reads.`);
|
|
476
|
-
}
|
|
477
|
-
return new ReadVerification(this.#propertyStubMetadata.reads[index]);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
class InvocationVerification {
|
|
482
|
-
#call;
|
|
483
|
-
constructor(call) {
|
|
484
|
-
this.#call = call;
|
|
485
|
-
}
|
|
486
|
-
wasBefore(otherInvocation) {
|
|
487
|
-
if (this.#call.invocationTime >= otherInvocation.#call.invocationTime) {
|
|
488
|
-
throw new Error(`Expected invocation at ${this.#call.invocationTime.toISOString()} to be before invocation at ${otherInvocation.#call.invocationTime.toISOString()}, but it was not.`);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
wasAfter(otherInvocation) {
|
|
492
|
-
if (this.#call.invocationTime <= otherInvocation.#call.invocationTime) {
|
|
493
|
-
throw new Error(`Expected invocation at ${this.#call.invocationTime.toISOString()} to be after invocation at ${otherInvocation.#call.invocationTime.toISOString()}, but it was not.`);
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
returned(validator) {
|
|
497
|
-
if (this.#call.exception) {
|
|
498
|
-
throw new Error(`Expected invocation to return a value, but it threw an exception.`);
|
|
499
|
-
}
|
|
500
|
-
if (!validator(this.#call.returnValue)) {
|
|
501
|
-
throw new Error(`The returned value did not satisfy the provided validator.`);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
threw(validator) {
|
|
505
|
-
if (!this.#call.exception) {
|
|
506
|
-
throw new Error(`Expected invocation to throw an exception, but it did not.`);
|
|
507
|
-
}
|
|
508
|
-
if (!validator(this.#call.exception)) {
|
|
509
|
-
throw new Error(`The thrown exception did not satisfy the provided validator.`);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
didNotThrow() {
|
|
513
|
-
if (this.#call.exception) {
|
|
514
|
-
throw new Error(`Expected invocation to not throw an exception, but it threw: ${this.#call.exception}`);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
hadArguments(...validators) {
|
|
518
|
-
const hasArguments = validators.every((validator, index) => validator(this.#call.args[index]));
|
|
519
|
-
if (!hasArguments) {
|
|
520
|
-
throw new Error(`The invocation arguments did not satisfy the provided validators.`);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
class FunctionVerification {
|
|
526
|
-
#functionStubMetadata;
|
|
527
|
-
constructor(functionStubMetadata) {
|
|
528
|
-
this.#functionStubMetadata = functionStubMetadata;
|
|
529
|
-
}
|
|
530
|
-
wasCalled() {
|
|
531
|
-
this.wasCalledTimes(1);
|
|
532
|
-
}
|
|
533
|
-
wasNotCalled() {
|
|
534
|
-
this.wasCalledTimes(0);
|
|
535
|
-
}
|
|
536
|
-
wasCalledTimes(expectedCallCount) {
|
|
537
|
-
const actualCallCount = this.#functionStubMetadata.calls.length;
|
|
538
|
-
if (actualCallCount !== expectedCallCount) {
|
|
539
|
-
throw new Error(`Expected function to be called ${expectedCallCount} times, but was called ${actualCallCount} times.`);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
nthInvocation(invocationIndex) {
|
|
543
|
-
if (invocationIndex < 0 || invocationIndex >= this.#functionStubMetadata.calls.length) {
|
|
544
|
-
throw new Error(`Invocation index ${invocationIndex} is out of bounds. There are only ${this.#functionStubMetadata.calls.length} invocations.`);
|
|
545
|
-
}
|
|
546
|
-
const call = this.#functionStubMetadata.calls[invocationIndex];
|
|
547
|
-
return new InvocationVerification(call);
|
|
548
|
-
}
|
|
549
|
-
firstInvocation() {
|
|
550
|
-
return this.nthInvocation(0);
|
|
551
|
-
}
|
|
552
|
-
lastInvocation() {
|
|
553
|
-
return this.nthInvocation(this.#functionStubMetadata.calls.length - 1);
|
|
554
|
-
}
|
|
555
|
-
invocationMatching(predicate) {
|
|
556
|
-
const call = this.#functionStubMetadata.calls.find(call => predicate(...call.args));
|
|
557
|
-
if (!call) {
|
|
558
|
-
throw new Error(`No invocation found matching the provided predicate.`);
|
|
559
|
-
}
|
|
560
|
-
return new InvocationVerification(call);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
function verify(mockOrSpy) {
|
|
565
|
-
return new Proxy({}, {
|
|
566
|
-
get(_target, propertyName, _receiver) {
|
|
567
|
-
if (!mockOrSpy[mock$1].stubs.hasOwnProperty(propertyName)) {
|
|
568
|
-
throw new Error(`No stub defined for property "${String(propertyName)}".`);
|
|
569
|
-
}
|
|
570
|
-
const stub = mockOrSpy[mock$1].stubs[propertyName];
|
|
571
|
-
const stubMetadata = stub[stub$1];
|
|
572
|
-
if (stubMetadata.type === "function") {
|
|
573
|
-
return new FunctionVerification(stubMetadata.metadata);
|
|
574
|
-
}
|
|
575
|
-
else {
|
|
576
|
-
return new PropertyVerification(stubMetadata.metadata);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
export { answer, callThrough, defaultValue, delegateTo, fixedValue, mock, noop, returnFixed, returnSerial, spy, stub, throwOnCall, trackValue, verify, when };
|
|
1
|
+
const t=Symbol("mock"),e=Symbol("stub");function n(n){const r={};return new Proxy(n,{set(t,n,o,a){if(!r.hasOwnProperty(n))throw new Error(`No stub defined for property "${String(n)}".`);const i=r[n][e];if("property"!==i.type)throw new Error(`Cannot set value of non-property stub "${String(n)}".`);return i.set(t,n,o),!0},get(o,a,i){if(a===t)return{stubs:r};if(!r.hasOwnProperty(a))throw new Error(`No stub defined for property "${String(a)}".`);const s=r[a][e];if("function"===s.type){return function(...t){return s.invoke(n,a,...t)}}return s.get(o,a)},has:(e,n)=>n===t||Reflect.has(e,n)})}function r(t){return n(Object.create(t.prototype))}function o(n){const r=n[t].stubs;return new Proxy({},{set(t,n,o,a){if("object"!=typeof o||null===o||!Reflect.has(o,e))throw new Error(`Stub value for property "${String(n)}" must be a stub created by a stub factory function.`);if(r.hasOwnProperty(n))throw new Error(`Stub for property "${String(n)}" is already defined.`);return r[n]=o,!0},get(t,e,n){throw new Error(`Cannot get stub for property "${String(e)}". Stubs can only be set, not retrieved.`)}})}function a(t,e,...n){try{return{result:"success",returnValue:t.apply(e,n)}}catch(t){return{result:"error",error:t}}}function i(t){const n={calls:[]};return{[e]:{invoke:function(e,r,...o){const i=a(t,e,...o);if(n.calls.push({args:o,..."success"===i.result?{returnValue:i.returnValue}:{exception:i.error},time:BigInt(Math.floor(1e6*performance.now()))}),"error"===i.result)throw i.error;return i.returnValue},type:"function",metadata:n}}}function s(){const t={calls:[]};return{[e]:{invoke:function(e,n,...r){const o=a((...t)=>e[n](...t),e,...r);if(t.calls.push({args:r,..."success"===o.result?{returnValue:o.returnValue}:{exception:o.error},time:BigInt(Math.floor(1e6*performance.now()))}),"error"===o.result)throw o.error;return o.returnValue},type:"function",metadata:t}}}function c(t){const n={calls:[]};return{[e]:{invoke:function(e,r,...o){return n.calls.push({args:o,returnValue:t.apply(e,o),time:BigInt(Math.floor(1e6*performance.now()))}),t.apply(e,o)},type:"function",metadata:n}}}function u(){const t={calls:[]};return{[e]:{invoke:function(e,n,...r){t.calls.push({args:r,time:BigInt(Math.floor(1e6*performance.now()))})},type:"function",metadata:t}}}function h(t){const n={calls:[]};return{[e]:{invoke:function(e,r,...o){return n.calls.push({args:o,returnValue:t,time:BigInt(Math.floor(1e6*performance.now()))}),t},type:"function",metadata:n}}}function d(...t){const n={calls:[]};return{[e]:{type:"function",metadata:n,invoke:function(e,r,...o){const a=n.calls.length;if(a===t.length){const e=new Error(`No more return values left in returnSerial stub (called ${a+1} times, but only ${t.length} values were provided)`);throw n.calls.push({args:o,time:BigInt(Math.floor(1e6*performance.now())),exception:e}),e}const i=t[a];return n.calls.push({args:o,returnValue:i,time:BigInt(Math.floor(1e6*performance.now()))}),i}}}}function l(t){const n={calls:[]};return{[e]:{invoke:function(e,r,...o){const a=t();throw n.calls.push({args:o,exception:a,time:BigInt(Math.floor(1e6*performance.now()))}),a},type:"function",metadata:n}}}class p{get[e](){return{invoke:(t,e,...n)=>this.#t(t,e,...n),type:"function",metadata:{calls:[...this.#e?this.#e[e].metadata.calls:[],...this.#n[e].metadata.calls]}}}#r;#n;#e;constructor(t,e,n){this.#r=t,this.#n=e,this.#e=n}when(...t){return new f(t,this)}#t(t,n,...r){if(this.#e&&this.#e.#o(r))return this.#e.#t(t,n,...r);if(this.#o(r))return this.#n[e].invoke(t,n,...r);throw new Error("No matching stub found for the given arguments.")}#o(t){return t.every((t,e)=>this.#r[e](t))}}class f{get[e](){return{invoke:(t,e,...n)=>{throw new Error("Cannot invoke a stub builder. Please complete the stub definition using 'then' before invoking.")},type:"function",metadata:{calls:[]}}}#r;#e;constructor(t,e){this.#r=t,this.#e=e}then(t){return new p(this.#r,t,this.#e)}}function w(...t){return new f(t)}function m(t){const n={reads:[],assignments:[]};let r=t;return{[e]:{type:"property",get:(t,e)=>(n.reads.push({value:r,time:BigInt(Math.floor(1e6*performance.now()))}),r),set(t,e,o){n.assignments.push({newValue:o,time:BigInt(Math.floor(1e6*performance.now()))}),r=o},metadata:n}}}function g(t){const n={reads:[],assignments:[]};return{[e]:{type:"property",get:(e,r)=>(n.reads.push({value:t,time:BigInt(Math.floor(1e6*performance.now()))}),t),set(t,e,r){throw n.assignments.push({newValue:r,time:BigInt(Math.floor(1e6*performance.now()))}),new Error("Cannot set value of a fixedValue property stub.")},metadata:n}}}function b(){const t={reads:[],assignments:[]};return{[e]:{type:"property",get(e,n){const r=e[n];return t.reads.push({value:r,time:BigInt(Math.floor(1e6*performance.now()))}),r},set(e,n,r){e[n]=r,t.assignments.push({newValue:r,time:BigInt(Math.floor(1e6*performance.now()))})},metadata:t}}}class y{constructor(t){this.interactionRecord=t}wasBefore(t){if(t instanceof y){const e=t;if(this.interactionRecord.time>=e.interactionRecord.time)throw new Error(`Expected interaction at ${this.interactionRecord.time} to be before interaction at ${e.interactionRecord.time}, but it was not.`)}else{const e=BigInt(1e6*t.getTime());if(this.interactionRecord.time>=e)throw new Error(`Expected interaction at ${this.interactionRecord.time} to be before ${e}, but it was not.`)}}wasAfter(t){if(t instanceof y){if(this.interactionRecord.time<=t.interactionRecord.time)throw new Error(`Expected interaction at ${this.interactionRecord.time} to be after interaction at ${t.interactionRecord.time}, but it was not.`)}else{const e=BigInt(1e6*t.getTime());if(this.interactionRecord.time<=e)throw new Error(`Expected interaction at ${this.interactionRecord.time} to be after ${e}, but it was not.`)}}}class v extends y{constructor(t){super(t)}returned(t){if(this.interactionRecord.exception)throw new Error("Expected invocation to return a value, but it threw an exception.");if(!t(this.interactionRecord.returnValue))throw new Error("The returned value did not satisfy the provided validator.")}threw(t){if(!this.interactionRecord.exception)throw new Error("Expected invocation to throw an exception, but it did not.");if(!t(this.interactionRecord.exception))throw new Error("The thrown exception did not satisfy the provided validator.")}didNotThrow(){if(this.interactionRecord.exception)throw new Error(`Expected invocation to not throw an exception, but it threw: ${this.interactionRecord.exception}`)}hadArguments(...t){if(!t.every((t,e)=>t(this.interactionRecord.args[e])))throw new Error("The invocation arguments did not satisfy the provided validators.")}}class S{#a;constructor(t){this.#a=t}wasCalled(t=t=>1===t){if(!t(this.#a.calls.length))throw new Error(`Expected invocation count to pass predicate. (was called ${this.#a.calls.length} times)`)}wasNotCalled(){this.wasCalledTimes(0)}wasCalledTimes(t){this.wasCalled(e=>e===t)}nthInvocation(t){if(t<0||t>=this.#a.calls.length)throw new Error(`Invocation index ${t} is out of bounds. There are only ${this.#a.calls.length} invocations.`);const e=this.#a.calls[t];return new v(e)}firstInvocation(){return this.nthInvocation(0)}lastInvocation(){return this.nthInvocation(this.#a.calls.length-1)}invocationMatching(t){return this.firstInvocationMatching(e=>t(...e.args))}firstInvocationMatching(t){const e=this.#a.calls.find(e=>t(e))??function(t){throw t}(new Error("No invocation found matching the provided predicate."));return new v(e)}lastInvocationMatching(t){const e=this.#a.calls.toReversed().find(e=>t(e));if(!e)throw new Error("No invocation found matching the provided predicate.");return new v(e)}allInvocationsMatching(t){return this.#a.calls.filter(e=>t(e)).map(t=>new v(t))}}class M extends y{constructor(t){super(t)}hasValue(t){if(!t(this.interactionRecord.newValue))throw new Error("The assigned value did not satisfy the provided validator.")}}class E extends y{constructor(t){super(t)}hadValue(t){if(!t(this.interactionRecord.value))throw new Error("The read value did not satisfy the provided validator.")}}class R{#i;constructor(t){this.#i=t}wasReadNTimes(t=1){this.wasRead(e=>e===t)}wasRead(t=t=>1===t){const e=this.#i.reads.length;if(!t(e))throw new Error(`Expected read count to pass predicate. (was read ${e} times)`)}wasAssigned(t=t=>1===t){const e=this.#i.assignments.length;if(!t(e))throw new Error(`Expected read count to pass predicate. (was read ${e} times)`)}wasNotAssigned(){this.wasAssigned(t=>0===t)}wasNotRead(){this.wasReadNTimes(0)}wasAssignedNTimes(t=1){this.wasAssigned(e=>e===t)}nthAssignment(t){if(t<0||t>=this.#i.assignments.length)throw new Error(`Assignment index ${t} is out of bounds. There are only ${this.#i.assignments.length} assignments.`);return new M(this.#i.assignments[t])}firstAssignment(){return this.nthAssignment(0)}lastAssignment(){return this.nthAssignment(this.#i.assignments.length-1)}firstRead(){return this.nthRead(0)}lastRead(){return this.nthRead(this.#i.reads.length-1)}nthRead(t){if(t<0||t>=this.#i.reads.length)throw new Error(`Read index ${t} is out of bounds. There are only ${this.#i.reads.length} reads.`);return new E(this.#i.reads[t])}}function x(n){return new Proxy({},{get(r,o,a){if(!n[t].stubs.hasOwnProperty(o))throw new Error(`No stub defined for property "${String(o)}".`);const i=n[t].stubs[o][e];return"function"===i.type?new S(i.metadata):new R(i.metadata)}})}export{i as answer,s as callThrough,m as defaultValue,c as delegateTo,g as fixedValue,r as mock,u as noop,h as returnFixed,d as returnSerial,n as spy,o as stub,l as throwOnCall,b as trackValue,x as verify,w as when};
|