expect-matcher-node-mock 1.1.2 → 1.1.3

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 CHANGED
@@ -22,15 +22,15 @@ This package bridges that gap by:
22
22
  ## Installation
23
23
 
24
24
  ```bash
25
- npm install expect-matcher-node-mock
25
+ npm install --save-dev expect-matcher-node-mock
26
26
  ```
27
27
 
28
28
  ```bash
29
- yarn add expect-matcher-node-mock
29
+ yarn add --dev expect-matcher-node-mock
30
30
  ```
31
31
 
32
32
  ```bash
33
- pnpm add expect-matcher-node-mock
33
+ pnpm add --save-dev expect-matcher-node-mock
34
34
  ```
35
35
 
36
36
  ## Usage
@@ -163,14 +163,24 @@ https://jestjs.io/docs/expect#tohaventhreturnedwithnthcall-value
163
163
  ## Requirements
164
164
 
165
165
  - **Node.js 18.0.0 or higher** (for native test runner support)
166
- - **expect package** (peer dependency) - Install with `npm install expect`
166
+ - **expect package** (peer dependency) - Version 29.0.0 or higher
167
+ - **jest-matcher-utils package** (peer dependency) - Version 29.0.0 or higher
168
+ - **chalk package** (peer dependency) - Version 4.0.0 or higher
167
169
 
168
170
  ## Peer Dependencies
169
171
 
170
- This package requires the `expect` library to be installed in your project:
172
+ This package requires the following libraries to be installed in your project:
171
173
 
172
174
  ```bash
173
- npm install expect
175
+ npm install expect jest-matcher-utils chalk
176
+ ```
177
+
178
+ ```bash
179
+ yarn add expect jest-matcher-utils chalk
180
+ ```
181
+
182
+ ```bash
183
+ pnpm add expect jest-matcher-utils chalk
174
184
  ```
175
185
 
176
186
  ## Troubleshooting
@@ -225,8 +235,14 @@ npm install
225
235
  # Run tests
226
236
  npm test
227
237
 
228
- # Run linting
238
+ # Run linting (uses Biome)
229
239
  npm run lint
