strong-mock 8.0.0-beta.1 → 8.0.0-beta.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 +93 -50
- package/dist/errors.d.ts +4 -4
- package/dist/expectation/expectation.d.ts +3 -7
- package/dist/expectation/it.d.ts +1 -1
- package/dist/expectation/repository/expectation-repository.d.ts +6 -6
- package/dist/expectation/repository/flexible-repository.d.ts +8 -7
- package/dist/expectation/repository/return-value.d.ts +13 -0
- package/dist/expectation/strong-expectation.d.ts +6 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +250 -285
- package/dist/index.js.map +1 -1
- package/dist/mock/defaults.d.ts +1 -1
- package/dist/mock/map.d.ts +4 -4
- package/dist/mock/mock.d.ts +8 -6
- package/dist/mock/options.d.ts +51 -24
- package/dist/mock/stub.d.ts +5 -14
- package/dist/print.d.ts +3 -2
- package/dist/proxy.d.ts +1 -1
- package/dist/return/invocation-count.d.ts +1 -1
- package/dist/return/returns.d.ts +11 -7
- package/dist/verify/reset.d.ts +1 -1
- package/dist/verify/verify.d.ts +2 -2
- package/dist/when/pending-expectation.d.ts +20 -19
- package/dist/when/when.d.ts +7 -2
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -168,151 +168,71 @@ ${snippet}`);
|
|
|
168
168
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
* Controls what happens when a property is accessed, or a call is made,
|
|
173
|
-
* and there are no expectations set for it.
|
|
174
|
-
*/
|
|
175
|
-
exports.Strictness = void 0;
|
|
171
|
+
exports.UnexpectedProperty = void 0;
|
|
176
172
|
|
|
177
|
-
(function (
|
|
173
|
+
(function (UnexpectedProperty) {
|
|
178
174
|
/**
|
|
179
|
-
*
|
|
180
|
-
* expectation, will throw immediately.
|
|
175
|
+
* Throw an error immediately.
|
|
181
176
|
*
|
|
182
177
|
* @example
|
|
183
|
-
*
|
|
184
|
-
* const service = mock<Service>();
|
|
185
|
-
*
|
|
186
|
-
* // This will throw.
|
|
178
|
+
* // Will throw "Didn't expect foo to be accessed".
|
|
187
179
|
* const { foo } = service;
|
|
188
180
|
*
|
|
189
181
|
* // Will throw "Didn't expect foo to be accessed",
|
|
190
182
|
* // without printing the arguments.
|
|
191
183
|
* foo(42);
|
|
192
184
|
*/
|
|
193
|
-
|
|
185
|
+
UnexpectedProperty[UnexpectedProperty["THROW"] = 0] = "THROW";
|
|
194
186
|
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* but never calls it.
|
|
187
|
+
* Return a function that will throw if called. This can be useful if your
|
|
188
|
+
* code destructures a function but never calls it.
|
|
198
189
|
*
|
|
199
190
|
* It will also improve error messages for unexpected calls because arguments
|
|
200
191
|
* will be captured instead of throwing immediately on the property access.
|
|
201
192
|
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
193
|
+
* The function will be returned even if the property is not supposed to be a
|
|
194
|
+
* function. This could cause weird behavior at runtime, when your code expects
|
|
195
|
+
* e.g. a number and gets a function instead.
|
|
205
196
|
*
|
|
206
|
-
*
|
|
197
|
+
* @example
|
|
198
|
+
* // This will NOT throw.
|
|
207
199
|
* const { foo } = service;
|
|
208
200
|
*
|
|
201
|
+
* // This will NOT throw, and might produce unexpected results.
|
|
202
|
+
* foo > 0
|
|
203
|
+
*
|
|
209
204
|
* // Will throw "Didn't expect foo(42) to be called".
|
|
210
205
|
* foo(42);
|
|
211
206
|
*/
|
|
212
207
|
|
|
213
|
-
|
|
214
|
-
})(exports.
|
|
215
|
-
|
|
216
|
-
const createProxy = traps => // eslint-disable-next-line no-empty-function
|
|
217
|
-
new Proxy(
|
|
218
|
-
/* istanbul ignore next */
|
|
219
|
-
() => {}, {
|
|
220
|
-
get: (target, prop) => {
|
|
221
|
-
if (prop === 'bind') {
|
|
222
|
-
return (thisArg, ...args) => (...moreArgs) => traps.apply([...args, ...moreArgs]);
|
|
223
|
-
}
|
|
208
|
+
UnexpectedProperty[UnexpectedProperty["CALL_THROW"] = 1] = "CALL_THROW";
|
|
209
|
+
})(exports.UnexpectedProperty || (exports.UnexpectedProperty = {}));
|
|
224
210
|
|
|
225
|
-
if (prop === 'apply') {
|
|
226
|
-
return (thisArg, args) => traps.apply(args || []);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (prop === 'call') {
|
|
230
|
-
return (thisArg, ...args) => traps.apply(args);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return traps.property(prop);
|
|
234
|
-
},
|
|
235
|
-
apply: (target, thisArg, args) => traps.apply(args),
|
|
236
|
-
ownKeys: () => traps.ownKeys(),
|
|
237
|
-
|
|
238
|
-
getOwnPropertyDescriptor(target, prop) {
|
|
239
|
-
const keys = traps.ownKeys();
|
|
240
|
-
|
|
241
|
-
if (keys.includes(prop)) {
|
|
242
|
-
return {
|
|
243
|
-
configurable: true,
|
|
244
|
-
enumerable: true
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return undefined;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Since `when` doesn't receive the mock subject (because we can't make it
|
|
255
|
-
* consistently return it from `mock()`, `mock.foo` and `mock.bar()`) we need
|
|
256
|
-
* to store a global state for the currently active mock.
|
|
257
|
-
*
|
|
258
|
-
* We also want to throw in the following case:
|
|
259
|
-
*
|
|
260
|
-
* ```
|
|
261
|
-
* when(() => mock()) // forgot returns here
|
|
262
|
-
* when(() => mock()) // should throw
|
|
263
|
-
* ```
|
|
264
|
-
*
|
|
265
|
-
* For that reason we can't just store the currently active mock, but also
|
|
266
|
-
* whether we finished the expectation or not.
|
|
267
|
-
*/
|
|
268
|
-
|
|
269
|
-
let activeMock;
|
|
270
|
-
const setActiveMock = mock => {
|
|
271
|
-
activeMock = mock;
|
|
272
|
-
};
|
|
273
|
-
const getActiveMock = () => activeMock;
|
|
274
211
|
/**
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
* This is needed because we can't reliably pass the state between `when`
|
|
278
|
-
* and `thenReturn`.
|
|
279
|
-
*/
|
|
280
|
-
|
|
281
|
-
const mockMap = new Map();
|
|
282
|
-
const getMockState = mock => {
|
|
283
|
-
if (mockMap.has(mock)) {
|
|
284
|
-
return mockMap.get(mock);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
throw new NotAMock();
|
|
288
|
-
};
|
|
289
|
-
const setMockState = (mock, state) => {
|
|
290
|
-
mockMap.set(mock, state);
|
|
291
|
-
};
|
|
292
|
-
const getAllMocks = () => Array.from(mockMap.entries());
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Return the expectation's return value.
|
|
212
|
+
* Unbox the expectation's return value.
|
|
296
213
|
*
|
|
297
214
|
* If the value is an error then throw it.
|
|
298
215
|
*
|
|
299
216
|
* If the value is a promise then resolve/reject it.
|
|
300
217
|
*/
|
|
301
|
-
|
|
302
|
-
const returnOrThrow = ({
|
|
218
|
+
const unboxReturnValue = ({
|
|
303
219
|
isError,
|
|
304
220
|
isPromise,
|
|
305
221
|
value
|
|
306
222
|
}) => {
|
|
307
223
|
if (isError) {
|
|
308
|
-
if (isPromise) {
|
|
309
|
-
return Promise.reject(value);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
224
|
if (value instanceof Error) {
|
|
225
|
+
if (isPromise) {
|
|
226
|
+
return Promise.reject(value);
|
|
227
|
+
}
|
|
228
|
+
|
|
313
229
|
throw value;
|
|
314
230
|
}
|
|
315
231
|
|
|
232
|
+
if (isPromise) {
|
|
233
|
+
return Promise.reject(new Error(value));
|
|
234
|
+
}
|
|
235
|
+
|
|
316
236
|
throw new Error(value);
|
|
317
237
|
}
|
|
318
238
|
|
|
@@ -322,67 +242,20 @@ const returnOrThrow = ({
|
|
|
322
242
|
|
|
323
243
|
return value;
|
|
324
244
|
};
|
|
325
|
-
const createStub = (repo, pendingExpectation, getCurrentMode, concreteMatcher) => {
|
|
326
|
-
const stub = createProxy({
|
|
327
|
-
property: property => {
|
|
328
|
-
if (getCurrentMode() === Mode.CALL) {
|
|
329
|
-
return returnOrThrow(repo.get(property));
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
setActiveMock(stub);
|
|
333
|
-
pendingExpectation.start(repo, concreteMatcher); // eslint-disable-next-line no-param-reassign
|
|
334
|
-
|
|
335
|
-
pendingExpectation.property = property;
|
|
336
|
-
return createProxy({
|
|
337
|
-
property: childProp => {
|
|
338
|
-
pendingExpectation.clear();
|
|
339
|
-
throw new NestedWhen(property, childProp);
|
|
340
|
-
},
|
|
341
|
-
apply: args => {
|
|
342
|
-
// eslint-disable-next-line no-param-reassign
|
|
343
|
-
pendingExpectation.args = args;
|
|
344
|
-
},
|
|
345
|
-
ownKeys: () => {
|
|
346
|
-
throw new Error('Spreading during an expectation is not supported.');
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
},
|
|
350
|
-
apply: args => {
|
|
351
|
-
if (getCurrentMode() === Mode.CALL) {
|
|
352
|
-
return repo.apply(args);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
setActiveMock(stub);
|
|
356
|
-
pendingExpectation.start(repo, concreteMatcher); // eslint-disable-next-line no-param-reassign
|
|
357
|
-
|
|
358
|
-
pendingExpectation.property = ApplyProp; // eslint-disable-next-line no-param-reassign
|
|
359
|
-
|
|
360
|
-
pendingExpectation.args = args;
|
|
361
|
-
return undefined;
|
|
362
|
-
},
|
|
363
|
-
ownKeys: () => {
|
|
364
|
-
if (getCurrentMode() === Mode.CALL) {
|
|
365
|
-
return repo.getAllProperties();
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
throw new Error('Spreading during an expectation is not supported.');
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
return stub;
|
|
372
|
-
};
|
|
373
245
|
|
|
374
246
|
/**
|
|
375
|
-
* An expectation repository
|
|
247
|
+
* An expectation repository with a configurable behavior for
|
|
248
|
+
* unexpected property access.
|
|
376
249
|
*/
|
|
377
250
|
|
|
378
251
|
class FlexibleRepository {
|
|
379
|
-
constructor(
|
|
380
|
-
this.
|
|
252
|
+
constructor(unexpectedProperty = exports.UnexpectedProperty.THROW) {
|
|
253
|
+
this.unexpectedProperty = void 0;
|
|
381
254
|
this.expectations = new Map();
|
|
382
255
|
this.expectedCallStats = new Map();
|
|
383
256
|
this.unexpectedCallStats = new Map();
|
|
384
257
|
|
|
385
|
-
this.apply = args => this.get(ApplyProp)
|
|
258
|
+
this.apply = args => this.get(ApplyProp)(...args);
|
|
386
259
|
|
|
387
260
|
this.handlePropertyWithMatchingExpectations = (property, expectations) => {
|
|
388
261
|
// Avoid recording call stats for function calls, since the property is an
|
|
@@ -397,64 +270,51 @@ class FlexibleRepository {
|
|
|
397
270
|
|
|
398
271
|
if (propertyExpectation) {
|
|
399
272
|
this.countAndConsume(propertyExpectation);
|
|
400
|
-
return propertyExpectation.expectation.returnValue;
|
|
273
|
+
return unboxReturnValue(propertyExpectation.expectation.returnValue);
|
|
401
274
|
}
|
|
402
275
|
|
|
403
|
-
return {
|
|
404
|
-
|
|
405
|
-
const callExpectation = expectations.find(e => e.expectation.matches(args));
|
|
406
|
-
|
|
407
|
-
if (callExpectation) {
|
|
408
|
-
this.recordExpected(property, args);
|
|
409
|
-
this.countAndConsume(callExpectation); // TODO: this is duplicated in stub
|
|
276
|
+
return (...args) => {
|
|
277
|
+
const callExpectation = expectations.find(e => e.expectation.matches(args));
|
|
410
278
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
return
|
|
279
|
+
if (callExpectation) {
|
|
280
|
+
this.recordExpected(property, args);
|
|
281
|
+
this.countAndConsume(callExpectation);
|
|
282
|
+
return unboxReturnValue(callExpectation.expectation.returnValue);
|
|
415
283
|
}
|
|
284
|
+
|
|
285
|
+
return this.getValueForUnexpectedCall(property, args);
|
|
416
286
|
};
|
|
417
287
|
};
|
|
418
288
|
|
|
419
289
|
this.handlePropertyWithNoExpectations = property => {
|
|
420
290
|
switch (property) {
|
|
421
291
|
case 'toString':
|
|
422
|
-
return
|
|
423
|
-
value: () => 'mock'
|
|
424
|
-
};
|
|
292
|
+
return () => 'mock';
|
|
425
293
|
|
|
426
294
|
case '@@toStringTag':
|
|
427
295
|
case Symbol.toStringTag:
|
|
428
296
|
case 'name':
|
|
429
|
-
return
|
|
430
|
-
value: 'mock'
|
|
431
|
-
};
|
|
297
|
+
return 'mock';
|
|
432
298
|
// pretty-format
|
|
433
299
|
|
|
434
300
|
case '$$typeof':
|
|
435
301
|
case 'constructor':
|
|
436
302
|
case '@@__IMMUTABLE_ITERABLE__@@':
|
|
437
303
|
case '@@__IMMUTABLE_RECORD__@@':
|
|
438
|
-
return
|
|
439
|
-
value: null
|
|
440
|
-
};
|
|
304
|
+
return null;
|
|
441
305
|
|
|
442
306
|
case MATCHER_SYMBOL:
|
|
443
|
-
return
|
|
444
|
-
value: false
|
|
445
|
-
};
|
|
307
|
+
return false;
|
|
446
308
|
|
|
447
309
|
case ApplyProp:
|
|
448
|
-
return
|
|
449
|
-
value: (...args) => this.getValueForUnexpectedCall(property, args)
|
|
450
|
-
};
|
|
310
|
+
return (...args) => this.getValueForUnexpectedCall(property, args);
|
|
451
311
|
|
|
452
312
|
default:
|
|
453
313
|
return this.getValueForUnexpectedAccess(property);
|
|
454
314
|
}
|
|
455
315
|
};
|
|
456
316
|
|
|
457
|
-
this.
|
|
317
|
+
this.unexpectedProperty = unexpectedProperty;
|
|
458
318
|
}
|
|
459
319
|
|
|
460
320
|
add(expectation) {
|
|
@@ -474,6 +334,8 @@ class FlexibleRepository {
|
|
|
474
334
|
this.unexpectedCallStats.clear();
|
|
475
335
|
}
|
|
476
336
|
|
|
337
|
+
// TODO: this returns any, but the interface returns unknown
|
|
338
|
+
// unknown causes errors in apply tests, and any causes bugs in bootstrapped SM
|
|
477
339
|
get(property) {
|
|
478
340
|
const expectations = this.expectations.get(property);
|
|
479
341
|
|
|
@@ -537,16 +399,14 @@ class FlexibleRepository {
|
|
|
537
399
|
}
|
|
538
400
|
|
|
539
401
|
getValueForUnexpectedAccess(property) {
|
|
540
|
-
if (this.
|
|
402
|
+
if (this.unexpectedProperty === exports.UnexpectedProperty.THROW) {
|
|
541
403
|
this.recordUnexpected(property, undefined);
|
|
542
404
|
throw new UnexpectedAccess(property, this.getUnmet());
|
|
543
405
|
}
|
|
544
406
|
|
|
545
|
-
return {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
throw new UnexpectedCall(property, args, this.getUnmet());
|
|
549
|
-
}
|
|
407
|
+
return (...args) => {
|
|
408
|
+
this.recordUnexpected(property, args);
|
|
409
|
+
throw new UnexpectedCall(property, args, this.getUnmet());
|
|
550
410
|
};
|
|
551
411
|
}
|
|
552
412
|
|
|
@@ -565,16 +425,18 @@ class FlexibleRepository {
|
|
|
565
425
|
*/
|
|
566
426
|
|
|
567
427
|
class StrongExpectation {
|
|
568
|
-
constructor(property, args, returnValue) {
|
|
428
|
+
constructor(property, args, returnValue, exactParams = false) {
|
|
569
429
|
this.property = void 0;
|
|
570
430
|
this.args = void 0;
|
|
571
431
|
this.returnValue = void 0;
|
|
432
|
+
this.exactParams = void 0;
|
|
572
433
|
this.matched = 0;
|
|
573
434
|
this.min = 1;
|
|
574
435
|
this.max = 1;
|
|
575
436
|
this.property = property;
|
|
576
437
|
this.args = args;
|
|
577
438
|
this.returnValue = returnValue;
|
|
439
|
+
this.exactParams = exactParams;
|
|
578
440
|
}
|
|
579
441
|
|
|
580
442
|
setInvocationCount(min, max = 1) {
|
|
@@ -604,6 +466,12 @@ class StrongExpectation {
|
|
|
604
466
|
return false;
|
|
605
467
|
}
|
|
606
468
|
|
|
469
|
+
if (this.exactParams) {
|
|
470
|
+
if (this.args.length !== received.length) {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
607
475
|
return this.args.every((arg, i) => arg.matches(received[i]));
|
|
608
476
|
}
|
|
609
477
|
|
|
@@ -613,55 +481,43 @@ class StrongExpectation {
|
|
|
613
481
|
|
|
614
482
|
}
|
|
615
483
|
|
|
616
|
-
class
|
|
617
|
-
constructor(createExpectation) {
|
|
484
|
+
class PendingExpectationWithFactory {
|
|
485
|
+
constructor(createExpectation, concreteMatcher, exactParams) {
|
|
618
486
|
this.createExpectation = void 0;
|
|
619
|
-
this.
|
|
620
|
-
this.
|
|
621
|
-
this.
|
|
622
|
-
this.
|
|
487
|
+
this.concreteMatcher = void 0;
|
|
488
|
+
this.exactParams = void 0;
|
|
489
|
+
this.args = void 0;
|
|
490
|
+
this.property = void 0;
|
|
623
491
|
this.createExpectation = createExpectation;
|
|
492
|
+
this.concreteMatcher = concreteMatcher;
|
|
493
|
+
this.exactParams = exactParams;
|
|
624
494
|
}
|
|
625
495
|
|
|
626
|
-
|
|
627
|
-
if (this.
|
|
496
|
+
setProperty(value) {
|
|
497
|
+
if (this.property) {
|
|
628
498
|
throw new UnfinishedExpectation(this);
|
|
629
499
|
}
|
|
630
500
|
|
|
631
|
-
this.
|
|
632
|
-
this._repo = repo;
|
|
633
|
-
this._concreteMatcher = concreteMatcher;
|
|
501
|
+
this.property = value;
|
|
634
502
|
}
|
|
635
503
|
|
|
636
|
-
|
|
637
|
-
this.
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
set args(value) {
|
|
641
|
-
this._args = value;
|
|
504
|
+
setArgs(value) {
|
|
505
|
+
this.args = value;
|
|
642
506
|
}
|
|
643
507
|
|
|
644
508
|
finish(returnValue) {
|
|
645
|
-
if (!this.
|
|
509
|
+
if (!this.property) {
|
|
646
510
|
throw new MissingWhen();
|
|
647
511
|
}
|
|
648
512
|
|
|
649
|
-
const expectation = this.createExpectation(this.
|
|
650
|
-
|
|
651
|
-
this.
|
|
652
|
-
|
|
653
|
-
this.clear();
|
|
513
|
+
const expectation = this.createExpectation(this.property, this.args, returnValue, this.concreteMatcher, this.exactParams);
|
|
514
|
+
this.property = undefined;
|
|
515
|
+
this.args = undefined;
|
|
654
516
|
return expectation;
|
|
655
517
|
}
|
|
656
518
|
|
|
657
|
-
clear() {
|
|
658
|
-
this._repo = undefined;
|
|
659
|
-
this._args = undefined;
|
|
660
|
-
this._property = '';
|
|
661
|
-
}
|
|
662
|
-
|
|
663
519
|
toJSON() {
|
|
664
|
-
return printWhen(this.
|
|
520
|
+
return printWhen(this.property, this.args);
|
|
665
521
|
}
|
|
666
522
|
|
|
667
523
|
}
|
|
@@ -952,7 +808,8 @@ const It = {
|
|
|
952
808
|
|
|
953
809
|
const defaults = {
|
|
954
810
|
concreteMatcher: It.deepEquals,
|
|
955
|
-
|
|
811
|
+
unexpectedProperty: exports.UnexpectedProperty.CALL_THROW,
|
|
812
|
+
exactParams: false
|
|
956
813
|
};
|
|
957
814
|
let currentDefaults = defaults;
|
|
958
815
|
/**
|
|
@@ -967,8 +824,128 @@ const setDefaults = newDefaults => {
|
|
|
967
824
|
currentDefaults = _extends({}, defaults, newDefaults);
|
|
968
825
|
};
|
|
969
826
|
|
|
970
|
-
|
|
971
|
-
|
|
827
|
+
/**
|
|
828
|
+
* Since `when` doesn't receive the mock subject (because we can't make it
|
|
829
|
+
* consistently return it from `mock()`, `mock.foo` and `mock.bar()`) we need
|
|
830
|
+
* to store a global state for the currently active mock.
|
|
831
|
+
*
|
|
832
|
+
* We also want to throw in the following case:
|
|
833
|
+
*
|
|
834
|
+
* ```
|
|
835
|
+
* when(() => mock()) // forgot returns here
|
|
836
|
+
* when(() => mock()) // should throw
|
|
837
|
+
* ```
|
|
838
|
+
*
|
|
839
|
+
* For that reason we can't just store the currently active mock, but also
|
|
840
|
+
* whether we finished the expectation or not.
|
|
841
|
+
*/
|
|
842
|
+
|
|
843
|
+
let activeMock;
|
|
844
|
+
const setActiveMock = mock => {
|
|
845
|
+
activeMock = mock;
|
|
846
|
+
};
|
|
847
|
+
const getActiveMock = () => activeMock;
|
|
848
|
+
/**
|
|
849
|
+
* Store a global map of all mocks created and their state.
|
|
850
|
+
*
|
|
851
|
+
* This is needed because we can't reliably pass the state between `when`
|
|
852
|
+
* and `thenReturn`.
|
|
853
|
+
*/
|
|
854
|
+
|
|
855
|
+
const mockMap = new Map();
|
|
856
|
+
const getMockState = mock => {
|
|
857
|
+
if (mockMap.has(mock)) {
|
|
858
|
+
return mockMap.get(mock);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
throw new NotAMock();
|
|
862
|
+
};
|
|
863
|
+
const setMockState = (mock, state) => {
|
|
864
|
+
mockMap.set(mock, state);
|
|
865
|
+
};
|
|
866
|
+
const getAllMocks = () => Array.from(mockMap.entries());
|
|
867
|
+
|
|
868
|
+
const createProxy = traps => // eslint-disable-next-line no-empty-function
|
|
869
|
+
new Proxy(
|
|
870
|
+
/* istanbul ignore next */
|
|
871
|
+
() => {}, {
|
|
872
|
+
get: (target, prop) => {
|
|
873
|
+
if (prop === 'bind') {
|
|
874
|
+
return (thisArg, ...args) => (...moreArgs) => traps.apply([...args, ...moreArgs]);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if (prop === 'apply') {
|
|
878
|
+
return (thisArg, args) => traps.apply(args || []);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (prop === 'call') {
|
|
882
|
+
return (thisArg, ...args) => traps.apply(args);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
return traps.property(prop);
|
|
886
|
+
},
|
|
887
|
+
apply: (target, thisArg, args) => traps.apply(args),
|
|
888
|
+
ownKeys: () => traps.ownKeys(),
|
|
889
|
+
|
|
890
|
+
getOwnPropertyDescriptor(target, prop) {
|
|
891
|
+
const keys = traps.ownKeys();
|
|
892
|
+
|
|
893
|
+
if (keys.includes(prop)) {
|
|
894
|
+
return {
|
|
895
|
+
configurable: true,
|
|
896
|
+
enumerable: true
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
return undefined;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
const createStub = (repo, pendingExpectation, getCurrentMode) => {
|
|
906
|
+
const stub = createProxy({
|
|
907
|
+
property: property => {
|
|
908
|
+
if (getCurrentMode() === Mode.CALL) {
|
|
909
|
+
return repo.get(property);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
setActiveMock(stub);
|
|
913
|
+
pendingExpectation.setProperty(property);
|
|
914
|
+
return createProxy({
|
|
915
|
+
property: childProp => {
|
|
916
|
+
throw new NestedWhen(property, childProp);
|
|
917
|
+
},
|
|
918
|
+
apply: args => {
|
|
919
|
+
pendingExpectation.setArgs(args);
|
|
920
|
+
},
|
|
921
|
+
ownKeys: () => {
|
|
922
|
+
throw new Error('Spreading during an expectation is not supported.');
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
},
|
|
926
|
+
apply: args => {
|
|
927
|
+
if (getCurrentMode() === Mode.CALL) {
|
|
928
|
+
return repo.apply(args);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
setActiveMock(stub);
|
|
932
|
+
pendingExpectation.setProperty(ApplyProp);
|
|
933
|
+
pendingExpectation.setArgs(args);
|
|
934
|
+
return undefined;
|
|
935
|
+
},
|
|
936
|
+
ownKeys: () => {
|
|
937
|
+
if (getCurrentMode() === Mode.CALL) {
|
|
938
|
+
return repo.getAllProperties();
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
throw new Error('Spreading during an expectation is not supported.');
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
return stub;
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
const strongExpectationFactory = (property, args, returnValue, concreteMatcher, exactParams) => new StrongExpectation(property, // Wrap every non-matcher in the default matcher.
|
|
948
|
+
args == null ? void 0 : args.map(arg => isMatcher(arg) ? arg : concreteMatcher(arg)), returnValue, exactParams);
|
|
972
949
|
|
|
973
950
|
var Mode;
|
|
974
951
|
|
|
@@ -981,6 +958,8 @@ let currentMode = Mode.CALL;
|
|
|
981
958
|
const setMode = mode => {
|
|
982
959
|
currentMode = mode;
|
|
983
960
|
};
|
|
961
|
+
|
|
962
|
+
const getMode = () => currentMode;
|
|
984
963
|
/**
|
|
985
964
|
* Create a type safe mock.
|
|
986
965
|
*
|
|
@@ -988,9 +967,12 @@ const setMode = mode => {
|
|
|
988
967
|
*
|
|
989
968
|
* @param options Configure the options for this specific mock, overriding any
|
|
990
969
|
* defaults that were set with {@link setDefaults}.
|
|
991
|
-
* @param options.
|
|
992
|
-
*
|
|
993
|
-
* @param options.concreteMatcher The matcher that will be used when one isn't
|
|
970
|
+
* @param options.unexpectedProperty Controls what happens when an unexpected
|
|
971
|
+
* property is accessed.
|
|
972
|
+
* @param options.concreteMatcher The matcher that will be used when one isn't
|
|
973
|
+
* specified explicitly.
|
|
974
|
+
* @param options.exactParams Controls whether the number of received arguments
|
|
975
|
+
* has to match the expectation.
|
|
994
976
|
*
|
|
995
977
|
* @example
|
|
996
978
|
* const fn = mock<() => number>();
|
|
@@ -1000,17 +982,20 @@ const setMode = mode => {
|
|
|
1000
982
|
* fn() === 23;
|
|
1001
983
|
*/
|
|
1002
984
|
|
|
985
|
+
|
|
1003
986
|
const mock = ({
|
|
1004
|
-
|
|
1005
|
-
concreteMatcher
|
|
987
|
+
unexpectedProperty,
|
|
988
|
+
concreteMatcher,
|
|
989
|
+
exactParams
|
|
1006
990
|
} = {}) => {
|
|
1007
|
-
const pendingExpectation = new RepoSideEffectPendingExpectation(strongExpectationFactory);
|
|
1008
991
|
const options = {
|
|
1009
|
-
|
|
1010
|
-
concreteMatcher: concreteMatcher != null ? concreteMatcher : currentDefaults.concreteMatcher
|
|
992
|
+
unexpectedProperty: unexpectedProperty != null ? unexpectedProperty : currentDefaults.unexpectedProperty,
|
|
993
|
+
concreteMatcher: concreteMatcher != null ? concreteMatcher : currentDefaults.concreteMatcher,
|
|
994
|
+
exactParams: exactParams != null ? exactParams : currentDefaults.exactParams
|
|
1011
995
|
};
|
|
1012
|
-
const repository = new FlexibleRepository(options.
|
|
1013
|
-
const
|
|
996
|
+
const repository = new FlexibleRepository(options.unexpectedProperty);
|
|
997
|
+
const pendingExpectation = new PendingExpectationWithFactory(strongExpectationFactory, options.concreteMatcher, options.exactParams);
|
|
998
|
+
const stub = createStub(repository, pendingExpectation, getMode);
|
|
1014
999
|
setMockState(stub, {
|
|
1015
1000
|
repository,
|
|
1016
1001
|
pendingExpectation,
|
|
@@ -1058,13 +1043,9 @@ const createInvocationCount = expectation => ({
|
|
|
1058
1043
|
|
|
1059
1044
|
});
|
|
1060
1045
|
|
|
1061
|
-
|
|
1062
|
-
* Set a return value for the currently pending expectation.
|
|
1063
|
-
*/
|
|
1064
|
-
|
|
1065
|
-
const finishPendingExpectation = (returnValue, pendingExpectation) => {
|
|
1046
|
+
const finishPendingExpectation = (returnValue, pendingExpectation, repo) => {
|
|
1066
1047
|
const finishedExpectation = pendingExpectation.finish(returnValue);
|
|
1067
|
-
|
|
1048
|
+
repo.add(finishedExpectation);
|
|
1068
1049
|
return createInvocationCount(finishedExpectation);
|
|
1069
1050
|
};
|
|
1070
1051
|
|
|
@@ -1080,45 +1061,29 @@ const getError = errorOrMessage => {
|
|
|
1080
1061
|
return new Error();
|
|
1081
1062
|
};
|
|
1082
1063
|
|
|
1083
|
-
const createReturns = pendingExpectation => {
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
}, pendingExpectation),
|
|
1107
|
-
thenResolve: promiseValue => finishPendingExpectation({
|
|
1108
|
-
value: promiseValue,
|
|
1109
|
-
isError: false,
|
|
1110
|
-
isPromise: true
|
|
1111
|
-
}, pendingExpectation),
|
|
1112
|
-
thenReject: errorOrMessage => finishPendingExpectation({
|
|
1113
|
-
value: getError(errorOrMessage),
|
|
1114
|
-
isError: true,
|
|
1115
|
-
isPromise: true
|
|
1116
|
-
}, pendingExpectation)
|
|
1117
|
-
}; // @ts-expect-error because the return type is a conditional, and we're merging
|
|
1118
|
-
// both branches here
|
|
1119
|
-
|
|
1120
|
-
return _extends({}, nonPromiseStub, promiseStub);
|
|
1121
|
-
};
|
|
1064
|
+
const createReturns = (pendingExpectation, repository) => ({
|
|
1065
|
+
thenReturn: returnValue => finishPendingExpectation( // This will handle both thenReturn(23) and thenReturn(Promise.resolve(3)).
|
|
1066
|
+
{
|
|
1067
|
+
value: returnValue,
|
|
1068
|
+
isError: false,
|
|
1069
|
+
isPromise: false
|
|
1070
|
+
}, pendingExpectation, repository),
|
|
1071
|
+
thenThrow: errorOrMessage => finishPendingExpectation({
|
|
1072
|
+
value: getError(errorOrMessage),
|
|
1073
|
+
isError: true,
|
|
1074
|
+
isPromise: false
|
|
1075
|
+
}, pendingExpectation, repository),
|
|
1076
|
+
thenResolve: promiseValue => finishPendingExpectation({
|
|
1077
|
+
value: promiseValue,
|
|
1078
|
+
isError: false,
|
|
1079
|
+
isPromise: true
|
|
1080
|
+
}, pendingExpectation, repository),
|
|
1081
|
+
thenReject: errorOrMessage => finishPendingExpectation({
|
|
1082
|
+
value: getError(errorOrMessage),
|
|
1083
|
+
isError: true,
|
|
1084
|
+
isPromise: true
|
|
1085
|
+
}, pendingExpectation, repository)
|
|
1086
|
+
});
|
|
1122
1087
|
|
|
1123
1088
|
/**
|
|
1124
1089
|
* Set an expectation on a mock.
|
|
@@ -1145,16 +1110,16 @@ const createReturns = pendingExpectation => {
|
|
|
1145
1110
|
* const fn = mock<(x: number) => Promise<number>();
|
|
1146
1111
|
* when(() => fn(23)).thenResolve(42);
|
|
1147
1112
|
*/
|
|
1148
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars,no-unused-vars
|
|
1149
1113
|
|
|
1150
1114
|
const when = expectation => {
|
|
1151
1115
|
setMode(Mode.EXPECT);
|
|
1152
1116
|
expectation();
|
|
1153
1117
|
setMode(Mode.CALL);
|
|
1154
1118
|
const {
|
|
1155
|
-
pendingExpectation
|
|
1119
|
+
pendingExpectation,
|
|
1120
|
+
repository
|
|
1156
1121
|
} = getMockState(getActiveMock());
|
|
1157
|
-
return createReturns(pendingExpectation);
|
|
1122
|
+
return createReturns(pendingExpectation, repository);
|
|
1158
1123
|
};
|
|
1159
1124
|
|
|
1160
1125
|
/**
|