strong-mock 8.0.0 → 9.0.0-beta.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.
Files changed (40) hide show
  1. package/README.md +53 -31
  2. package/dist/errors/api.d.ts +13 -0
  3. package/dist/errors/unexpected-access.d.ts +5 -0
  4. package/dist/errors/unexpected-call.d.ts +17 -0
  5. package/dist/errors/verify.d.ts +8 -0
  6. package/dist/expectation/expectation.d.ts +27 -30
  7. package/dist/expectation/repository/expectation-repository.d.ts +90 -90
  8. package/dist/expectation/repository/flexible-repository.d.ts +38 -38
  9. package/dist/expectation/repository/return-value.d.ts +13 -13
  10. package/dist/expectation/strong-expectation.d.ts +30 -30
  11. package/dist/index.d.ts +14 -9
  12. package/dist/index.js +815 -512
  13. package/dist/index.js.map +1 -1
  14. package/dist/matchers/deep-equals.d.ts +16 -0
  15. package/dist/matchers/is-any.d.ts +11 -0
  16. package/dist/matchers/is-array.d.ts +22 -0
  17. package/dist/matchers/is-number.d.ts +12 -0
  18. package/dist/matchers/is-object.d.ts +27 -0
  19. package/dist/matchers/is-string.d.ts +15 -0
  20. package/dist/matchers/is.d.ts +9 -0
  21. package/dist/matchers/it.d.ts +9 -0
  22. package/dist/matchers/matcher.d.ts +92 -0
  23. package/dist/matchers/will-capture.d.ts +21 -0
  24. package/dist/mock/defaults.d.ts +11 -11
  25. package/dist/mock/map.d.ts +16 -16
  26. package/dist/mock/mock.d.ts +29 -29
  27. package/dist/mock/options.d.ts +99 -99
  28. package/dist/mock/stub.d.ts +5 -5
  29. package/dist/print.d.ts +10 -10
  30. package/dist/proxy.d.ts +48 -48
  31. package/dist/return/invocation-count.d.ts +44 -44
  32. package/dist/return/returns.d.ts +61 -77
  33. package/dist/verify/reset.d.ts +20 -20
  34. package/dist/verify/verify.d.ts +27 -27
  35. package/dist/when/{pending-expectation.d.ts → expectation-builder.d.ts} +26 -31
  36. package/dist/when/when.d.ts +32 -32
  37. package/package.json +20 -16
  38. package/dist/errors.d.ts +0 -28
  39. package/dist/expectation/it.d.ts +0 -29
  40. package/dist/expectation/matcher.d.ts +0 -21
package/dist/index.js CHANGED
@@ -1,19 +1,84 @@
1
1
  var jestMatcherUtils = require('jest-matcher-utils');
2
+ var jestDiff = require('jest-diff');
3
+ var stripAnsi = require('strip-ansi');
2
4
  var lodash = require('lodash');
3
5
 
6
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
7
+
8
+ var stripAnsi__default = /*#__PURE__*/_interopDefaultLegacy(stripAnsi);
9
+
10
+ /**
11
+ * Special symbol denoting the call of a function.
12
+ */
13
+ const ApplyProp = Symbol('apply');
14
+
4
15
  const MATCHER_SYMBOL = Symbol('matcher');
5
- /**
6
- * Used to test if an expectation on an argument is a custom matcher.
16
+ /**
17
+ * Used to test if an expectation is an argument is a custom matcher.
7
18
  */
8
19
 
9
20
  function isMatcher(f) {
10
21
  return !!(f && f[MATCHER_SYMBOL]);
11
22
  }
12
-
13
- /**
14
- * Special symbol denoting the call of a function.
23
+ const getMatcherDiffs = (matchers, args) => {
24
+ const matcherDiffs = matchers.map((matcher, i) => matcher.getDiff(args[i]));
25
+ const actual = matcherDiffs.map(d => d.actual);
26
+ const expected = matcherDiffs.map(d => d.expected);
27
+ return {
28
+ actual,
29
+ expected
30
+ };
31
+ };
32
+ /**
33
+ * Create a custom matcher.
34
+ *
35
+ * @param predicate Will receive the actual value and return whether it matches the expectation.
36
+ * @param options
37
+ * @param options.toString An optional function that should return a string that will be
38
+ * used when the matcher needs to be printed in an error message. By default,
39
+ * it stringifies `predicate`.
40
+ * @param options.getDiff An optional function that will be called when printing the
41
+ * diff for a failed expectation. It will only be called if there's a mismatch
42
+ * between the expected and received values i.e. `predicate(actual)` fails.
43
+ * By default, the `toString` method will be used to format the expected value,
44
+ * while the received value will be returned as-is.
45
+ *
46
+ * @example
47
+ * // Create a matcher for positive numbers.
48
+ * const fn = mock<(x: number) => number>();
49
+ * when(() => fn(It.matches(x => x >= 0))).thenReturn(42);
50
+ *
51
+ * fn(2) === 42
52
+ * fn(-1) // throws
15
53
  */
16
- const ApplyProp = Symbol('apply');
54
+
55
+ const matches = (predicate, options) => {
56
+ var _options$toString, _options$getDiff;
57
+
58
+ // We can't use destructuring with default values because `options` is optional,
59
+ // so it needs a default value of `{}`, which will come with a native `toString`.
60
+ const toString = (_options$toString = options == null ? void 0 : options.toString) != null ? _options$toString : () => `Matcher(${predicate.toString()})`;
61
+ const getDiff = (_options$getDiff = options == null ? void 0 : options.getDiff) != null ? _options$getDiff : actual => ({
62
+ actual,
63
+ expected: toString()
64
+ });
65
+ const matcher = {
66
+ [MATCHER_SYMBOL]: true,
67
+ matches: actual => predicate(actual),
68
+ toString,
69
+ getDiff: actual => {
70
+ if (predicate(actual)) {
71
+ return {
72
+ actual,
73
+ expected: actual
74
+ };
75
+ }
76
+
77
+ return getDiff(actual);
78
+ }
79
+ };
80
+ return matcher;
81
+ };
17
82
 
