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.
- package/README.md +53 -31
- package/dist/errors/api.d.ts +13 -0
- package/dist/errors/unexpected-access.d.ts +5 -0
- package/dist/errors/unexpected-call.d.ts +17 -0
- package/dist/errors/verify.d.ts +8 -0
- package/dist/expectation/expectation.d.ts +27 -30
- package/dist/expectation/repository/expectation-repository.d.ts +90 -90
- package/dist/expectation/repository/flexible-repository.d.ts +38 -38
- package/dist/expectation/repository/return-value.d.ts +13 -13
- package/dist/expectation/strong-expectation.d.ts +30 -30
- package/dist/index.d.ts +14 -9
- package/dist/index.js +815 -512
- package/dist/index.js.map +1 -1
- package/dist/matchers/deep-equals.d.ts +16 -0
- package/dist/matchers/is-any.d.ts +11 -0
- package/dist/matchers/is-array.d.ts +22 -0
- package/dist/matchers/is-number.d.ts +12 -0
- package/dist/matchers/is-object.d.ts +27 -0
- package/dist/matchers/is-string.d.ts +15 -0
- package/dist/matchers/is.d.ts +9 -0
- package/dist/matchers/it.d.ts +9 -0
- package/dist/matchers/matcher.d.ts +92 -0
- package/dist/matchers/will-capture.d.ts +21 -0
- package/dist/mock/defaults.d.ts +11 -11
- package/dist/mock/map.d.ts +16 -16
- package/dist/mock/mock.d.ts +29 -29
- package/dist/mock/options.d.ts +99 -99
- package/dist/mock/stub.d.ts +5 -5
- package/dist/print.d.ts +10 -10
- package/dist/proxy.d.ts +48 -48
- package/dist/return/invocation-count.d.ts +44 -44
- package/dist/return/returns.d.ts +61 -77
- package/dist/verify/reset.d.ts +20 -20
- package/dist/verify/verify.d.ts +27 -27
- package/dist/when/{pending-expectation.d.ts → expectation-builder.d.ts} +26 -31
- package/dist/when/when.d.ts +32 -32
- package/package.json +20 -16
- package/dist/errors.d.ts +0 -28
- package/dist/expectation/it.d.ts +0 -29
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
30
|
-
|
|
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
|
-
|
|
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.
|
|
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(
|
|
140
|
+
return `when(() => mock${jestMatcherUtils.EXPECTED_COLOR(`${prettyProperty}(${printArgs(args)})`)})`;
|
|
60
141
|
}
|
|
61
142
|
|
|
62
|
-
return `when(() => ${jestMatcherUtils.EXPECTED_COLOR(
|
|
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.
|
|
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 ${
|
|
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
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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 (
|
|
136
|
-
return
|
|
214
|
+
if (!((_e$args = e.args) != null && _e$args.length)) {
|
|
215
|
+
return '';
|
|
137
216
|
}
|
|
138
217
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
227
|
+
if (diff) {
|
|
228
|
+
return `${e.toString()}
|
|
229
|
+
${jestMatcherUtils.EXPECTED_COLOR('- Expected')}
|
|
230
|
+
${jestMatcherUtils.RECEIVED_COLOR('+ Received')}
|
|
148
231
|
|
|
149
|
-
${
|
|
232
|
+
${diff}`;
|
|
150
233
|
}
|
|
151
234
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
const
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
`;
|
|
162
|
-
super(`Setting an expectation on a nested property is not supported.
|
|
242
|
+
if (propertyExpectations.length) {
|
|
243
|
+
var _propertyExpectations;
|
|
163
244
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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:
|
|
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 => //
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
933
|
-
|
|
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
|
|
998
|
-
const stub = createStub(repository,
|
|
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
|
-
|
|
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
|
|
1047
|
-
const finishedExpectation =
|
|
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 = (
|
|
1065
|
-
thenReturn: returnValue =>
|
|
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
|
-
},
|
|
1071
|
-
thenThrow: errorOrMessage =>
|
|
995
|
+
}, builder, repository),
|
|
996
|
+
thenThrow: errorOrMessage => finishExpectation({
|
|
1072
997
|
value: getError(errorOrMessage),
|
|
1073
998
|
isError: true,
|
|
1074
999
|
isPromise: false
|
|
1075
|
-
},
|
|
1076
|
-
thenResolve: promiseValue =>
|
|
1000
|
+
}, builder, repository),
|
|
1001
|
+
thenResolve: promiseValue => finishExpectation({
|
|
1077
1002
|
value: promiseValue,
|
|
1078
1003
|
isError: false,
|
|
1079
1004
|
isPromise: true
|
|
1080
|
-
},
|
|
1081
|
-
thenReject: errorOrMessage =>
|
|
1005
|
+
}, builder, repository),
|
|
1006
|
+
thenReject: errorOrMessage => finishExpectation({
|
|
1082
1007
|
value: getError(errorOrMessage),
|
|
1083
1008
|
isError: true,
|
|
1084
1009
|
isPromise: true
|
|
1085
|
-
},
|
|
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
|
-
|
|
1044
|
+
builder,
|
|
1120
1045
|
repository
|
|
1121
1046
|
} = getMockState(getActiveMock());
|
|
1122
|
-
return createReturns(
|
|
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
|
-
|
|
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;
|