240
+
241
+ # Auto-fix linting issues
242
+ npm run lint:fix
243
+
244
+ # Format code
245
+ npm run format
230
246
  ```
231
247
 
232
248
  ## Changelog
package/lib/index.mjs CHANGED
@@ -6,12 +6,12 @@ import {
6
6
  toHaveBeenCalledWith,
7
7
  toHaveBeenLastCalledWith,
8
8
  toHaveBeenNthCalledWith,
9
- toReturn,
9
+ toHaveLastReturnedWith,
10
+ toHaveNthReturnedWith,
10
11
  toHaveReturned,
11
12
  toHaveReturnedTimes,
12
13
  toHaveReturnedWith,
13
- toHaveLastReturnedWith,
14
- toHaveNthReturnedWith,
14
+ toReturn,
15
15
  } from './mockMethodMatchers.mjs';
16
16
 
17
17
  expect.extend({
@@ -13,13 +13,44 @@ export const INVERTED_COLOR = chalk.inverse;
13
13
  export const BOLD_WEIGHT = chalk.bold;
14
14
  export const DIM_COLOR = chalk.dim;
15
15
 
16
+ /**
17
+ * Helper function to check if arguments match using deep equality
18
+ * @param {Array} callArgs - The arguments from the actual call
19
+ * @param {Array} expectedArgs - The expected arguments
20
+ * @returns {boolean} - Whether the arguments match
21
+ */
22
+ function argumentsMatch(callArgs, expectedArgs) {
23
+ if (callArgs.length !== expectedArgs.length) {
24
+ return false;
25
+ }
26
+ return expectedArgs.every((arg, index) => {
27
+ try {
28
+ expect(callArgs[index]).toEqual(arg);
29
+ return true;
30
+ } catch (_) {
31
+ return false;
32
+ }
33
+ });
34
+ }
35
+
36
+ /**
37
+ * Helper function to format expected arguments message
38
+ * @param {Array} args - The arguments to format
39
+ * @returns {string} - Formatted message
40
+ */
41
+ function formatExpectedArgs(args) {
42
+ return args.length === 0
43
+ ? 'called with 0 arguments'
44
+ : args.map(arg => printExpected(arg)).join(', ');
45
+ }
46
+
16
47
  /**
17
48
  * Function to ensure that the received value is a mock function
18
- * @param {Function} received
19
- * @param {string} matcherName
20
- * @param {Object} options
21
- * @returns {boolean}
22
- * @throws {TypeError}
49
+ * @param {Function} received - The value to check if it's a mock function
50
+ * @param {string} matcherName - The name of the matcher being used
51
+ * @param {Object} [options={}] - Optional configuration for error messages
52
+ * @returns {boolean} - Returns true if validation passes
53
+ * @throws {TypeError} - Throws if received is not a function or not a mock function
23
54
  */
24
55
  function ensureReceivedIsNodeMock(received, matcherName, options = {}) {
25
56
  if (typeof received !== 'function') {
@@ -45,11 +76,11 @@ function ensureReceivedIsNodeMock(received, matcherName, options = {}) {
45
76
 
46
77
  /**
47
78
  * Factory function to create a matcher object
48
- * @param {string} matcherName
49
- * @param {Object} options
50
- * @param {boolean} options.isNot
51
- * @param {boolean} options.promise
52
- * @returns {Object}
79
+ * @param {string} matcherName - The name of the matcher
80
+ * @param {Object} [options={}] - Optional configuration
81
+ * @param {boolean} [options.isNot] - Whether this is a negated matcher
82
+ * @param {boolean} [options.promise] - Whether this is a promise matcher
83
+ * @returns {Object} - An object containing matcherName, options, and receivedText
53
84
  */
54
85
  function matcherFactory(matcherName, { isNot, promise } = {}) {
55
86
  return {
@@ -64,9 +95,11 @@ function matcherFactory(matcherName, { isNot, promise } = {}) {
64
95
  }
65
96
 
66
97
  /**
67
- * toHaveBeenCalled
68
- *
69
- * https://jestjs.io/docs/expect#tohavebeencalled
98
+ * Matcher to verify that a mock function was called at least once
99
+ * @param {Function} receivedMethod - The mock function to check
100
+ * @param {...any} args - Should not be provided (will cause an error if present)
101
+ * @returns {Object} - An object with pass and message properties
102
+ * @see https://jestjs.io/docs/expect#tohavebeencalled
70
103
  */
71
104
  function toHaveBeenCalled(receivedMethod, ...args) {
72
105
  const { matcherName, options, receivedText } = matcherFactory(
@@ -74,46 +107,37 @@ function toHaveBeenCalled(receivedMethod, ...args) {
74
107
  this
75
108
  );
76
109
 
77
- if (ensureReceivedIsNodeMock(receivedMethod, matcherName, options)) {
78
- let message = `\n${matcherHint(
79
- matcherName,
80
- receivedText,
81
- '',
82
- options
83
- )}\n\n`;
84
- let pass = false;
110
+ ensureReceivedIsNodeMock(receivedMethod, matcherName, options);
85
111
 
86
- if (args && args[0]) {
87
- message += `Matcher error: this matcher must not have an expected argument
88
- \nExpected has type: ${typeof args[0]}\nExpected has value: ${EXPECTED_COLOR(
89
- args[0]
90
- )}\n`;
91
- } else {
92
- pass = receivedMethod.mock.calls.length > 0;
93
- if (pass) {
94
- message += `Expected number of calls: ${EXPECTED_COLOR(
95
- '0'
96
- )}\nReceived number of calls: ${RECEIVED_COLOR(
97
- receivedMethod.mock.calls.length
98
- )}\n`;
99
- } else {
100
- message += `Expected number of calls: >= ${EXPECTED_COLOR(
101
- '1'
102
- )}\nReceived number of calls: ${RECEIVED_COLOR('0')}\n`;
103
- }
104
- }
112
+ const hint = `\n${matcherHint(matcherName, receivedText, '', options)}\n\n`;
105
113
 
114
+ if (args.length > 0) {
106
115
  return {
107
- pass,
108
- message: () => message,
116
+ pass: false,
117
+ message: () =>
118
+ `${hint}Matcher error: this matcher must not have an expected argument\n` +
119
+ `Expected has type: ${typeof args[0]}\nExpected has value: ${EXPECTED_COLOR(args[0])}\n`,
109
120
  };
110
121
  }
122
+
123
+ const pass = receivedMethod.mock.calls.length > 0;
124
+ const callsCount = receivedMethod.mock.calls.length;
125
+ const message = pass
126
+ ? `Expected number of calls: ${EXPECTED_COLOR('0')}\nReceived number of calls: ${RECEIVED_COLOR(callsCount)}\n`
127
+ : `Expected number of calls: >= ${EXPECTED_COLOR('1')}\nReceived number of calls: ${RECEIVED_COLOR('0')}\n`;
128
+
129
+ return {
130
+ pass,
131
+ message: () => hint + message,
132
+ };
111
133
  }
112
134
 
113
135
  /**
114
- * toHaveBeenCalledTimes
115
- *
116
- * https://jestjs.io/docs/expect#tohavebeencalledtimesnumber
136
+ * Matcher to verify that a mock function was called an exact number of times
137
+ * @param {Function} receivedMethod - The mock function to check
138
+ * @param {number} expected - The expected number of calls
139
+ * @returns {Object} - An object with pass and message properties
140
+ * @see https://jestjs.io/docs/expect#tohavebeencalledtimesnumber
117
141
  */
118
142
  function toHaveBeenCalledTimes(receivedMethod, expected) {
119
143
  const { matcherName, options, receivedText } = matcherFactory(
@@ -147,9 +171,11 @@ function toHaveBeenCalledTimes(receivedMethod, expected) {
147
171
  }
148
172
 
149
173
  /**
150
- * toHaveBeenCalledWith
151
- *
152
- * https://jestjs.io/docs/expect#tohavebeencalledwitharg1-arg2-
174
+ * Matcher to verify that a mock function was called with specific arguments at least once
175
+ * @param {Function} receivedMethod - The mock function to check
176
+ * @param {...any} args - The expected arguments
177
+ * @returns {Object} - An object with pass and message properties
178
+ * @see https://jestjs.io/docs/expect#tohavebeencalledwitharg1-arg2-
153
179
  */
154
180
  function toHaveBeenCalledWith(receivedMethod, ...args) {
155
181
  const { matcherName, options, receivedText } = matcherFactory(
@@ -157,77 +183,41 @@ function toHaveBeenCalledWith(receivedMethod, ...args) {
157
183
  this
158
184
  );
159
185
 
160
- if (ensureReceivedIsNodeMock(receivedMethod, matcherName, options)) {
161
- const argsCount = args.length;
162
- const receivedArgs = [];
163
- let pass = false;
164
- let message = '';
165
-
166
- if (!receivedMethod.mock.calls.length) {
167
- pass = false;
168
- message = `Expected: `;
169
- if (argsCount === 0) {
170
- message += `called with 0 arguments`;
171
- } else {
172
- message += `${args.map(arg => printExpected(arg)).join(', ')}`;
173
- }
174
- } else {
175
- pass = receivedMethod.mock.calls.some(call => {
176
- receivedArgs.push(call.arguments);
177
- return (
178
- argsCount &&
179
- args.every((arg, index) => {
180
- try {
181
- expect(call.arguments[index]).toEqual(arg);
182
- return true;
183
- } catch (_) {
184
- return false;
185
- }
186
- })
187
- );
188
- });
189
-
190
- message = `Expected: ${pass ? 'not ' : ''}`;
191
-
192
- if (argsCount === 0) {
193
- message += `called with 0 arguments`;
194
- } else {
195
- message += `${args.map(arg => printExpected(arg)).join(', ')}`;
196
- }
197
-
198
- message += `\n\nReceived\n${receivedArgs.reduce(
199
- (receivedText, args, index) =>
200
- (receivedText +=
201
- args.length === 0
202
- ? '\t' + index + ': called with 0 arguments\n'
203
- : '\t' +
204
- index +
205
- ': ' +
206
- args.map(arg => printReceived(arg)).join(', ') +
207
- '\n'),
208
- ''
209
- )}`;
210
- }
211
-
212
- return {
213
- pass,
214
- message: () =>
215
- `\n${matcherHint(
216
- matcherName,
217
- receivedText,
218
- '...expected',
219
- options
220
- )}\n\n${message}\n\nNumber of calls: ${RECEIVED_COLOR(
221
- receivedMethod.mock.calls.length
222
- )}\n`,
223
- };
186
+ ensureReceivedIsNodeMock(receivedMethod, matcherName, options);
187
+
188
+ const calls = receivedMethod.mock.calls;
189
+ const pass = calls.some(call => argumentsMatch(call.arguments, args));
190
+
191
+ const expectedMsg = `Expected: ${pass ? 'not ' : ''}${formatExpectedArgs(args)}`;
192
+
193
+ let receivedMsg = '';
194
+ if (calls.length === 0) {
195
+ receivedMsg = 'But the function was not called';
196
+ } else {
197
+ receivedMsg = `\n\nReceived\n${calls
198
+ .map((call, index) => {
199
+ const callArgs = call.arguments;
200
+ return callArgs.length === 0
201
+ ? `\t${index}: called with 0 arguments`
202
+ : `\t${index}: ${callArgs.map(arg => printReceived(arg)).join(', ')}`;
203
+ })
204
+ .join('\n')}`;
224
205
  }
206
+
207
+ return {
208
+ pass,
209
+ message: () =>
210
+ `\n${matcherHint(matcherName, receivedText, '...expected', options)}\n\n` +
211
+ `${expectedMsg}${receivedMsg}\n\nNumber of calls: ${RECEIVED_COLOR(calls.length)}\n`,
212
+ };
225
213
  }
226
214
 
227
215
  /**
228
- * toHaveBeenLastCalledWith
229
- *
230
- * https://jestjs.io/docs/expect#tohavebeenlastcalledwitharg1-arg2-
216
+ * Matcher to verify that a mock function was last called with specific arguments
217
+ * @param {Function} receivedMethod - The mock function to check
218
+ * @param {...any} args - The expected arguments
219
+ * @returns {Object} - An object with pass and message properties
220
+ * @see https://jestjs.io/docs/expect#tohavebeenlastcalledwitharg1-arg2-
231
221
  */
232
222
  function toHaveBeenLastCalledWith(receivedMethod, ...args) {
233
223
  const { matcherName, options, receivedText } = matcherFactory(
@@ -235,67 +225,44 @@ function toHaveBeenLastCalledWith(receivedMethod, ...args) {
235
225
  this
236
226
  );
237
227
 
238
- if (ensureReceivedIsNodeMock(receivedMethod, matcherName, options)) {
239
- const argsCount = args.length;
240
- const receivedArgs = [];
241
- let pass = false;
242
- let message = '';
243
- const callsCount = receivedMethod.mock.calls.length;
244
-
245
- if (!callsCount) {
246
- pass = false;
247
- message = `Expected: `;
248
- if (argsCount === 0) {
249
- message += `called with 0 arguments`;
250
- } else {
251
- message += `${args.map(arg => printExpected(arg)).join(', ')}`;
252
- }
253
- } else {
254
- const lastCall = receivedMethod.mock.calls[callsCount - 1];
255
- receivedArgs.push(lastCall.arguments);
256
- pass =
257
- argsCount &&
258
- args.every((arg, index) => {
259
- try {
260
- expect(lastCall.arguments[index]).toEqual(arg);
261
- return true;
262
- } catch (_) {
263
- return false;
264
- }
265
- });
266
-
267
- message = `Expected: ${pass ? 'not ' : ''}`;
268
-
269
- if (argsCount === 0) {
270
- message += `called with 0 arguments`;
271
- } else {
272
- message += `${args.map(arg => printExpected(arg)).join(', ')}`;
273
- }
274
-
275
- message += `\n\n\nReceived: ${receivedArgs.map(args =>
276
- args.map(arg => printReceived(arg)).join(', ')
277
- )}`;
278
- }
228
+ ensureReceivedIsNodeMock(receivedMethod, matcherName, options);
279
229
 
230
+ const calls = receivedMethod.mock.calls;
231
+ const callsCount = calls.length;
232
+
233
+ if (callsCount === 0) {
280
234
  return {
281
- pass,
235
+ pass: false,
282
236
  message: () =>
283
- `${matcherHint(
284
- matcherName,
285
- receivedText,
286
- '...expected',
287
- options
288
- )}\n\n${message}\n\nNumber of calls: ${RECEIVED_COLOR(
289
- receivedMethod.mock.calls.length
290
- )}\n`,
237
+ `${matcherHint(matcherName, receivedText, '...expected', options)}\n\n` +
238
+ `Expected: ${formatExpectedArgs(args)}\n` +
239
+ `But the function was not called\n\nNumber of calls: ${RECEIVED_COLOR(0)}\n`,
291
240
  };
292
241
  }
242
+
243
+ const lastCall = calls[callsCount - 1];
244
+ const pass = argumentsMatch(lastCall.arguments, args);
245
+ const receivedArgs = lastCall.arguments
246
+ .map(arg => printReceived(arg))
247
+ .join(', ');
248
+
249
+ return {
250
+ pass,
251
+ message: () =>
252
+ `${matcherHint(matcherName, receivedText, '...expected', options)}\n\n` +
253
+ `Expected: ${pass ? 'not ' : ''}${formatExpectedArgs(args)}\n\n\n` +
254
+ `Received: ${receivedArgs || 'called with 0 arguments'}\n\n` +
255
+ `Number of calls: ${RECEIVED_COLOR(callsCount)}\n`,
256
+ };
293
257
  }
294
258
 
295
259
  /**
296
- * toHaveBeenNthCalledWith
297
- *
298
- * https://jestjs.io/docs/expect#tohavebeennthcalledwithnthcall-arg1-arg2-
260
+ * Matcher to verify that a mock function was called with specific arguments on the nth call
261
+ * @param {Function} receivedMethod - The mock function to check
262
+ * @param {number} nthCallIndex - The call number to check (1-indexed)
263
+ * @param {...any} args - The expected arguments
264
+ * @returns {Object} - An object with pass and message properties
265
+ * @see https://jestjs.io/docs/expect#tohavebeennthcalledwithnthcall-arg1-arg2-
299
266
  */
300
267
  function toHaveBeenNthCalledWith(receivedMethod, nthCallIndex, ...args) {
301
268
  const { matcherName, options, receivedText } = matcherFactory(
@@ -303,71 +270,45 @@ function toHaveBeenNthCalledWith(receivedMethod, nthCallIndex, ...args) {
303
270
  this
304
271
  );
305
272
 
306
- if (ensureReceivedIsNodeMock(receivedMethod, matcherName, options)) {
307
- const argsCount = args.length;
308
- const receivedArgs = [];
309
- let pass = false;
310
- let message = `n: ${nthCallIndex}\n`;
311
- const callsCount = receivedMethod.mock.calls.length;
312
-
313
- if (!callsCount || nthCallIndex > callsCount) {
314
- pass = false;
315
- message += `Expected: `;
316
- if (argsCount === 0) {
317
- message += `called with 0 arguments`;
318
- } else {
319
- message += `${args.map(arg => printExpected(arg)).join(', ')}`;
320
- }
321
- } else {
322
- const nthCall = receivedMethod.mock.calls[nthCallIndex - 1];
323
- receivedArgs.push(nthCall.arguments);
324
- pass =
325
- argsCount &&
326
- args.every((arg, index) => {
327
- try {
328
- expect(nthCall.arguments[index]).toEqual(arg);
329
- return true;
330
- } catch (_) {
331
- return false;
332
- }
333
- });
334
-
335
- message += `Expected: ${pass ? 'not ' : ''}`;
336
-
337
- if (argsCount === 0) {
338
- message += `called with 0 arguments`;
339
- } else {
340
- message += `${args.map(arg => printExpected(arg)).join(', ')}`;
341
- }
342
-
343
- message += `\nReceived: ${
344
- receivedArgs.length
345
- ? receivedArgs.map(args =>
346
- args.map(arg => printReceived(arg)).join(', ')
347
- )
348
- : 'called with 0 arguments'
349
- }`;
350
- }
273
+ ensureReceivedIsNodeMock(receivedMethod, matcherName, options);
274
+
275
+ const calls = receivedMethod.mock.calls;
276
+ const callsCount = calls.length;
351
277
 
278
+ if (nthCallIndex < 1 || nthCallIndex > callsCount) {
352
279
  return {
353
- pass,
280
+ pass: false,
354
281
  message: () =>
355
- `${matcherHint(
356
- matcherName,
357
- receivedText,
358
- '...expected',
359
- options
360
- )}\n\n${message}\n\nNumber of calls: ${RECEIVED_COLOR(
361
- receivedMethod.mock.calls.length
362
- )}\n`,
282
+ `${matcherHint(matcherName, receivedText, '...expected', options)}\n\n` +
283
+ `n: ${nthCallIndex}\n` +
284
+ `Expected: ${formatExpectedArgs(args)}\n` +
285
+ `But the function was ${callsCount === 0 ? 'not called' : `only called ${callsCount} time(s)`}\n\n` +
286
+ `Number of calls: ${RECEIVED_COLOR(callsCount)}\n`,
363
287
  };
364
288
  }
289
+
290
+ const nthCall = calls[nthCallIndex - 1];
291
+ const pass = argumentsMatch(nthCall.arguments, args);
292
+ const receivedArgs = nthCall.arguments
293
+ .map(arg => printReceived(arg))
294
+ .join(', ');
295
+
296
+ return {
297
+ pass,
298
+ message: () =>
299
+ `${matcherHint(matcherName, receivedText, '...expected', options)}\n\n` +
300
+ `n: ${nthCallIndex}\n` +
301
+ `Expected: ${pass ? 'not ' : ''}${formatExpectedArgs(args)}\n` +
302
+ `Received: ${receivedArgs || 'called with 0 arguments'}\n\n` +
303
+ `Number of calls: ${RECEIVED_COLOR(callsCount)}\n`,
304
+ };
365
305
  }
366
306
 
367
307
  /**
368
- * toHaveReturned
369
- *
370
- * https://jestjs.io/docs/expect#tohavereturned
308
+ * Matcher to verify that a mock function successfully returned (without error) at least once
309
+ * @param {Function} receivedMethod - The mock function to check
310
+ * @returns {Object} - An object with pass and message properties
311
+ * @see https://jestjs.io/docs/expect#tohavereturned
371
312
  */
372
313
  function toHaveReturned(receivedMethod) {
373
314
  const { matcherName, options, receivedText } = matcherFactory(
@@ -405,18 +346,21 @@ function toHaveReturned(receivedMethod) {
405
346
  }
406
347
 
407
348
  /**
408
- * toReturn
409
- *
410
- * https://jestjs.io/docs/expect#tohavereturned
349
+ * Alias for toHaveReturned - verifies that a mock function successfully returned at least once
350
+ * @param {Function} receivedMethod - The mock function to check
351
+ * @returns {Object} - An object with pass and message properties
352
+ * @see https://jestjs.io/docs/expect#tohavereturned
411
353
  */
412
354
  function toReturn(receivedMethod) {
413
355
  return toHaveReturned(receivedMethod);
414
356
  }
415
357
 
416
358
  /**
417
- * toHaveReturnedTimes
418
- *
419
- * https://jestjs.io/docs/expect#tohavereturnedtimesnumber
359
+ * Matcher to verify that a mock function successfully returned a specific number of times
360
+ * @param {Function} receivedMethod - The mock function to check
361
+ * @param {number} times - The expected number of successful returns
362
+ * @returns {Object} - An object with pass and message properties
363
+ * @see https://jestjs.io/docs/expect#tohavereturnedtimesnumber
420
364
  */
421
365
  function toHaveReturnedTimes(receivedMethod, times) {
422
366
  const { matcherName, options, receivedText } = matcherFactory(
@@ -424,40 +368,28 @@ function toHaveReturnedTimes(receivedMethod, times) {
424
368
  this
425
369
  );
426
370
 
427
- if (ensureReceivedIsNodeMock(receivedMethod, matcherName, options)) {
428
- const noErrorCalls = receivedMethod.mock.calls.filter(
429
- call => call.error === undefined
430
- );
431
- const pass = noErrorCalls.length === times;
432
-
433
- let message = `\n${matcherHint(
434
- matcherName,
435
- receivedText,
436
- 'expected',
437
- options
438
- )}\n\n`;
371
+ ensureReceivedIsNodeMock(receivedMethod, matcherName, options);
439
372
 
440
- if (pass) {
441
- message += `Expected: ${printExpected(times)}\nReceived: ${printReceived(
442
- noErrorCalls.length
443
- )}`;
444
- } else {
445
- message += `Expected: ${printExpected(times)}\nReceived: ${printReceived(
446
- noErrorCalls.length
447
- )}`;
448
- }
373
+ const successfulReturns = receivedMethod.mock.calls.filter(
374
+ call => call.error === undefined
375
+ ).length;
376
+ const pass = successfulReturns === times;
449
377
 
450
- return {
451
- pass,
452
- message: () => message,
453
- };
454
- }
378
+ return {
379
+ pass,
380
+ message: () =>
381
+ `\n${matcherHint(matcherName, receivedText, 'expected', options)}\n\n` +
382
+ `Expected: ${pass ? 'not ' : ''}${printExpected(times)}\n` +
383
+ `Received: ${printReceived(successfulReturns)}`,
384
+ };
455
385
  }
456
386
 
457
387
  /**
458
- * toHaveReturnedWith
459
- *
460
- * https://jestjs.io/docs/expect#tohavereturnedwithvalue
388
+ * Matcher to verify that a mock function returned a specific value at least once
389
+ * @param {Function} receivedMethod - The mock function to check
390
+ * @param {any} expected - The expected return value
391
+ * @returns {Object} - An object with pass and message properties
392
+ * @see https://jestjs.io/docs/expect#tohavereturnedwithvalue
461
393
  */
462
394
  function toHaveReturnedWith(receivedMethod, expected) {
463
395
  const { matcherName, options, receivedText } = matcherFactory(
@@ -465,43 +397,34 @@ function toHaveReturnedWith(receivedMethod, expected) {
465
397
  this
466
398
  );
467
399
 
468
- if (ensureReceivedIsNodeMock(receivedMethod, matcherName, options)) {
469
- const pass = receivedMethod.mock.calls.some(
470
- call => call.result === expected
471
- );
400
+ ensureReceivedIsNodeMock(receivedMethod, matcherName, options);
472
401
 
473
- let message = `\n${matcherHint(
474
- matcherName,
475
- receivedText,
476
- 'expected',
477
- options
478
- )}\n\n`;
479
-
480
- if (pass) {
481
- message += `Expected: ${printExpected(
482
- expected
483
- )}\nReceived: ${printReceived(
484
- receivedMethod.mock.calls.map(call => call.result)
485
- )}`;
486
- } else {
487
- message += `Expected: ${printExpected(
488
- expected
489
- )}\nReceived: ${printReceived(
490
- receivedMethod.mock.calls.map(call => call.result)
491
- )}`;
402
+ const pass = receivedMethod.mock.calls.some(call => {
403
+ try {
404
+ expect(call.result).toEqual(expected);
405
+ return true;
406
+ } catch (_) {
407
+ return false;
492
408
  }
409
+ });
493
410
 
494
- return {
495
- pass,
496
- message: () => message,
497
- };
498
- }
411
+ const allResults = receivedMethod.mock.calls.map(call => call.result);
412
+
413
+ return {
414
+ pass,
415
+ message: () =>
416
+ `\n${matcherHint(matcherName, receivedText, 'expected', options)}\n\n` +
417
+ `Expected: ${pass ? 'not ' : ''}${printExpected(expected)}\n` +
418
+ `Received: ${printReceived(allResults)}`,
419
+ };
499
420
  }
500
421
 
501
422
  /**
502
- * toHaveLastReturnedWith
503
- *
504
- * https://jestjs.io/docs/expect#tohavelastreturnedwithvalue
423
+ * Matcher to verify that a mock function's last return value matches the expected value
424
+ * @param {Function} receivedMethod - The mock function to check
425
+ * @param {any} expected - The expected return value
426
+ * @returns {Object} - An object with pass and message properties
427
+ * @see https://jestjs.io/docs/expect#tohavelastreturnedwithvalue
505
428
  */
506
429
  function toHaveLastReturnedWith(receivedMethod, expected) {
507
430
  const { matcherName, options, receivedText } = matcherFactory(
@@ -509,39 +432,42 @@ function toHaveLastReturnedWith(receivedMethod, expected) {
509
432
  this
510
433
  );
511
434
 
512
- if (ensureReceivedIsNodeMock(receivedMethod, matcherName, options)) {
513
- const lastCall =
514
- receivedMethod.mock.calls[receivedMethod.mock.calls.length - 1];
515
- const pass = lastCall.result === expected;
516
-
517
- let message = `\n${matcherHint(
518
- matcherName,
519
- receivedText,
520
- 'expected',
521
- options
522
- )}\n\n`;
523
-
524
- if (pass) {
525
- message += `Expected: ${printExpected(
526
- expected
527
- )}\nReceived: ${printReceived(lastCall.result)}`;
528
- } else {
529
- message += `Expected: ${printExpected(
530
- expected
531
- )}\nReceived: ${printReceived(lastCall.result)}`;
532
- }
435
+ ensureReceivedIsNodeMock(receivedMethod, matcherName, options);
533
436
 
437
+ const calls = receivedMethod.mock.calls;
438
+ if (calls.length === 0) {
534
439
  return {
535
- pass,
536
- message: () => message,
440
+ pass: false,
441
+ message: () =>
442
+ `\n${matcherHint(matcherName, receivedText, 'expected', options)}\n\nExpected: ${printExpected(expected)}\nBut the function was not called`,
537
443
  };
538
444
  }
445
+
446
+ const lastCall = calls[calls.length - 1];
447
+ let pass = false;
448
+ try {
449
+ expect(lastCall.result).toEqual(expected);
450
+ pass = true;
451
+ } catch (_) {
452
+ pass = false;
453
+ }
454
+
455
+ return {
456
+ pass,
457
+ message: () =>
458
+ `\n${matcherHint(matcherName, receivedText, 'expected', options)}\n\n` +
459
+ `Expected: ${pass ? 'not ' : ''}${printExpected(expected)}\n` +
460
+ `Received: ${printReceived(lastCall.result)}`,
461
+ };
539
462
  }
540
463
 
541
464
  /**
542
- * toHaveNthReturnedWith
543
- *
544
- * https://jestjs.io/docs/expect#tohaventhreturnedwithnthcall-value
465
+ * Matcher to verify that a mock function's nth return value matches the expected value
466
+ * @param {Function} receivedMethod - The mock function to check
467
+ * @param {number} nthCall - The call number to check (1-indexed)
468
+ * @param {any} expected - The expected return value
469
+ * @returns {Object} - An object with pass and message properties
470
+ * @see https://jestjs.io/docs/expect#tohaventhreturnedwithnthcall-value
545
471
  */
546
472
  function toHaveNthReturnedWith(receivedMethod, nthCall, expected) {
547
473
  const { matcherName, options, receivedText } = matcherFactory(
@@ -549,32 +475,37 @@ function toHaveNthReturnedWith(receivedMethod, nthCall, expected) {
549
475
  this
550
476
  );
551
477
 
552
- if (ensureReceivedIsNodeMock(receivedMethod, matcherName, options)) {
553
- const nthCallResult = receivedMethod.mock.calls[nthCall - 1].result;
554
- const pass = nthCallResult === expected;
555
-
556
- let message = `\n${matcherHint(
557
- matcherName,
558
- receivedText,
559
- 'expected',
560
- options
561
- )}\n\n`;
562
-
563
- if (pass) {
564
- message += `Expected: ${printExpected(
565
- expected
566
- )}\nReceived: ${printReceived(nthCallResult)}`;
567
- } else {
568
- message += `Expected: ${printExpected(
569
- expected
570
- )}\nReceived: ${printReceived(nthCallResult)}`;
571
- }
478
+ ensureReceivedIsNodeMock(receivedMethod, matcherName, options);
572
479
 
480
+ const calls = receivedMethod.mock.calls;
481
+ if (nthCall < 1 || nthCall > calls.length) {
573
482
  return {
574
- pass,
575
- message: () => message,
483
+ pass: false,
484
+ message: () =>
485
+ `\n${matcherHint(matcherName, receivedText, 'expected', options)}\n\n` +
486
+ `n: ${nthCall}\n` +
487
+ `Expected: ${printExpected(expected)}\n` +
488
+ `But the function was ${calls.length === 0 ? 'not called' : `only called ${calls.length} time(s)`}`,
576
489
  };
577
490
  }
491
+
492
+ const nthCallResult = calls[nthCall - 1].result;
493
+ let pass = false;
494
+ try {
495
+ expect(nthCallResult).toEqual(expected);
496
+ pass = true;
497
+ } catch (_) {
498
+ pass = false;
499
+ }
500
+
501
+ return {
502
+ pass,
503
+ message: () =>
504
+ `\n${matcherHint(matcherName, receivedText, 'expected', options)}\n\n` +
505
+ `n: ${nthCall}\n` +
506
+ `Expected: ${pass ? 'not ' : ''}${printExpected(expected)}\n` +
507
+ `Received: ${printReceived(nthCallResult)}`,
508
+ };
578
509
  }
579
510
 
580
511
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expect-matcher-node-mock",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Jest-like expect matchers for native Node.js test runner mock objects. Provides toHaveBeenCalled, toHaveBeenCalledWith, toHaveReturned and more.",
5
5
  "main": "lib/index.mjs",
6
6
  "type": "module",
@@ -39,19 +39,18 @@
39
39
  "license": "MIT",
40
40
  "author": "Crysa Drak <cd@l33t.cz>",
41
41
  "scripts": {
42
- "lint": "eslint --cache --cache-location=node_modules/.cache/ './**/*.{js,jsx,ts,tsx,mjs}'",
42
+ "lint": "biome check .",
43
+ "lint:fix": "biome check --write .",
44
+ "format": "biome format --write .",
43
45
  "test": "node --test --test-reporter=spec"
44
46
  },
45
47
  "devDependencies": {
46
- "eslint": "^8.27.0",
47
- "eslint-config-prettier": "^8.5.0",
48
- "eslint-plugin-import": "^2.27.5",
49
- "eslint-plugin-prettier": "^4.2.1",
50
- "prettier": "^2.8.4",
48
+ "@biomejs/biome": "^1.9.4",
51
49
  "strip-ansi": "7.1.0"
52
50
  },
53
- "dependencies": {
54
- "expect": "29.7.0",
55
- "jest-matcher-utils": "29.7.0"
51
+ "peerDependencies": {
52
+ "chalk": ">=4.0.0",
53
+ "expect": ">=29.0.0",
54
+ "jest-matcher-utils": ">=29.0.0"
56
55
  }
57
56
  }