18
83
  const printProperty = property => {
19
84
  if (property === ApplyProp) {
@@ -26,12 +91,26 @@ const printProperty = property => {
26
91
 
27
92
  return `.${property}`;
28
93
  };
29
- const printArg = arg => // Call toJSON on matchers directly to avoid wrapping them in quotes.
30
- isMatcher(arg) ? arg.toJSON() : jestMatcherUtils.printExpected(arg);
94
+ const printValue = arg => {
95
+ // Call toString on matchers directly to avoid wrapping strings returned by them in quotes.
96
+ if (isMatcher(arg)) {
97
+ return arg.toString();
98
+ }
99
+
100
+ return jestMatcherUtils.stringify(arg);
101
+ };
102
+
103
+ const printArgs = args => args.map(arg => printValue(arg)).join(', ');
104
+
31
105
  const printCall = (property, args) => {
32
- const prettyArgs = args.map(arg => printArg(arg)).join(', ');
33
106
  const prettyProperty = printProperty(property);
34
- return `${prettyProperty}(${prettyArgs})`;
107
+
108
+ if (args) {
109
+ const prettyArgs = printArgs(args);
110
+ return `mock${jestMatcherUtils.RECEIVED_COLOR(`${prettyProperty}(${prettyArgs})`)}`;
111
+ }
112
+
113
+ return `mock${jestMatcherUtils.RECEIVED_COLOR(`${prettyProperty}`)}`;
35
114
  };
36
115
  const printReturns = ({
37
116
  isError,
@@ -52,118 +131,141 @@ const printReturns = ({
52
131
  thenPrefix += 'thenReturn';
53
132
  }
54
133
 
55
- return `.${thenPrefix}(${jestMatcherUtils.printExpected(value)}).between(${min}, ${max})`;
134
+ return `.${thenPrefix}(${jestMatcherUtils.RECEIVED_COLOR(printValue(value))}).between(${min}, ${max})`;
56
135
  };
57
136
  const printWhen = (property, args) => {
137
+ const prettyProperty = printProperty(property);
138
+
58
139
  if (args) {
59
- return `when(() => ${jestMatcherUtils.EXPECTED_COLOR(`mock${printCall(property, args)}`)})`;
140
+ return `when(() => mock${jestMatcherUtils.EXPECTED_COLOR(`${prettyProperty}(${printArgs(args)})`)})`;
60
141
  }
61
142
 
62
- return `when(() => ${jestMatcherUtils.EXPECTED_COLOR(`mock${printProperty(property)}`)})`;
143
+ return `when(() => mock${jestMatcherUtils.EXPECTED_COLOR(`${printProperty(property)}`)})`;
63
144
  };
64
145
  const printExpectation = (property, args, returnValue, min, max) => `${printWhen(property, args)}${printReturns(returnValue, min, max)}`;
65
146
  const printRemainingExpectations = expectations => expectations.length ? `Remaining unmet expectations:
66
- - ${expectations.map(e => e.toJSON()).join('\n - ')}` : 'There are no remaining unmet expectations.';
67
-
68
- class UnfinishedExpectation extends Error {
69
- constructor(pendingExpectation) {
70
- super(`There is an unfinished pending expectation:
71
-
72
- ${pendingExpectation.toJSON()}
73
-
74
- Please finish it by setting a return value even if the value
75
- is undefined.`);
76
- }
147
+ - ${expectations.map(e => e.toString()).join('\n - ')}` : 'There are no remaining unmet expectations.';
77
148
 
78
- }
79
- class MissingWhen extends Error {
80
- constructor() {
81
- super(`You tried setting a return value without an expectation.
82
-
83
- Every call to set a return value must be preceded by an expectation.`);
84
- }
85
-
86
- }
87
149
  class UnexpectedAccess extends Error {
88
150
  constructor(property, expectations) {
89
- super(`Didn't expect ${jestMatcherUtils.EXPECTED_COLOR(`mock${printProperty(property)}`)} to be accessed.
151
+ super(jestMatcherUtils.DIM_COLOR(`Didn't expect ${printCall(property)} to be accessed.
90
152
 
91
153
  If you expect this property to be accessed then please
92
154
  set an expectation for it.
93
155
 
94
- ${printRemainingExpectations(expectations)}`);
156
+ ${printRemainingExpectations(expectations)}`));
95
157
  }
96
158
 
97
159
  }
98
- class UnexpectedCall extends Error {
99
- constructor(property, args, expectations) {
100
- super(`Didn't expect ${jestMatcherUtils.EXPECTED_COLOR(`mock${printCall(property, args)}`)} to be called.
101
160
 
102
- ${printRemainingExpectations(expectations)}`);
161
+ const printArgsDiff = (expected, actual) => {
162
+ const diff = jestDiff.diff(expected, actual, {
163
+ omitAnnotationLines: true
164
+ });
165
+ /* istanbul ignore next this is not expected in practice */
166
+
167
+ if (!diff) {
168
+ return '';
103
169
  }
104
170
 
105
- }
106
- class NotAMock extends Error {
107
- constructor() {
108
- super(`We couldn't find the mock.
171
+ const ansilessDiffLines = stripAnsi__default["default"](diff).split('\n');
172
+ let relevantDiffLines; // Strip Array [ ... ] surroundings.
173
+
174
+ if (!expected.length) {
175
+ // - Array []
176
+ // + Array [
177
+ // ...
178
+ // ]
179
+ relevantDiffLines = ansilessDiffLines.slice(2, -1);
180
+ } else if (!actual.length) {
181
+ // - Array [
182
+ // ...
183
+ // ]
184
+ // + Array []
185
+ relevantDiffLines = ansilessDiffLines.slice(1, -2);
186
+ } else {
187
+ // Array [
188
+ // ...
189
+ // ]
190
+ relevantDiffLines = ansilessDiffLines.slice(1, -1);
191
+ } // Strip the trailing comma.
109
192
 
110
- Make sure you're passing in an actual mock.`);
111
- }
112
193
 
113
- }
114
- class UnmetExpectations extends Error {
115
- constructor(expectations) {
116
- super(`There are unmet expectations:
194
+ const lastLine = relevantDiffLines[relevantDiffLines.length - 1].slice(0, -1);
195
+ const coloredDiffLines = [...relevantDiffLines.slice(0, -1), lastLine].map(line => {
196
+ const first = line.charAt(0);
117
197
 
118
- - ${expectations.map(e => e.toJSON()).join('\n - ')}`);
119
- }
198
+ switch (first) {
199
+ case '-':
200
+ return jestMatcherUtils.EXPECTED_COLOR(line);
120
201
 
121
- }
122
- /**
123
- * Merge property accesses and method calls for the same property
124
- * into a single call.
125
- *
126
- * @example
127
- * mergeCalls({ foo: [{ arguments: undefined }, { arguments: [1, 2, 3] }] }
128
- * // returns { foo: [{ arguments: [1, 2, 3] } }
129
- */
202
+ case '+':
203
+ return jestMatcherUtils.RECEIVED_COLOR(line);
130
204
 
131
- const mergeCalls = callMap => new Map(Array.from(callMap.entries()).map(([property, calls]) => {
132
- const hasMethodCalls = calls.some(call => call.arguments);
133
- const hasPropertyAccesses = calls.some(call => !call.arguments);
205
+ default:
206
+ return line;
207
+ }
208
+ });
209
+ return coloredDiffLines.join('\n');
210
+ };
211
+ const printExpectationDiff = (e, args) => {
212
+ var _e$args;
134
213
 
135
- if (hasMethodCalls && hasPropertyAccesses) {
136
- return [property, calls.filter(call => call.arguments)];
214
+ if (!((_e$args = e.args) != null && _e$args.length)) {
215
+ return '';
137
216
  }
138
217
 
139
- return [property, calls];
140
- }));
141
-
142
- class UnexpectedCalls extends Error {
143
- constructor(unexpectedCalls, expectations) {
144
- const printedCalls = Array.from(mergeCalls(unexpectedCalls).entries()).map(([property, calls]) => calls.map(call => call.arguments ? jestMatcherUtils.EXPECTED_COLOR(`mock${printCall(property, call.arguments)}`) : jestMatcherUtils.EXPECTED_COLOR(`mock${printProperty(property)}`)).join('\n - ')).join('\n - ');
145
- super(`The following calls were unexpected:
218
+ const {
219
+ actual,
220
+ expected
221
+ } = getMatcherDiffs(e.args, args);
222
+ return printArgsDiff(expected, actual);
223
+ };
224
+ const printDiffForAllExpectations = (expectations, actual) => expectations.map(e => {
225
+ const diff = printExpectationDiff(e, actual);
146
226
 
147
- - ${printedCalls}
227
+ if (diff) {
228
+ return `${e.toString()}
229
+ ${jestMatcherUtils.EXPECTED_COLOR('- Expected')}
230
+ ${jestMatcherUtils.RECEIVED_COLOR('+ Received')}
148
231
 
149
- ${printRemainingExpectations(expectations)}`);
232
+ ${diff}`;
150
233
  }
151
234
 
152
- }
153
- class NestedWhen extends Error {
154
- constructor(parentProp, childProp) {
155
- const snippet = `
156
- const parentMock = mock<T1>();
157
- const childMock = mock<T2>();
235
+ return undefined;
236
+ }).filter(x => x).join('\n\n');
237
+ class UnexpectedCall extends Error {
238
+ constructor(property, args, expectations) {
239
+ const header = `Didn't expect ${printCall(property, args)} to be called.`;
240
+ const propertyExpectations = expectations.filter(e => e.property === property);
158
241
 
159
- when(() => childMock${printProperty(childProp)}).thenReturn(...);
160
- when(() => parentMock${printProperty(parentProp)}).thenReturn(childMock)
161
- `;
162
- super(`Setting an expectation on a nested property is not supported.
242
+ if (propertyExpectations.length) {
243
+ var _propertyExpectations;
163
244
 
164
- You can return an object directly when the first property is accessed,
165
- or you can even return a separate mock:
166
- ${snippet}`);
245
+ super(jestMatcherUtils.DIM_COLOR(`${header}
246
+
247
+ Remaining expectations:
248
+ ${printDiffForAllExpectations(propertyExpectations, args)}`)); // If we have a single expectation we can attach the actual/expected args
249
+ // to the error instance, so that an IDE may show its own diff for them.
250
+
251
+ this.matcherResult = void 0;
252
+
253
+ if (propertyExpectations.length === 1 && (_propertyExpectations = propertyExpectations[0].args) != null && _propertyExpectations.length) {
254
+ const {
255
+ actual,
256
+ expected
257
+ } = getMatcherDiffs(propertyExpectations[0].args, args);
258
+ this.matcherResult = {
259
+ actual,
260
+ expected
261
+ };
262
+ }
263
+ } else {
264
+ super(jestMatcherUtils.DIM_COLOR(`${header}
265
+
266
+ No remaining expectations.`));
267
+ this.matcherResult = void 0;
268
+ }
167
269
  }
168
270
 
169
271
  }
@@ -171,49 +273,49 @@ ${snippet}`);
171
273
  exports.UnexpectedProperty = void 0;
172
274
 
173
275
  (function (UnexpectedProperty) {
174
- /**
175
- * Throw an error immediately.
176
- *
177
- * @example
178
- * // Will throw "Didn't expect foo to be accessed".
179
- * const { foo } = service;
180
- *
181
- * // Will throw "Didn't expect foo to be accessed",
182
- * // without printing the arguments.
183
- * foo(42);
276
+ /**
277
+ * Throw an error immediately.
278
+ *
279
+ * @example
280
+ * // Will throw "Didn't expect foo to be accessed".
281
+ * const { foo } = service;
282
+ *
283
+ * // Will throw "Didn't expect foo to be accessed",
284
+ * // without printing the arguments.
285
+ * foo(42);
184
286
  */
185
287
  UnexpectedProperty[UnexpectedProperty["THROW"] = 0] = "THROW";
186
- /**
187
- * Return a function that will throw if called. This can be useful if your
188
- * code destructures a function but never calls it.
189
- *
190
- * It will also improve error messages for unexpected calls because arguments
191
- * will be captured instead of throwing immediately on the property access.
192
- *
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.
196
- *
197
- * @example
198
- * // This will NOT throw.
199
- * const { foo } = service;
200
- *
201
- * // This will NOT throw, and might produce unexpected results.
202
- * foo > 0
203
- *
204
- * // Will throw "Didn't expect foo(42) to be called".
205
- * foo(42);
288
+ /**
289
+ * Return a function that will throw if called. This can be useful if your
290
+ * code destructures a function but never calls it.
291
+ *
292
+ * It will also improve error messages for unexpected calls because arguments
293
+ * will be captured instead of throwing immediately on the property access.
294
+ *
295
+ * The function will be returned even if the property is not supposed to be a
296
+ * function. This could cause weird behavior at runtime, when your code expects
297
+ * e.g. a number and gets a function instead.
298
+ *
299
+ * @example
300
+ * // This will NOT throw.
301
+ * const { foo } = service;
302
+ *
303
+ * // This will NOT throw, and might produce unexpected results.
304
+ * foo > 0
305
+ *
306
+ * // Will throw "Didn't expect foo(42) to be called".
307
+ * foo(42);
206
308
  */
207
309
 
208
310
  UnexpectedProperty[UnexpectedProperty["CALL_THROW"] = 1] = "CALL_THROW";
209
311
  })(exports.UnexpectedProperty || (exports.UnexpectedProperty = {}));
210
312
 
211
- /**
212
- * Unbox the expectation's return value.
213
- *
214
- * If the value is an error then throw it.
215
- *
216
- * If the value is a promise then resolve/reject it.
313
+ /**
314
+ * Unbox the expectation's return value.
315
+ *
316
+ * If the value is an error then throw it.
317
+ *
318
+ * If the value is a promise then resolve/reject it.
217
319
  */
218
320
  const unboxReturnValue = ({
219
321
  isError,
@@ -243,9 +345,9 @@ const unboxReturnValue = ({
243
345
  return value;
244
346
  };
245
347
 
246
- /**
247
- * An expectation repository with a configurable behavior for
248
- * unexpected property access.
348
+ /**
349
+ * An expectation repository with a configurable behavior for
350
+ * unexpected property access.
249
351
  */
250
352
 
251
353
  class FlexibleRepository {
@@ -295,6 +397,11 @@ class FlexibleRepository {
295
397
  case Symbol.toStringTag:
296
398
  case 'name':
297
399
  return 'mock';
400
+ // Promise.resolve() tries to see if it's a "thenable".
401
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables
402
+
403
+ case 'then':
404
+ return undefined;
298
405
  // pretty-format
299
406
 
300
407
  case '$$typeof':
@@ -412,16 +519,16 @@ class FlexibleRepository {
412
519
 
413
520
  }
414
521
 
415
- /**
416
- * Matches a call with more parameters than expected because it is assumed the
417
- * compiler will check that those parameters are optional.
418
- *
419
- * @example
420
- * new StrongExpectation(
421
- * 'bar',
422
- * deepEquals([1, 2, 3]),
423
- * 23
424
- * ).matches('bar', [1, 2, 3]) === true;
522
+ /**
523
+ * Matches a call with more parameters than expected because it is assumed the
524
+ * compiler will check that those parameters are optional.
525
+ *
526
+ * @example
527
+ * new StrongExpectation(
528
+ * 'bar',
529
+ * deepEquals([1, 2, 3]),
530
+ * 23
531
+ * ).matches('bar', [1, 2, 3]) === true;
425
532
  */
426
533
 
427
534
  class StrongExpectation {
@@ -475,13 +582,58 @@ class StrongExpectation {
475
582
  return this.args.every((arg, i) => arg.matches(received[i]));
476
583
  }
477
584
 
478
- toJSON() {
585
+ toString() {
479
586
  return printExpectation(this.property, this.args, this.returnValue, this.min, this.max);
480
587
  }
481
588
 
482
589
  }
483
590
 
484
- class PendingExpectationWithFactory {
591
+ class UnfinishedExpectation extends Error {
592
+ constructor(property, args) {
593
+ super(`There is an unfinished pending expectation:
594
+
595
+ ${printWhen(property, args)}
596
+
597
+ Please finish it by setting a return value even if the value
598
+ is undefined.`);
599
+ }
600
+
601
+ }
602
+ class MissingWhen extends Error {
603
+ constructor() {
604
+ super(`You tried setting a return value without an expectation.
605
+
606
+ Every call to set a return value must be preceded by an expectation.`);
607
+ }
608
+
609
+ }
610
+ class NotAMock extends Error {
611
+ constructor() {
612
+ super(`We couldn't find the mock.
613
+
614
+ Make sure you're passing in an actual mock.`);
615
+ }
616
+
617
+ }
618
+ class NestedWhen extends Error {
619
+ constructor(parentProp, childProp) {
620
+ const snippet = `
621
+ const parentMock = mock<T1>();
622
+ const childMock = mock<T2>();
623
+
624
+ when(() => childMock${printProperty(childProp)}).thenReturn(...);
625
+ when(() => parentMock${printProperty(parentProp)}).thenReturn(childMock)
626
+ `;
627
+ super(`Setting an expectation on a nested property is not supported.
628
+
629
+ You can return an object directly when the first property is accessed,
630
+ or you can even return a separate mock:
631
+ ${snippet}`);
632
+ }
633
+
634
+ }
635
+
636
+ class ExpectationBuilderWithFactory {
485
637
  constructor(createExpectation, concreteMatcher, exactParams) {
486
638
  this.createExpectation = void 0;
487
639
  this.concreteMatcher = void 0;
@@ -495,7 +647,7 @@ class PendingExpectationWithFactory {
495
647
 
496
648
  setProperty(value) {
497
649
  if (this.property) {
498
- throw new UnfinishedExpectation(this);
650
+ throw new UnfinishedExpectation(this.property, this.args);
499
651
  }
500
652
 
501
653
  this.property = value;
@@ -516,10 +668,6 @@ class PendingExpectationWithFactory {
516
668
  return expectation;
517
669
  }
518
670
 
519
- toJSON() {
520
- return printWhen(this.property, this.args);
521
- }
522
-
523
671
  }
524
672
 
525
673
  function _extends() {
@@ -540,33 +688,6 @@ function _extends() {
540
688
  return _extends.apply(this, arguments);
541
689
  }
542
690
 
543
- /**
544
- * Match a custom predicate.
545
- *
546
- * @param cb Will receive the value and returns whether it matches.
547
- * @param toJSON An optional function that should return a string that will be
548
- * used when the matcher needs to be printed in an error message. By default,
549
- * it stringifies `cb`.
550
- *
551
- * @example
552
- * const fn = mock<(x: number) => number>();
553
- * when(() => fn(It.matches(x => x >= 0))).returns(42);
554
- *
555
- * fn(2) === 42
556
- * fn(-1) // throws
557
- */
558
-
559
- const matches = (cb, {
560
- toJSON = () => `matches(${cb.toString()})`
561
- } = {}) => {
562
- const matcher = {
563
- [MATCHER_SYMBOL]: true,
564
- matches: arg => cb(arg),
565
- toJSON
566
- };
567
- return matcher;
568
- };
569
-
570
691
  const removeUndefined = object => {
571
692
  if (Array.isArray(object)) {
572
693
  return object.map(x => removeUndefined(x));
@@ -578,16 +699,17 @@ const removeUndefined = object => {
578
699
 
579
700
  return lodash.omitBy(object, lodash.isUndefined);
580
701
  };
581
- /**
582
- * Compare values using deep equality.
583
- *
584
- * @param expected
585
- * @param strict By default, this matcher will treat a missing key in an object
586
- * and a key with the value `undefined` as not equal. It will also consider
587
- * non `Object` instances with different constructors as not equal. Setting
588
- * this to `false` will consider the objects in both cases as equal.
589
- *
590
- * @see It.is A matcher that uses strict equality.
702
+ /**
703
+ * Compare values using deep equality.
704
+ *
705
+ * @param expected
706
+ * @param strict By default, this matcher will treat a missing key in an object
707
+ * and a key with the value `undefined` as not equal. It will also consider
708
+ * non `Object` instances with different constructors as not equal. Setting
709
+ * this to `false` will consider the objects in both cases as equal.
710
+ *
711
+ * @see {@link It.isObject} or {@link It.isArray} if you want to nest matchers.
712
+ * @see {@link It.is} if you want to use strict equality.
591
713
  */
592
714
 
593
715
 
@@ -600,244 +722,45 @@ const deepEquals = (expected, {
600
722
 
601
723
  return lodash.isEqual(removeUndefined(actual), removeUndefined(expected));
602
724
  }, {
603
- toJSON: () => printArg(expected)
604
- });
605
- /**
606
- * Compare values using `Object.is`.
607
- *
608
- * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
609
- *
610
- * @see It.deepEquals A matcher that uses deep equality.
611
- */
612
-
613
-
614
- const is = expected => matches(actual => Object.is(actual, expected), {
615
- toJSON: () => `${jestMatcherUtils.printExpected(expected)}`
616
- });
617
- /**
618
- * Match any value, including `undefined` and `null`.
619
- *
620
- * @example
621
- * const fn = mock<(x: number, y: string) => number>();
622
- * when(() => fn(It.isAny(), It.isAny())).thenReturn(1);
623
- *
624
- * fn(23, 'foobar') === 1
625
- */
626
-
627
-
628
- const isAny = () => matches(() => true, {
629
- toJSON: () => 'anything'
725
+ toString: () => printValue(expected),
726
+ getDiff: actual => ({
727
+ actual,
728
+ expected
729
+ })
630
730
  });
631
- /**
632
- * Recursively match an object.
633
- *
634
- * Supports nested matcher.
635
- *
636
- * @param partial An optional subset of the expected objected.
637
- *
638
- * @example
639
- * const fn = mock<(foo: { x: number, y: number }) => number>();
640
- * when(() => fn(It.isObject({ x: 23 }))).returns(42);
641
- *
642
- * fn({ x: 100, y: 200 }) // throws
643
- * fn({ x: 23, y: 200 }) // returns 42
644
- *
645
- * @example
646
- * It.isObject({ foo: It.isString() })
647
- */
648
-
649
-
650
- const isObject = partial => matches(actual => lodash.isMatchWith(actual, partial || {}, (argValue, partialValue) => {
651
- if (isMatcher(partialValue)) {
652
- return partialValue.matches(argValue);
653
- } // Let lodash handle it otherwise.
654
-
655
-
656
- return undefined;
657
- }), {
658
- toJSON: () => partial ? `object(${jestMatcherUtils.printExpected(partial)})` : 'object'
659
- });
660
- /**
661
- * Match any number.
662
- *
663
- * @example
664
- * const fn = mock<(x: number) => number>();
665
- * when(() => fn(It.isNumber())).returns(42);
666
- *
667
- * fn(20.5) === 42
668
- * fn(NaN) // throws
669
- */
670
-
671
-
672
- const isNumber = () => matches(actual => typeof actual === 'number' && !Number.isNaN(actual), {
673
- toJSON: () => 'number'
674
- });
675
- /**
676
- * Match a string, potentially by a pattern.
677
- *
678
- * @param matching The string has to match this RegExp.
679
- * @param containing The string has to contain this substring.
680
- *
681
- * @example
682
- * const fn = mock<(x: string, y: string) => number>();
683
- * when(() => fn(It.isString(), It.isString({ containing: 'bar' }))).returns(42);
684
- *
685
- * fn('foo', 'baz') // throws
686
- * fn('foo', 'bar') === 42
687
- */
688
-
689
-
690
- const isString = ({
691
- matching,
692
- containing
693
- } = {}) => {
694
- if (matching && containing) {
695
- throw new Error('You can only pass `matching` or `containing`, not both.');
696
- }
697
-
698
- return matches(actual => {
699
- var _matching$test;
700
-
701
- if (typeof actual !== 'string') {
702
- return false;
703
- }
704
-
705
- if (containing) {
706
- return actual.indexOf(containing) !== -1;
707
- }
708
-
709
- return (_matching$test = matching == null ? void 0 : matching.test(actual)) != null ? _matching$test : true;
710
- }, {
711
- toJSON: () => containing || matching ? `string(${jestMatcherUtils.printExpected(containing || matching)})` : 'string'
712
- });
713
- };
714
- /**
715
- * Match an array.
716
- *
717
- * Supports nested matchers.
718
- *
719
- * @param containing If given, the matched array has to contain ALL of these
720
- * elements in ANY order.
721
- *
722
- * @example
723
- * const fn = mock<(arr: number[]) => number>();
724
- * when(() => fn(It.isArray())).thenReturn(1);
725
- * when(() => fn(It.isArray([2, 3]))).thenReturn(2);
726
- *
727
- * fn({ length: 1, 0: 42 }) // throws
728
- * fn([]) === 1
729
- * fn([3, 2, 1]) === 2
730
- *
731
- * @example
732
- * It.isArray([It.isString({ containing: 'foobar' })])
733
- */
734
-
735
-
736
- const isArray = containing => matches(actual => {
737
- if (!Array.isArray(actual)) {
738
- return false;
739
- }
740
-
741
- if (!containing) {
742
- return true;
743
- }
744
-
745
- return containing.every(x => actual.find(y => {
746
- if (isMatcher(x)) {
747
- return x.matches(y);
748
- }
749
-
750
- return deepEquals(x).matches(y);
751
- }) !== undefined);
752
- }, {
753
- toJSON: () => containing ? `array(${jestMatcherUtils.printExpected(containing)})` : 'array'
754
- });
755
- /**
756
- * Matches anything and stores the received value.
757
- *
758
- * This should not be needed for most cases, but can be useful if you need
759
- * access to a complex argument outside the expectation e.g. to test a
760
- * callback.
761
- *
762
- * @param name If given, this name will be printed in error messages.
763
- *
764
- * @example
765
- * const fn = mock<(cb: (value: number) => number) => void>();
766
- * const matcher = It.willCapture();
767
- * when(() => fn(matcher)).thenReturn();
768
- *
769
- * fn(x => x + 1);
770
- * matcher.value?.(3) === 4
771
- */
772
-
773
-
774
- const willCapture = name => {
775
- let capturedValue;
776
- const matcher = {
777
- [MATCHER_SYMBOL]: true,
778
- matches: actual => {
779
- capturedValue = actual;
780
- return true;
781
- },
782
- toJSON: () => name != null ? name : 'captures',
783
-
784
- get value() {
785
- return capturedValue;
786
- }
787
-
788
- };
789
- return matcher;
790
- };
791
- /**
792
- * Contains argument matchers that can be used to ignore arguments in an
793
- * expectation or to match complex arguments.
794
- */
795
-
796
-
797
- const It = {
798
- matches,
799
- deepEquals,
800
- is,
801
- isAny,
802
- isObject,
803
- isNumber,
804
- isString,
805
- isArray,
806
- willCapture
807
- };
808
731
 
809
732
  const defaults = {
810
- concreteMatcher: It.deepEquals,
733
+ concreteMatcher: deepEquals,
811
734
  unexpectedProperty: exports.UnexpectedProperty.CALL_THROW,
812
735
  exactParams: false
813
736
  };
814
737
  let currentDefaults = defaults;
815
- /**
816
- * Override strong-mock's defaults.
817
- *
818
- * @param newDefaults These will be applied to the library defaults. Multiple
819
- * calls don't stack e.g. calling this with `{}` will clear any previously
820
- * applied defaults.
738
+ /**
739
+ * Override strong-mock's defaults.
740
+ *
741
+ * @param newDefaults These will be applied to the library defaults. Multiple
742
+ * calls don't stack e.g. calling this with `{}` will clear any previously
743
+ * applied defaults.
821
744
  */
822
745
 
823
746
  const setDefaults = newDefaults => {
824
747
  currentDefaults = _extends({}, defaults, newDefaults);
825
748
  };
826
749
 
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.
750
+ /**
751
+ * Since `when` doesn't receive the mock subject (because we can't make it
752
+ * consistently return it from `mock()`, `mock.foo` and `mock.bar()`) we need
753
+ * to store a global state for the currently active mock.
754
+ *
755
+ * We also want to throw in the following case:
756
+ *
757
+ * ```
758
+ * when(() => mock()) // forgot returns here
759
+ * when(() => mock()) // should throw
760
+ * ```
761
+ *
762
+ * For that reason we can't just store the currently active mock, but also
763
+ * whether we finished the expectation or not.
841
764
  */
842
765
 
843
766
  let activeMock;
@@ -845,11 +768,11 @@ const setActiveMock = mock => {
845
768
  activeMock = mock;
846
769
  };
847
770
  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`.
771
+ /**
772
+ * Store a global map of all mocks created and their state.
773
+ *
774
+ * This is needed because we can't reliably pass the state between `when`
775
+ * and `thenReturn`.
853
776
  */
854
777
 
855
778
  const mockMap = new Map();
@@ -865,7 +788,9 @@ const setMockState = (mock, state) => {
865
788
  };
866
789
  const getAllMocks = () => Array.from(mockMap.entries());
867
790
 
868
- const createProxy = traps => // eslint-disable-next-line no-empty-function
791
+ const createProxy = traps => // The Proxy target MUST be a function, otherwise we can't use the `apply` trap:
792
+ // https://262.ecma-international.org/6.0/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist
793
+ // eslint-disable-next-line no-empty-function,@typescript-eslint/no-empty-function
869
794
  new Proxy(
870
795
  /* istanbul ignore next */
871
796
  () => {}, {
@@ -902,7 +827,7 @@ new Proxy(
902
827
 
903
828
  });
904
829
 
905
- const createStub = (repo, pendingExpectation, getCurrentMode) => {
830
+ const createStub = (repo, builder, getCurrentMode) => {
906
831
  const stub = createProxy({
907
832
  property: property => {
908
833
  if (getCurrentMode() === Mode.CALL) {
@@ -910,13 +835,13 @@ const createStub = (repo, pendingExpectation, getCurrentMode) => {
910
835
  }
911
836
 
912
837
  setActiveMock(stub);
913
- pendingExpectation.setProperty(property);
838
+ builder.setProperty(property);
914
839
  return createProxy({
915
840
  property: childProp => {
916
841
  throw new NestedWhen(property, childProp);
917
842
  },
918
843
  apply: args => {
919
- pendingExpectation.setArgs(args);
844
+ builder.setArgs(args);
920
845
  },
921
846
  ownKeys: () => {
922
847
  throw new Error('Spreading during an expectation is not supported.');
@@ -929,8 +854,8 @@ const createStub = (repo, pendingExpectation, getCurrentMode) => {
929
854
  }
930
855
 
931
856
  setActiveMock(stub);
932
- pendingExpectation.setProperty(ApplyProp);
933
- pendingExpectation.setArgs(args);
857
+ builder.setProperty(ApplyProp);
858
+ builder.setArgs(args);
934
859
  return undefined;
935
860
  },
936
861
  ownKeys: () => {
@@ -960,26 +885,26 @@ const setMode = mode => {
960
885
  };
961
886
 
962
887
  const getMode = () => currentMode;
963
- /**
964
- * Create a type safe mock.
965
- *
966
- * @see {@link when} Set expectations on the mock using `when`.
967
- *
968
- * @param options Configure the options for this specific mock, overriding any
969
- * defaults that were set with {@link setDefaults}.
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.
976
- *
977
- * @example
978
- * const fn = mock<() => number>();
979
- *
980
- * when(() => fn()).thenReturn(23);
981
- *
982
- * fn() === 23;
888
+ /**
889
+ * Create a type safe mock.
890
+ *
891
+ * @see {@link when} Set expectations on the mock using `when`.
892
+ *
893
+ * @param options Configure the options for this specific mock, overriding any
894
+ * defaults that were set with {@link setDefaults}.
895
+ * @param options.unexpectedProperty Controls what happens when an unexpected
896
+ * property is accessed.
897
+ * @param options.concreteMatcher The matcher that will be used when one isn't
898
+ * specified explicitly.
899
+ * @param options.exactParams Controls whether the number of received arguments
900
+ * has to match the expectation.
901
+ *
902
+ * @example
903
+ * const fn = mock<() => number>();
904
+ *
905
+ * when(() => fn()).thenReturn(23);
906
+ *
907
+ * fn() === 23;
983
908
  */
984
909
 
985
910
 
@@ -994,11 +919,11 @@ const mock = ({
994
919
  exactParams: exactParams != null ? exactParams : currentDefaults.exactParams
995
920
  };
996
921
  const repository = new FlexibleRepository(options.unexpectedProperty);
997
- const pendingExpectation = new PendingExpectationWithFactory(strongExpectationFactory, options.concreteMatcher, options.exactParams);
998
- const stub = createStub(repository, pendingExpectation, getMode);
922
+ const builder = new ExpectationBuilderWithFactory(strongExpectationFactory, options.concreteMatcher, options.exactParams);
923
+ const stub = createStub(repository, builder, getMode);
999
924
  setMockState(stub, {
1000
925
  repository,
1001
- pendingExpectation,
926
+ builder,
1002
927
  options
1003
928
  });
1004
929
  return stub;
@@ -1043,8 +968,8 @@ const createInvocationCount = expectation => ({
1043
968
 
1044
969
  });
1045
970
 
1046
- const finishPendingExpectation = (returnValue, pendingExpectation, repo) => {
1047
- const finishedExpectation = pendingExpectation.finish(returnValue);
971
+ const finishExpectation = (returnValue, builder, repo) => {
972
+ const finishedExpectation = builder.finish(returnValue);
1048
973
  repo.add(finishedExpectation);
1049
974
  return createInvocationCount(finishedExpectation);
1050
975
  };
@@ -1061,54 +986,54 @@ const getError = errorOrMessage => {
1061
986
  return new Error();
1062
987
  };
1063
988
 
1064
- const createReturns = (pendingExpectation, repository) => ({
1065
- thenReturn: returnValue => finishPendingExpectation( // This will handle both thenReturn(23) and thenReturn(Promise.resolve(3)).
989
+ const createReturns = (builder, repository) => ({
990
+ thenReturn: returnValue => finishExpectation( // This will handle both thenReturn(23) and thenReturn(Promise.resolve(3)).
1066
991
  {
1067
992
  value: returnValue,
1068
993
  isError: false,
1069
994
  isPromise: false
1070
- }, pendingExpectation, repository),
1071
- thenThrow: errorOrMessage => finishPendingExpectation({
995
+ }, builder, repository),
996
+ thenThrow: errorOrMessage => finishExpectation({
1072
997
  value: getError(errorOrMessage),
1073
998
  isError: true,
1074
999
  isPromise: false
1075
- }, pendingExpectation, repository),
1076
- thenResolve: promiseValue => finishPendingExpectation({
1000
+ }, builder, repository),
1001
+ thenResolve: promiseValue => finishExpectation({
1077
1002
  value: promiseValue,
1078
1003
  isError: false,
1079
1004
  isPromise: true
1080
- }, pendingExpectation, repository),
1081
- thenReject: errorOrMessage => finishPendingExpectation({
1005
+ }, builder, repository),
1006
+ thenReject: errorOrMessage => finishExpectation({
1082
1007
  value: getError(errorOrMessage),
1083
1008
  isError: true,
1084
1009
  isPromise: true
1085
- }, pendingExpectation, repository)
1010
+ }, builder, repository)
1086
1011
  });
1087
1012
 
1088
- /**
1089
- * Set an expectation on a mock.
1090
- *
1091
- * The expectation must be finished by setting a return value, even if the value
1092
- * is `undefined`.
1093
- *
1094
- * If a call happens that was not expected then the mock will throw an error.
1095
- * By default, the call is expected to only be made once. Use the invocation
1096
- * count helpers to expect a call multiple times.
1097
- *
1098
- * @param expectation A callback to set the expectation on your mock. The
1099
- * callback must return the value from the mock to properly infer types.
1100
- *
1101
- * @example
1102
- * const fn = mock<() => void>();
1103
- * when(() => fn()).thenReturn(undefined);
1104
- *
1105
- * @example
1106
- * const fn = mock<() => number>();
1107
- * when(() => fn()).thenReturn(42).atMost(3);
1108
- *
1109
- * @example
1110
- * const fn = mock<(x: number) => Promise<number>();
1111
- * when(() => fn(23)).thenResolve(42);
1013
+ /**
1014
+ * Set an expectation on a mock.
1015
+ *
1016
+ * The expectation must be finished by setting a return value, even if the value
1017
+ * is `undefined`.
1018
+ *
1019
+ * If a call happens that was not expected then the mock will throw an error.
1020
+ * By default, the call is expected to only be made once. Use the invocation
1021
+ * count helpers to expect a call multiple times.
1022
+ *
1023
+ * @param expectation A callback to set the expectation on your mock. The
1024
+ * callback must return the value from the mock to properly infer types.
1025
+ *
1026
+ * @example
1027
+ * const fn = mock<() => void>();
1028
+ * when(() => fn()).thenReturn(undefined);
1029
+ *
1030
+ * @example
1031
+ * const fn = mock<() => number>();
1032
+ * when(() => fn()).thenReturn(42).atMost(3);
1033
+ *
1034
+ * @example
1035
+ * const fn = mock<(x: number) => Promise<number>();
1036
+ * when(() => fn(23)).thenResolve(42);
1112
1037
  */
1113
1038
 
1114
1039
  const when = expectation => {
@@ -1116,32 +1041,32 @@ const when = expectation => {
1116
1041
  expectation();
1117
1042
  setMode(Mode.CALL);
1118
1043
  const {
1119
- pendingExpectation,
1044
+ builder,
1120
1045
  repository
1121
1046
  } = getMockState(getActiveMock());
1122
- return createReturns(pendingExpectation, repository);
1047
+ return createReturns(builder, repository);
1123
1048
  };
1124
1049
 
1125
- /**
1126
- * Remove any remaining expectations on the given mock.
1127
- *
1128
- * @example
1129
- * const fn = mock<() => number>();
1130
- *
1131
- * when(() => fn()).thenReturn(23);
1132
- *
1133
- * reset(fn);
1134
- *
1135
- * fn(); // throws
1050
+ /**
1051
+ * Remove any remaining expectations on the given mock.
1052
+ *
1053
+ * @example
1054
+ * const fn = mock<() => number>();
1055
+ *
1056
+ * when(() => fn()).thenReturn(23);
1057
+ *
1058
+ * reset(fn);
1059
+ *
1060
+ * fn(); // throws
1136
1061
  */
1137
1062
 
1138
1063
  const reset = mock => {
1139
1064
  getMockState(mock).repository.clear();
1140
1065
  };
1141
- /**
1142
- * Reset all existing mocks.
1143
- *
1144
- * @see reset
1066
+ /**
1067
+ * Reset all existing mocks.
1068
+ *
1069
+ * @see reset
1145
1070
  */
1146
1071
 
1147
1072
  const resetAll = () => {
@@ -1150,6 +1075,46 @@ const resetAll = () => {
1150
1075
  });
1151
1076
  };
1152
1077
 
1078
+ class UnmetExpectations extends Error {
1079
+ constructor(expectations) {
1080
+ super(jestMatcherUtils.DIM_COLOR(`There are unmet expectations:
1081
+
1082
+ - ${expectations.map(e => e.toString()).join('\n - ')}`));
1083
+ }
1084
+
1085
+ }
1086
+ /**
1087
+ * Merge property accesses and method calls for the same property
1088
+ * into a single call.
1089
+ *
1090
+ * @example
1091
+ * mergeCalls({ getData: [{ arguments: undefined }, { arguments: [1, 2, 3] }] }
1092
+ * // returns { getData: [{ arguments: [1, 2, 3] } }
1093
+ */
1094
+
1095
+ const mergeCalls = callMap => new Map(Array.from(callMap.entries()).map(([property, calls]) => {
1096
+ const hasMethodCalls = calls.some(call => call.arguments);
1097
+ const hasPropertyAccesses = calls.some(call => !call.arguments);
1098
+
1099
+ if (hasMethodCalls && hasPropertyAccesses) {
1100
+ return [property, calls.filter(call => call.arguments)];
1101
+ }
1102
+
1103
+ return [property, calls];
1104
+ }));
1105
+
1106
+ class UnexpectedCalls extends Error {
1107
+ constructor(unexpectedCalls, expectations) {
1108
+ const printedCalls = Array.from(mergeCalls(unexpectedCalls).entries()).map(([property, calls]) => calls.map(call => printCall(property, call.arguments)).join('\n - ')).join('\n - ');
1109
+ super(jestMatcherUtils.DIM_COLOR(`The following calls were unexpected:
1110
+
1111
+ - ${printedCalls}
1112
+
1113
+ ${printRemainingExpectations(expectations)}`));
1114
+ }
1115
+
1116
+ }
1117
+
1153
1118
  const verifyRepo = repository => {
1154
1119
  const unmetExpectations = repository.getUnmet();
1155
1120
 
@@ -1163,22 +1128,22 @@ const verifyRepo = repository => {
1163
1128
  throw new UnexpectedCalls(callStats.unexpected, unmetExpectations);
1164
1129
  }
1165
1130
  };
1166
- /**
1167
- * Verify that all expectations on the given mock have been met.
1168
- *
1169
- * @throws Will throw if there are remaining expectations that were set
1170
- * using `when` and that weren't met.
1171
- *
1172
- * @throws Will throw if any unexpected calls happened. Normally those
1173
- * calls throw on their own, but the error might be caught by the code
1174
- * being tested.
1175
- *
1176
- * @example
1177
- * const fn = mock<() => number>();
1178
- *
1179
- * when(() => fn()).thenReturn(23);
1180
- *
1181
- * verify(fn); // throws
1131
+ /**
1132
+ * Verify that all expectations on the given mock have been met.
1133
+ *
1134
+ * @throws Will throw if there are remaining expectations that were set
1135
+ * using `when` and that weren't met.
1136
+ *
1137
+ * @throws Will throw if any unexpected calls happened. Normally those
1138
+ * calls throw on their own, but the error might be caught by the code
1139
+ * being tested.
1140
+ *
1141
+ * @example
1142
+ * const fn = mock<() => number>();
1143
+ *
1144
+ * when(() => fn()).thenReturn(23);
1145
+ *
1146
+ * verify(fn); // throws
1182
1147
  */
1183
1148
 
1184
1149
  const verify = mock => {
@@ -1187,10 +1152,10 @@ const verify = mock => {
1187
1152
  } = getMockState(mock);
1188
1153
  verifyRepo(repository);
1189
1154
  };
1190
- /**
1191
- * Verify all existing mocks.
1192
- *
1193
- * @see verify
1155
+ /**
1156
+ * Verify all existing mocks.
1157
+ *
1158
+ * @see verify
1194
1159
  */
1195
1160
 
1196
1161
  const verifyAll = () => {
@@ -1199,7 +1164,345 @@ const verifyAll = () => {
1199
1164
  });
1200
1165
  };
1201
1166
 
1202
- exports.It = It;
1167
+ /**
1168
+ * Compare values using `Object.is`.
1169
+ *
1170
+ * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
1171
+ *
1172
+ * @see It.deepEquals A matcher that uses deep equality.
1173
+ */
1174
+
1175
+ const is = expected => matches(actual => Object.is(actual, expected), {
1176
+ toString: () => `${printValue(expected)}`,
1177
+ getDiff: actual => ({
1178
+ actual,
1179
+ expected
1180
+ })
1181
+ });
1182
+
1183
+ /**
1184
+ * Match any value, including `undefined` and `null`.
1185
+ *
1186
+ * @example
1187
+ * const fn = mock<(x: number, y: string) => number>();
1188
+ * when(() => fn(It.isAny(), It.isAny())).thenReturn(1);
1189
+ *
1190
+ * fn(23, 'foobar') === 1
1191
+ */
1192
+
1193
+ const isAny = () => matches(() => true, {
1194
+ toString: () => 'Matcher<any>'
1195
+ });
1196
+
1197
+ /**
1198
+ * Match an array.
1199
+ *
1200
+ * Supports nested matchers.
1201
+ *
1202
+ * @param containing If given, the matched array has to contain ALL of these
1203
+ * elements in ANY order.
1204
+ *
1205
+ * @example
1206
+ * const fn = mock<(arr: number[]) => number>();
1207
+ * when(() => fn(It.isArray())).thenReturn(1);
1208
+ * when(() => fn(It.isArray([2, 3]))).thenReturn(2);
1209
+ *
1210
+ * fn({ length: 1, 0: 42 }) // throws
1211
+ * fn([]) === 1
1212
+ * fn([3, 2, 1]) === 2
1213
+ *
1214
+ * @example
1215
+ * It.isArray([It.isString({ containing: 'foobar' })])
1216
+ */
1217
+
1218
+ const isArray = containing => matches(actual => {
1219
+ if (!Array.isArray(actual)) {
1220
+ return false;
1221
+ }
1222
+
1223
+ if (!containing) {
1224
+ return true;
1225
+ }
1226
+
1227
+ return containing.every(x => actual.find(y => {
1228
+ if (isMatcher(x)) {
1229
+ return x.matches(y);
1230
+ }
1231
+
1232
+ return deepEquals(x).matches(y);
1233
+ }) !== undefined);
1234
+ }, {
1235
+ toString: () => containing ? `array(${printValue(containing)})` : 'array',
1236
+ getDiff: actual => {
1237
+ if (containing) {
1238
+ return {
1239
+ actual,
1240
+ expected: `Matcher<array>([${containing.map(value => {
1241
+ if (isMatcher(value)) {
1242
+ return value.toString();
1243
+ }
1244
+
1245
+ return value;
1246
+ }).join(', ')}])`
1247
+ };
1248
+ }
1249
+
1250
+ return {
1251
+ actual: `${printValue(actual)} (${typeof actual})`,
1252
+ expected: 'Matcher<array>'
1253
+ };
1254
+ }
1255
+ });
1256
+
1257
+ /**
1258
+ * Match any number.
1259
+ *
1260
+ * @example
1261
+ * const fn = mock<(x: number) => number>();
1262
+ * when(() => fn(It.isNumber())).returns(42);
1263
+ *
1264
+ * fn(20.5) === 42
1265
+ * fn(NaN) // throws
1266
+ */
1267
+
1268
+ const isNumber = () => matches(actual => typeof actual === 'number' && !Number.isNaN(actual), {
1269
+ toString: () => 'Matcher<number>',
1270
+ getDiff: actual => ({
1271
+ actual: `${printValue(actual)} (${typeof actual})`,
1272
+ expected: 'Matcher<number>'
1273
+ })
1274
+ });
1275
+
1276
+ const looksLikeObject = value => lodash.isPlainObject(value);
1277
+
1278
+ const getExpectedObjectDiff = (actual, expected) => Object.fromEntries(Reflect.ownKeys(expected).map(key => {
1279
+ const right = expected[key]; // @ts-expect-error because we're fine with a runtime undefined
1280
+
1281
+ const left = actual == null ? void 0 : actual[key];
1282
+
1283
+ if (isMatcher(right)) {
1284
+ return [key, right.getDiff(left).expected];
1285
+ }
1286
+
1287
+ if (looksLikeObject(right)) {
1288
+ return [key, getExpectedObjectDiff(left, right)];
1289
+ }
1290
+
1291
+ return [key, right];
1292
+ }));
1293
+
1294
+ const getActualObjectDiff = (actual, expected) => {
1295
+ if (!looksLikeObject(actual)) {
1296
+ return actual;
1297
+ }
1298
+
1299
+ return Object.fromEntries(Reflect.ownKeys(expected).map(key => {
1300
+ const right = expected[key];
1301
+ const left = actual[key];
1302
+
1303
+ if (!left) {
1304
+ return [];
1305
+ }
1306
+
1307
+ if (isMatcher(right)) {
1308
+ return [key, right.getDiff(left).actual];
1309
+ }
1310
+
1311
+ if (looksLikeObject(right)) {
1312
+ return [key, getActualObjectDiff(left, right)];
1313
+ }
1314
+
1315
+ return [key, left];
1316
+ }));
1317
+ };
1318
+
1319
+ const isMatch = (actual, expected) => Reflect.ownKeys(expected).every(key => {
1320
+ const right = expected[key];
1321
+ const left = looksLikeObject(actual) ? actual[key] : undefined;
1322
+
1323
+ if (!left) {
1324
+ return false;
1325
+ }
1326
+
1327
+ if (isMatcher(right)) {
1328
+ return right.matches(left);
1329
+ }
1330
+
1331
+ if (looksLikeObject(right)) {
1332
+ return isMatch(left, right);
1333
+ }
1334
+
1335
+ return deepEquals(right).matches(left);
1336
+ });
1337
+
1338
+ const deepPrintObject = value => lodash.cloneDeepWith(value, value => {
1339
+ if (isMatcher(value)) {
1340
+ return value.toString();
1341
+ }
1342
+
1343
+ return undefined;
1344
+ });
1345
+ /**
1346
+ * Match any plain object.
1347
+ *
1348
+ * Object like values, e.g. classes and arrays, will not be matched.
1349
+ *
1350
+ * @param partial An optional subset of the expected object that will be
1351
+ * recursively matched. Supports nested matchers. Values will be
1352
+ * compared with {@link deepEquals}.
1353
+ *
1354
+ * @example
1355
+ * const fn = mock<(pos: { x: number, y: number }) => number>();
1356
+ * when(() => fn(It.isObject({ x: 23 }))).returns(42);
1357
+ *
1358
+ * fn({ x: 100, y: 200 }) // throws
1359
+ * fn({ x: 23, y: 200 }) // returns 42
1360
+ *
1361
+ * @example
1362
+ * It.isObject({ foo: It.isString() })
1363
+ */
1364
+
1365
+
1366
+ const isObject = partial => matches(actual => {
1367
+ if (!looksLikeObject(actual)) {
1368
+ return false;
1369
+ }
1370
+
1371
+ if (!partial) {
1372
+ return true;
1373
+ }
1374
+
1375
+ return isMatch(actual, partial);
1376
+ }, {
1377
+ toString: () => {
1378
+ if (!partial) {
1379
+ return 'Matcher<object>';
1380
+ }
1381
+
1382
+ return `Matcher<object>(${printValue(deepPrintObject(partial))})`;
1383
+ },
1384
+ getDiff: actual => {
1385
+ if (!partial) {
1386
+ return {
1387
+ expected: 'Matcher<object>',
1388
+ actual: `${printValue(actual)}`
1389
+ };
1390
+ }
1391
+
1392
+ return {
1393
+ actual: getActualObjectDiff(actual, partial),
1394
+ expected: getExpectedObjectDiff(actual, partial)
1395
+ };
1396
+ }
1397
+ });
1398
+
1399
+ /**
1400
+ * Match any string.
1401
+ *
1402
+ * @param matching An optional string or RegExp to match the string against.
1403
+ * If it's a string, a case-sensitive search will be performed.
1404
+ *
1405
+ * @example
1406
+ * const fn = mock<(x: string, y: string) => number>();
1407
+ * when(() => fn(It.isString(), It.isString('bar'))).returns(42);
1408
+ *
1409
+ * fn('foo', 'baz') // throws
1410
+ * fn('foo', 'bar') === 42
1411
+ */
1412
+
1413
+ const isString = matching => matches(actual => {
1414
+ if (typeof actual !== 'string') {
1415
+ return false;
1416
+ }
1417
+
1418
+ if (!matching) {
1419
+ return true;
1420
+ }
1421
+
1422
+ if (typeof matching === 'string') {
1423
+ return actual.indexOf(matching) !== -1;
1424
+ }
1425
+
1426
+ return matching.test(actual);
1427
+ }, {
1428
+ toString: () => {
1429
+ if (matching) {
1430
+ return `Matcher<string>(${matching})`;
1431
+ }
1432
+
1433
+ return 'Matcher<string>';
1434
+ },
1435
+ getDiff: actual => {
1436
+ if (matching) {
1437
+ return {
1438
+ expected: `Matcher<string>(${matching})`,
1439
+ actual
1440
+ };
1441
+ }
1442
+
1443
+ return {
1444
+ expected: 'Matcher<string>',
1445
+ actual: `${actual} (${typeof actual})`
1446
+ };
1447
+ }
1448
+ });
1449
+
1450
+ /**
1451
+ * Matches anything and stores the received value.
1452
+ *
1453
+ * This should not be needed for most cases, but can be useful if you need
1454
+ * access to a complex argument outside the expectation e.g. to test a
1455
+ * callback.
1456
+ *
1457
+ * @param name If given, this name will be printed in error messages.
1458
+ *
1459
+ * @example
1460
+ * const fn = mock<(cb: (value: number) => number) => void>();
1461
+ * const matcher = It.willCapture();
1462
+ * when(() => fn(matcher)).thenReturn();
1463
+ *
1464
+ * fn(x => x + 1);
1465
+ * matcher.value?.(3) === 4
1466
+ */
1467
+
1468
+ const willCapture = name => {
1469
+ let capturedValue;
1470
+ const matcher = {
1471
+ [MATCHER_SYMBOL]: true,
1472
+ matches: actual => {
1473
+ capturedValue = actual;
1474
+ return true;
1475
+ },
1476
+ toString: () => name ? `Capture(${name})` : 'Capture',
1477
+ getDiff: actual => ({
1478
+ actual,
1479
+ expected: actual
1480
+ }),
1481
+
1482
+ get value() {
1483
+ return capturedValue;
1484
+ }
1485
+
1486
+ };
1487
+ return matcher;
1488
+ };
1489
+
1490
+ /* istanbul ignore file */
1491
+
1492
+ var it = {
1493
+ __proto__: null,
1494
+ deepEquals: deepEquals,
1495
+ is: is,
1496
+ isAny: isAny,
1497
+ isArray: isArray,
1498
+ isNumber: isNumber,
1499
+ isObject: isObject,
1500
+ isString: isString,
1501
+ matches: matches,
1502
+ willCapture: willCapture
1503
+ };
1504
+
1505
+ exports.It = it;
1203
1506
  exports.mock = mock;
1204
1507
  exports.reset = reset;
1205
1508
  exports.resetAll = resetAll;