strong-mock 8.0.1 → 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 +52 -30
- 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 +810 -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 -87
- 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 {
|
|
@@ -417,16 +519,16 @@ class FlexibleRepository {
|
|
|
417
519
|
|
|
418
520
|
}
|
|
419
521
|
|
|
420
|
-
/**
|
|
421
|
-
* Matches a call with more parameters than expected because it is assumed the
|
|
422
|
-
* compiler will check that those parameters are optional.
|
|
423
|
-
*
|
|
424
|
-
* @example
|
|
425
|
-
* new StrongExpectation(
|
|
426
|
-
* 'bar',
|
|
427
|
-
* deepEquals([1, 2, 3]),
|
|
428
|
-
* 23
|
|
429
|
-
* ).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;
|
|
430
532
|
*/
|
|
431
533
|
|
|
432
534
|
class StrongExpectation {
|
|
@@ -480,13 +582,58 @@ class StrongExpectation {
|
|
|
480
582
|
return this.args.every((arg, i) => arg.matches(received[i]));
|
|
481
583
|
}
|
|
482
584
|
|
|
483
|
-
|
|
585
|
+
toString() {
|
|
484
586
|
return printExpectation(this.property, this.args, this.returnValue, this.min, this.max);
|
|
485
587
|
}
|
|
486
588
|
|
|
487
589
|
}
|
|
488
590
|
|
|
489
|
-
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 {
|
|
490
637
|
constructor(createExpectation, concreteMatcher, exactParams) {
|
|
491
638
|
this.createExpectation = void 0;
|
|
492
639
|
this.concreteMatcher = void 0;
|
|
@@ -500,7 +647,7 @@ class PendingExpectationWithFactory {
|
|
|
500
647
|
|
|
501
648
|
setProperty(value) {
|
|
502
649
|
if (this.property) {
|
|
503
|
-
throw new UnfinishedExpectation(this);
|
|
650
|
+
throw new UnfinishedExpectation(this.property, this.args);
|
|
504
651
|
}
|
|
505
652
|
|
|
506
653
|
this.property = value;
|
|
@@ -521,10 +668,6 @@ class PendingExpectationWithFactory {
|
|
|
521
668
|
return expectation;
|
|
522
669
|
}
|
|
523
670
|
|
|
524
|
-
toJSON() {
|
|
525
|
-
return printWhen(this.property, this.args);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
671
|
}
|
|
529
672
|
|
|
530
673
|
function _extends() {
|
|
@@ -545,33 +688,6 @@ function _extends() {
|
|
|
545
688
|
return _extends.apply(this, arguments);
|
|
546
689
|
}
|
|
547
690
|
|
|
548
|
-
/**
|
|
549
|
-
* Match a custom predicate.
|
|
550
|
-
*
|
|
551
|
-
* @param cb Will receive the value and returns whether it matches.
|
|
552
|
-
* @param toJSON An optional function that should return a string that will be
|
|
553
|
-
* used when the matcher needs to be printed in an error message. By default,
|
|
554
|
-
* it stringifies `cb`.
|
|
555
|
-
*
|
|
556
|
-
* @example
|
|
557
|
-
* const fn = mock<(x: number) => number>();
|
|
558
|
-
* when(() => fn(It.matches(x => x >= 0))).returns(42);
|
|
559
|
-
*
|
|
560
|
-
* fn(2) === 42
|
|
561
|
-
* fn(-1) // throws
|
|
562
|
-
*/
|
|
563
|
-
|
|
564
|
-
const matches = (cb, {
|
|
565
|
-
toJSON = () => `matches(${cb.toString()})`
|
|
566
|
-
} = {}) => {
|
|
567
|
-
const matcher = {
|
|
568
|
-
[MATCHER_SYMBOL]: true,
|
|
569
|
-
matches: arg => cb(arg),
|
|
570
|
-
toJSON
|
|
571
|
-
};
|
|
572
|
-
return matcher;
|
|
573
|
-
};
|
|
574
|
-
|
|
575
691
|
const removeUndefined = object => {
|
|
576
692
|
if (Array.isArray(object)) {
|
|
577
693
|
return object.map(x => removeUndefined(x));
|
|
@@ -583,16 +699,17 @@ const removeUndefined = object => {
|
|
|
583
699
|
|
|
584
700
|
return lodash.omitBy(object, lodash.isUndefined);
|
|
585
701
|
};
|
|
586
|
-
/**
|
|
587
|
-
* Compare values using deep equality.
|
|
588
|
-
*
|
|
589
|
-
* @param expected
|
|
590
|
-
* @param strict By default, this matcher will treat a missing key in an object
|
|
591
|
-
* and a key with the value `undefined` as not equal. It will also consider
|
|
592
|
-
* non `Object` instances with different constructors as not equal. Setting
|
|
593
|
-
* this to `false` will consider the objects in both cases as equal.
|
|
594
|
-
*
|
|
595
|
-
* @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.
|
|
596
713
|
*/
|
|
597
714
|
|
|
598
715
|
|
|
@@ -605,244 +722,45 @@ const deepEquals = (expected, {
|
|
|
605
722
|
|
|
606
723
|
return lodash.isEqual(removeUndefined(actual), removeUndefined(expected));
|
|
607
724
|
}, {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
* @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
|
|
614
|
-
*
|
|
615
|
-
* @see It.deepEquals A matcher that uses deep equality.
|
|
616
|
-
*/
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const is = expected => matches(actual => Object.is(actual, expected), {
|
|
620
|
-
toJSON: () => `${jestMatcherUtils.printExpected(expected)}`
|
|
621
|
-
});
|
|
622
|
-
/**
|
|
623
|
-
* Match any value, including `undefined` and `null`.
|
|
624
|
-
*
|
|
625
|
-
* @example
|
|
626
|
-
* const fn = mock<(x: number, y: string) => number>();
|
|
627
|
-
* when(() => fn(It.isAny(), It.isAny())).thenReturn(1);
|
|
628
|
-
*
|
|
629
|
-
* fn(23, 'foobar') === 1
|
|
630
|
-
*/
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const isAny = () => matches(() => true, {
|
|
634
|
-
toJSON: () => 'anything'
|
|
635
|
-
});
|
|
636
|
-
/**
|
|
637
|
-
* Recursively match an object.
|
|
638
|
-
*
|
|
639
|
-
* Supports nested matcher.
|
|
640
|
-
*
|
|
641
|
-
* @param partial An optional subset of the expected objected.
|
|
642
|
-
*
|
|
643
|
-
* @example
|
|
644
|
-
* const fn = mock<(foo: { x: number, y: number }) => number>();
|
|
645
|
-
* when(() => fn(It.isObject({ x: 23 }))).returns(42);
|
|
646
|
-
*
|
|
647
|
-
* fn({ x: 100, y: 200 }) // throws
|
|
648
|
-
* fn({ x: 23, y: 200 }) // returns 42
|
|
649
|
-
*
|
|
650
|
-
* @example
|
|
651
|
-
* It.isObject({ foo: It.isString() })
|
|
652
|
-
*/
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
const isObject = partial => matches(actual => lodash.isMatchWith(actual, partial || {}, (argValue, partialValue) => {
|
|
656
|
-
if (isMatcher(partialValue)) {
|
|
657
|
-
return partialValue.matches(argValue);
|
|
658
|
-
} // Let lodash handle it otherwise.
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
return undefined;
|
|
662
|
-
}), {
|
|
663
|
-
toJSON: () => partial ? `object(${jestMatcherUtils.printExpected(partial)})` : 'object'
|
|
664
|
-
});
|
|
665
|
-
/**
|
|
666
|
-
* Match any number.
|
|
667
|
-
*
|
|
668
|
-
* @example
|
|
669
|
-
* const fn = mock<(x: number) => number>();
|
|
670
|
-
* when(() => fn(It.isNumber())).returns(42);
|
|
671
|
-
*
|
|
672
|
-
* fn(20.5) === 42
|
|
673
|
-
* fn(NaN) // throws
|
|
674
|
-
*/
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const isNumber = () => matches(actual => typeof actual === 'number' && !Number.isNaN(actual), {
|
|
678
|
-
toJSON: () => 'number'
|
|
679
|
-
});
|
|
680
|
-
/**
|
|
681
|
-
* Match a string, potentially by a pattern.
|
|
682
|
-
*
|
|
683
|
-
* @param matching The string has to match this RegExp.
|
|
684
|
-
* @param containing The string has to contain this substring.
|
|
685
|
-
*
|
|
686
|
-
* @example
|
|
687
|
-
* const fn = mock<(x: string, y: string) => number>();
|
|
688
|
-
* when(() => fn(It.isString(), It.isString({ containing: 'bar' }))).returns(42);
|
|
689
|
-
*
|
|
690
|
-
* fn('foo', 'baz') // throws
|
|
691
|
-
* fn('foo', 'bar') === 42
|
|
692
|
-
*/
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
const isString = ({
|
|
696
|
-
matching,
|
|
697
|
-
containing
|
|
698
|
-
} = {}) => {
|
|
699
|
-
if (matching && containing) {
|
|
700
|
-
throw new Error('You can only pass `matching` or `containing`, not both.');
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
return matches(actual => {
|
|
704
|
-
var _matching$test;
|
|
705
|
-
|
|
706
|
-
if (typeof actual !== 'string') {
|
|
707
|
-
return false;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
if (containing) {
|
|
711
|
-
return actual.indexOf(containing) !== -1;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
return (_matching$test = matching == null ? void 0 : matching.test(actual)) != null ? _matching$test : true;
|
|
715
|
-
}, {
|
|
716
|
-
toJSON: () => containing || matching ? `string(${jestMatcherUtils.printExpected(containing || matching)})` : 'string'
|
|
717
|
-
});
|
|
718
|
-
};
|
|
719
|
-
/**
|
|
720
|
-
* Match an array.
|
|
721
|
-
*
|
|
722
|
-
* Supports nested matchers.
|
|
723
|
-
*
|
|
724
|
-
* @param containing If given, the matched array has to contain ALL of these
|
|
725
|
-
* elements in ANY order.
|
|
726
|
-
*
|
|
727
|
-
* @example
|
|
728
|
-
* const fn = mock<(arr: number[]) => number>();
|
|
729
|
-
* when(() => fn(It.isArray())).thenReturn(1);
|
|
730
|
-
* when(() => fn(It.isArray([2, 3]))).thenReturn(2);
|
|
731
|
-
*
|
|
732
|
-
* fn({ length: 1, 0: 42 }) // throws
|
|
733
|
-
* fn([]) === 1
|
|
734
|
-
* fn([3, 2, 1]) === 2
|
|
735
|
-
*
|
|
736
|
-
* @example
|
|
737
|
-
* It.isArray([It.isString({ containing: 'foobar' })])
|
|
738
|
-
*/
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const isArray = containing => matches(actual => {
|
|
742
|
-
if (!Array.isArray(actual)) {
|
|
743
|
-
return false;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
if (!containing) {
|
|
747
|
-
return true;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
return containing.every(x => actual.find(y => {
|
|
751
|
-
if (isMatcher(x)) {
|
|
752
|
-
return x.matches(y);
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
return deepEquals(x).matches(y);
|
|
756
|
-
}) !== undefined);
|
|
757
|
-
}, {
|
|
758
|
-
toJSON: () => containing ? `array(${jestMatcherUtils.printExpected(containing)})` : 'array'
|
|
725
|
+
toString: () => printValue(expected),
|
|
726
|
+
getDiff: actual => ({
|
|
727
|
+
actual,
|
|
728
|
+
expected
|
|
729
|
+
})
|
|
759
730
|
});
|
|
760
|
-
/**
|
|
761
|
-
* Matches anything and stores the received value.
|
|
762
|
-
*
|
|
763
|
-
* This should not be needed for most cases, but can be useful if you need
|
|
764
|
-
* access to a complex argument outside the expectation e.g. to test a
|
|
765
|
-
* callback.
|
|
766
|
-
*
|
|
767
|
-
* @param name If given, this name will be printed in error messages.
|
|
768
|
-
*
|
|
769
|
-
* @example
|
|
770
|
-
* const fn = mock<(cb: (value: number) => number) => void>();
|
|
771
|
-
* const matcher = It.willCapture();
|
|
772
|
-
* when(() => fn(matcher)).thenReturn();
|
|
773
|
-
*
|
|
774
|
-
* fn(x => x + 1);
|
|
775
|
-
* matcher.value?.(3) === 4
|
|
776
|
-
*/
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
const willCapture = name => {
|
|
780
|
-
let capturedValue;
|
|
781
|
-
const matcher = {
|
|
782
|
-
[MATCHER_SYMBOL]: true,
|
|
783
|
-
matches: actual => {
|
|
784
|
-
capturedValue = actual;
|
|
785
|
-
return true;
|
|
786
|
-
},
|
|
787
|
-
toJSON: () => name != null ? name : 'captures',
|
|
788
|
-
|
|
789
|
-
get value() {
|
|
790
|
-
return capturedValue;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
};
|
|
794
|
-
return matcher;
|
|
795
|
-
};
|
|
796
|
-
/**
|
|
797
|
-
* Contains argument matchers that can be used to ignore arguments in an
|
|
798
|
-
* expectation or to match complex arguments.
|
|
799
|
-
*/
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
const It = {
|
|
803
|
-
matches,
|
|
804
|
-
deepEquals,
|
|
805
|
-
is,
|
|
806
|
-
isAny,
|
|
807
|
-
isObject,
|
|
808
|
-
isNumber,
|
|
809
|
-
isString,
|
|
810
|
-
isArray,
|
|
811
|
-
willCapture
|
|
812
|
-
};
|
|
813
731
|
|
|
814
732
|
const defaults = {
|
|
815
|
-
concreteMatcher:
|
|
733
|
+
concreteMatcher: deepEquals,
|
|
816
734
|
unexpectedProperty: exports.UnexpectedProperty.CALL_THROW,
|
|
817
735
|
exactParams: false
|
|
818
736
|
};
|
|
819
737
|
let currentDefaults = defaults;
|
|
820
|
-
/**
|
|
821
|
-
* Override strong-mock's defaults.
|
|
822
|
-
*
|
|
823
|
-
* @param newDefaults These will be applied to the library defaults. Multiple
|
|
824
|
-
* calls don't stack e.g. calling this with `{}` will clear any previously
|
|
825
|
-
* 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.
|
|
826
744
|
*/
|
|
827
745
|
|
|
828
746
|
const setDefaults = newDefaults => {
|
|
829
747
|
currentDefaults = _extends({}, defaults, newDefaults);
|
|
830
748
|
};
|
|
831
749
|
|
|
832
|
-
/**
|
|
833
|
-
* Since `when` doesn't receive the mock subject (because we can't make it
|
|
834
|
-
* consistently return it from `mock()`, `mock.foo` and `mock.bar()`) we need
|
|
835
|
-
* to store a global state for the currently active mock.
|
|
836
|
-
*
|
|
837
|
-
* We also want to throw in the following case:
|
|
838
|
-
*
|
|
839
|
-
* ```
|
|
840
|
-
* when(() => mock()) // forgot returns here
|
|
841
|
-
* when(() => mock()) // should throw
|
|
842
|
-
* ```
|
|
843
|
-
*
|
|
844
|
-
* For that reason we can't just store the currently active mock, but also
|
|
845
|
-
* 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.
|
|
846
764
|
*/
|
|
847
765
|
|
|
848
766
|
let activeMock;
|
|
@@ -850,11 +768,11 @@ const setActiveMock = mock => {
|
|
|
850
768
|
activeMock = mock;
|
|
851
769
|
};
|
|
852
770
|
const getActiveMock = () => activeMock;
|
|
853
|
-
/**
|
|
854
|
-
* Store a global map of all mocks created and their state.
|
|
855
|
-
*
|
|
856
|
-
* This is needed because we can't reliably pass the state between `when`
|
|
857
|
-
* 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`.
|
|
858
776
|
*/
|
|
859
777
|
|
|
860
778
|
const mockMap = new Map();
|
|
@@ -870,7 +788,9 @@ const setMockState = (mock, state) => {
|
|
|
870
788
|
};
|
|
871
789
|
const getAllMocks = () => Array.from(mockMap.entries());
|
|
872
790
|
|
|
873
|
-
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
|
|
874
794
|
new Proxy(
|
|
875
795
|
/* istanbul ignore next */
|
|
876
796
|
() => {}, {
|
|
@@ -907,7 +827,7 @@ new Proxy(
|
|
|
907
827
|
|
|
908
828
|
});
|
|
909
829
|
|
|
910
|
-
const createStub = (repo,
|
|
830
|
+
const createStub = (repo, builder, getCurrentMode) => {
|
|
911
831
|
const stub = createProxy({
|
|
912
832
|
property: property => {
|
|
913
833
|
if (getCurrentMode() === Mode.CALL) {
|
|
@@ -915,13 +835,13 @@ const createStub = (repo, pendingExpectation, getCurrentMode) => {
|
|
|
915
835
|
}
|
|
916
836
|
|
|
917
837
|
setActiveMock(stub);
|
|
918
|
-
|
|
838
|
+
builder.setProperty(property);
|
|
919
839
|
return createProxy({
|
|
920
840
|
property: childProp => {
|
|
921
841
|
throw new NestedWhen(property, childProp);
|
|
922
842
|
},
|
|
923
843
|
apply: args => {
|
|
924
|
-
|
|
844
|
+
builder.setArgs(args);
|
|
925
845
|
},
|
|
926
846
|
ownKeys: () => {
|
|
927
847
|
throw new Error('Spreading during an expectation is not supported.');
|
|
@@ -934,8 +854,8 @@ const createStub = (repo, pendingExpectation, getCurrentMode) => {
|
|
|
934
854
|
}
|
|
935
855
|
|
|
936
856
|
setActiveMock(stub);
|
|
937
|
-
|
|
938
|
-
|
|
857
|
+
builder.setProperty(ApplyProp);
|
|
858
|
+
builder.setArgs(args);
|
|
939
859
|
return undefined;
|
|
940
860
|
},
|
|
941
861
|
ownKeys: () => {
|
|
@@ -965,26 +885,26 @@ const setMode = mode => {
|
|
|
965
885
|
};
|
|
966
886
|
|
|
967
887
|
const getMode = () => currentMode;
|
|
968
|
-
/**
|
|
969
|
-
* Create a type safe mock.
|
|
970
|
-
*
|
|
971
|
-
* @see {@link when} Set expectations on the mock using `when`.
|
|
972
|
-
*
|
|
973
|
-
* @param options Configure the options for this specific mock, overriding any
|
|
974
|
-
* defaults that were set with {@link setDefaults}.
|
|
975
|
-
* @param options.unexpectedProperty Controls what happens when an unexpected
|
|
976
|
-
* property is accessed.
|
|
977
|
-
* @param options.concreteMatcher The matcher that will be used when one isn't
|
|
978
|
-
* specified explicitly.
|
|
979
|
-
* @param options.exactParams Controls whether the number of received arguments
|
|
980
|
-
* has to match the expectation.
|
|
981
|
-
*
|
|
982
|
-
* @example
|
|
983
|
-
* const fn = mock<() => number>();
|
|
984
|
-
*
|
|
985
|
-
* when(() => fn()).thenReturn(23);
|
|
986
|
-
*
|
|
987
|
-
* 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;
|
|
988
908
|
*/
|
|
989
909
|
|
|
990
910
|
|
|
@@ -999,11 +919,11 @@ const mock = ({
|
|
|
999
919
|
exactParams: exactParams != null ? exactParams : currentDefaults.exactParams
|
|
1000
920
|
};
|
|
1001
921
|
const repository = new FlexibleRepository(options.unexpectedProperty);
|
|
1002
|
-
const
|
|
1003
|
-
const stub = createStub(repository,
|
|
922
|
+
const builder = new ExpectationBuilderWithFactory(strongExpectationFactory, options.concreteMatcher, options.exactParams);
|
|
923
|
+
const stub = createStub(repository, builder, getMode);
|
|
1004
924
|
setMockState(stub, {
|
|
1005
925
|
repository,
|
|
1006
|
-
|
|
926
|
+
builder,
|
|
1007
927
|
options
|
|
1008
928
|
});
|
|
1009
929
|
return stub;
|
|
@@ -1048,8 +968,8 @@ const createInvocationCount = expectation => ({
|
|
|
1048
968
|
|
|
1049
969
|
});
|
|
1050
970
|
|
|
1051
|
-
const
|
|
1052
|
-
const finishedExpectation =
|
|
971
|
+
const finishExpectation = (returnValue, builder, repo) => {
|
|
972
|
+
const finishedExpectation = builder.finish(returnValue);
|
|
1053
973
|
repo.add(finishedExpectation);
|
|
1054
974
|
return createInvocationCount(finishedExpectation);
|
|
1055
975
|
};
|
|
@@ -1066,54 +986,54 @@ const getError = errorOrMessage => {
|
|
|
1066
986
|
return new Error();
|
|
1067
987
|
};
|
|
1068
988
|
|
|
1069
|
-
const createReturns = (
|
|
1070
|
-
thenReturn: returnValue =>
|
|
989
|
+
const createReturns = (builder, repository) => ({
|
|
990
|
+
thenReturn: returnValue => finishExpectation( // This will handle both thenReturn(23) and thenReturn(Promise.resolve(3)).
|
|
1071
991
|
{
|
|
1072
992
|
value: returnValue,
|
|
1073
993
|
isError: false,
|
|
1074
994
|
isPromise: false
|
|
1075
|
-
},
|
|
1076
|
-
thenThrow: errorOrMessage =>
|
|
995
|
+
}, builder, repository),
|
|
996
|
+
thenThrow: errorOrMessage => finishExpectation({
|
|
1077
997
|
value: getError(errorOrMessage),
|
|
1078
998
|
isError: true,
|
|
1079
999
|
isPromise: false
|
|
1080
|
-
},
|
|
1081
|
-
thenResolve: promiseValue =>
|
|
1000
|
+
}, builder, repository),
|
|
1001
|
+
thenResolve: promiseValue => finishExpectation({
|
|
1082
1002
|
value: promiseValue,
|
|
1083
1003
|
isError: false,
|
|
1084
1004
|
isPromise: true
|
|
1085
|
-
},
|
|
1086
|
-
thenReject: errorOrMessage =>
|
|
1005
|
+
}, builder, repository),
|
|
1006
|
+
thenReject: errorOrMessage => finishExpectation({
|
|
1087
1007
|
value: getError(errorOrMessage),
|
|
1088
1008
|
isError: true,
|
|
1089
1009
|
isPromise: true
|
|
1090
|
-
},
|
|
1010
|
+
}, builder, repository)
|
|
1091
1011
|
});
|
|
1092
1012
|
|
|
1093
|
-
/**
|
|
1094
|
-
* Set an expectation on a mock.
|
|
1095
|
-
*
|
|
1096
|
-
* The expectation must be finished by setting a return value, even if the value
|
|
1097
|
-
* is `undefined`.
|
|
1098
|
-
*
|
|
1099
|
-
* If a call happens that was not expected then the mock will throw an error.
|
|
1100
|
-
* By default, the call is expected to only be made once. Use the invocation
|
|
1101
|
-
* count helpers to expect a call multiple times.
|
|
1102
|
-
*
|
|
1103
|
-
* @param expectation A callback to set the expectation on your mock. The
|
|
1104
|
-
* callback must return the value from the mock to properly infer types.
|
|
1105
|
-
*
|
|
1106
|
-
* @example
|
|
1107
|
-
* const fn = mock<() => void>();
|
|
1108
|
-
* when(() => fn()).thenReturn(undefined);
|
|
1109
|
-
*
|
|
1110
|
-
* @example
|
|
1111
|
-
* const fn = mock<() => number>();
|
|
1112
|
-
* when(() => fn()).thenReturn(42).atMost(3);
|
|
1113
|
-
*
|
|
1114
|
-
* @example
|
|
1115
|
-
* const fn = mock<(x: number) => Promise<number>();
|
|
1116
|
-
* 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);
|
|
1117
1037
|
*/
|
|
1118
1038
|
|
|
1119
1039
|
const when = expectation => {
|
|
@@ -1121,32 +1041,32 @@ const when = expectation => {
|
|
|
1121
1041
|
expectation();
|
|
1122
1042
|
setMode(Mode.CALL);
|
|
1123
1043
|
const {
|
|
1124
|
-
|
|
1044
|
+
builder,
|
|
1125
1045
|
repository
|
|
1126
1046
|
} = getMockState(getActiveMock());
|
|
1127
|
-
return createReturns(
|
|
1047
|
+
return createReturns(builder, repository);
|
|
1128
1048
|
};
|
|
1129
1049
|
|
|
1130
|
-
/**
|
|
1131
|
-
* Remove any remaining expectations on the given mock.
|
|
1132
|
-
*
|
|
1133
|
-
* @example
|
|
1134
|
-
* const fn = mock<() => number>();
|
|
1135
|
-
*
|
|
1136
|
-
* when(() => fn()).thenReturn(23);
|
|
1137
|
-
*
|
|
1138
|
-
* reset(fn);
|
|
1139
|
-
*
|
|
1140
|
-
* 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
|
|
1141
1061
|
*/
|
|
1142
1062
|
|
|
1143
1063
|
const reset = mock => {
|
|
1144
1064
|
getMockState(mock).repository.clear();
|
|
1145
1065
|
};
|
|
1146
|
-
/**
|
|
1147
|
-
* Reset all existing mocks.
|
|
1148
|
-
*
|
|
1149
|
-
* @see reset
|
|
1066
|
+
/**
|
|
1067
|
+
* Reset all existing mocks.
|
|
1068
|
+
*
|
|
1069
|
+
* @see reset
|
|
1150
1070
|
*/
|
|
1151
1071
|
|
|
1152
1072
|
const resetAll = () => {
|
|
@@ -1155,6 +1075,46 @@ const resetAll = () => {
|
|
|
1155
1075
|
});
|
|
1156
1076
|
};
|
|
1157
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
|
+
|
|
1158
1118
|
const verifyRepo = repository => {
|
|
1159
1119
|
const unmetExpectations = repository.getUnmet();
|
|
1160
1120
|
|
|
@@ -1168,22 +1128,22 @@ const verifyRepo = repository => {
|
|
|
1168
1128
|
throw new UnexpectedCalls(callStats.unexpected, unmetExpectations);
|
|
1169
1129
|
}
|
|
1170
1130
|
};
|
|
1171
|
-
/**
|
|
1172
|
-
* Verify that all expectations on the given mock have been met.
|
|
1173
|
-
*
|
|
1174
|
-
* @throws Will throw if there are remaining expectations that were set
|
|
1175
|
-
* using `when` and that weren't met.
|
|
1176
|
-
*
|
|
1177
|
-
* @throws Will throw if any unexpected calls happened. Normally those
|
|
1178
|
-
* calls throw on their own, but the error might be caught by the code
|
|
1179
|
-
* being tested.
|
|
1180
|
-
*
|
|
1181
|
-
* @example
|
|
1182
|
-
* const fn = mock<() => number>();
|
|
1183
|
-
*
|
|
1184
|
-
* when(() => fn()).thenReturn(23);
|
|
1185
|
-
*
|
|
1186
|
-
* 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
|
|
1187
1147
|
*/
|
|
1188
1148
|
|
|
1189
1149
|
const verify = mock => {
|
|
@@ -1192,10 +1152,10 @@ const verify = mock => {
|
|
|
1192
1152
|
} = getMockState(mock);
|
|
1193
1153
|
verifyRepo(repository);
|
|
1194
1154
|
};
|
|
1195
|
-
/**
|
|
1196
|
-
* Verify all existing mocks.
|
|
1197
|
-
*
|
|
1198
|
-
* @see verify
|
|
1155
|
+
/**
|
|
1156
|
+
* Verify all existing mocks.
|
|
1157
|
+
*
|
|
1158
|
+
* @see verify
|
|
1199
1159
|
*/
|
|
1200
1160
|
|
|
1201
1161
|
const verifyAll = () => {
|
|
@@ -1204,7 +1164,345 @@ const verifyAll = () => {
|
|
|
1204
1164
|
});
|
|
1205
1165
|
};
|
|
1206
1166
|
|
|
1207
|
-
|
|
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;
|
|
1208
1506
|
exports.mock = mock;
|
|
1209
1507
|
exports.reset = reset;
|
|
1210
1508
|
exports.resetAll = resetAll;
|