@vitest/expect 2.1.4 → 2.2.0-beta.1

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/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { MockInstance } from '@vitest/spy';
1
2
  import { stringify, Constructable } from '@vitest/utils';
2
3
  import * as tinyrainbow from 'tinyrainbow';
3
4
  import { Formatter } from 'tinyrainbow';
@@ -97,55 +98,433 @@ interface ExpectStatic extends Chai.ExpectStatic, AsymmetricMatchersContaining {
97
98
  not: AsymmetricMatchersContaining;
98
99
  }
99
100
  interface AsymmetricMatchersContaining {
101
+ /**
102
+ * Matches if the received string contains the expected substring.
103
+ *
104
+ * @example
105
+ * expect('I have an apple').toEqual(expect.stringContaining('apple'));
106
+ * expect({ a: 'test string' }).toEqual({ a: expect.stringContaining('test') });
107
+ */
100
108
  stringContaining: (expected: string) => any;
109
+ /**
110
+ * Matches if the received object contains all properties of the expected object.
111
+ *
112
+ * @example
113
+ * expect({ a: '1', b: 2 }).toEqual(expect.objectContaining({ a: '1' }))
114
+ */
101
115
  objectContaining: <T = any>(expected: T) => any;
116
+ /**
117
+ * Matches if the received array contains all elements in the expected array.
118
+ *
119
+ * @example
120
+ * expect(['a', 'b', 'c']).toEqual(expect.arrayContaining(['b', 'a']));
121
+ */
102
122
  arrayContaining: <T = unknown>(expected: Array<T>) => any;
123
+ /**
124
+ * Matches if the received string or regex matches the expected pattern.
125
+ *
126
+ * @example
127
+ * expect('hello world').toEqual(expect.stringMatching(/^hello/));
128
+ * expect('hello world').toEqual(expect.stringMatching('hello'));
129
+ */
103
130
  stringMatching: (expected: string | RegExp) => any;
131
+ /**
132
+ * Matches if the received number is within a certain precision of the expected number.
133
+ *
134
+ * @param precision - Optional decimal precision for comparison. Default is 2.
135
+ *
136
+ * @example
137
+ * expect(10.45).toEqual(expect.closeTo(10.5, 1));
138
+ * expect(5.11).toEqual(expect.closeTo(5.12)); // with default precision
139
+ */
104
140
  closeTo: (expected: number, precision?: number) => any;
105
141
  }
106
142
  interface JestAssertion<T = any> extends jest.Matchers<void, T> {
143
+ /**
144
+ * Used when you want to check that two objects have the same value.
145
+ * This matcher recursively checks the equality of all fields, rather than checking for object identity.
146
+ *
147
+ * @example
148
+ * expect(user).toEqual({ name: 'Alice', age: 30 });
149
+ */
107
150
  toEqual: <E>(expected: E) => void;
151
+ /**
152
+ * Use to test that objects have the same types as well as structure.
153
+ *
154
+ * @example
155
+ * expect(user).toStrictEqual({ name: 'Alice', age: 30 });
156
+ */
108
157
  toStrictEqual: <E>(expected: E) => void;
158
+ /**
159
+ * Checks that a value is what you expect. It calls `Object.is` to compare values.
160
+ * Don't use `toBe` with floating-point numbers.
161
+ *
162
+ * @example
163
+ * expect(result).toBe(42);
164
+ * expect(status).toBe(true);
165
+ */
109
166
  toBe: <E>(expected: E) => void;
167
+ /**
168
+ * Check that a string matches a regular expression.
169
+ *
170
+ * @example
171
+ * expect(message).toMatch(/hello/);
172
+ * expect(greeting).toMatch('world');
173
+ */
110
174
  toMatch: (expected: string | RegExp) => void;
175
+ /**
176
+ * Used to check that a JavaScript object matches a subset of the properties of an object
177
+ *
178
+ * @example
179
+ * expect(user).toMatchObject({
180
+ * name: 'Alice',
181
+ * address: { city: 'Wonderland' }
182
+ * });
183
+ */
111
184
  toMatchObject: <E extends object | any[]>(expected: E) => void;
185
+ /**
186
+ * Used when you want to check that an item is in a list.
187
+ * For testing the items in the list, this uses `===`, a strict equality check.
188
+ *
189
+ * @example
190
+ * expect(items).toContain('apple');
191
+ * expect(numbers).toContain(5);
192
+ */
112
193
  toContain: <E>(item: E) => void;
194
+ /**
195
+ * Used when you want to check that an item is in a list.
196
+ * For testing the items in the list, this matcher recursively checks the
197
+ * equality of all fields, rather than checking for object identity.
198
+ *
199
+ * @example
200
+ * expect(items).toContainEqual({ name: 'apple', quantity: 1 });
201
+ */
113
202
  toContainEqual: <E>(item: E) => void;
203
+ /**
204
+ * Use when you don't care what a value is, you just want to ensure a value
205
+ * is true in a boolean context. In JavaScript, there are six falsy values:
206
+ * `false`, `0`, `''`, `null`, `undefined`, and `NaN`. Everything else is truthy.
207
+ *
208
+ * @example
209
+ * expect(user.isActive).toBeTruthy();
210
+ */
114
211
  toBeTruthy: () => void;
212
+ /**
213
+ * When you don't care what a value is, you just want to
214
+ * ensure a value is false in a boolean context.
215
+ *
216
+ * @example
217
+ * expect(user.isActive).toBeFalsy();
218
+ */
115
219
  toBeFalsy: () => void;
220
+ /**
221
+ * For comparing floating point numbers.
222
+ *
223
+ * @example
224
+ * expect(score).toBeGreaterThan(10);
225
+ */
116
226
  toBeGreaterThan: (num: number | bigint) => void;
227
+ /**
228
+ * For comparing floating point numbers.
229
+ *
230
+ * @example
231
+ * expect(score).toBeGreaterThanOrEqual(10);
232
+ */
117
233
  toBeGreaterThanOrEqual: (num: number | bigint) => void;
234
+ /**
235
+ * For comparing floating point numbers.
236
+ *
237
+ * @example
238
+ * expect(score).toBeLessThan(10);
239
+ */
118
240
  toBeLessThan: (num: number | bigint) => void;
241
+ /**
242
+ * For comparing floating point numbers.
243
+ *
244
+ * @example
245
+ * expect(score).toBeLessThanOrEqual(10);
246
+ */
119
247
  toBeLessThanOrEqual: (num: number | bigint) => void;
248
+ /**
249
+ * Used to check that a variable is NaN.
250
+ *
251
+ * @example
252
+ * expect(value).toBeNaN();
253
+ */
120
254
  toBeNaN: () => void;
255
+ /**
256
+ * Used to check that a variable is undefined.
257
+ *
258
+ * @example
259
+ * expect(value).toBeUndefined();
260
+ */
121
261
  toBeUndefined: () => void;
262
+ /**
263
+ * This is the same as `.toBe(null)` but the error messages are a bit nicer.
264
+ * So use `.toBeNull()` when you want to check that something is null.
265
+ *
266
+ * @example
267
+ * expect(value).toBeNull();
268
+ */
122
269
  toBeNull: () => void;
270
+ /**
271
+ * Ensure that a variable is not undefined.
272
+ *
273
+ * @example
274
+ * expect(value).toBeDefined();
275
+ */
123
276
  toBeDefined: () => void;
277
+ /**
278
+ * Ensure that an object is an instance of a class.
279
+ * This matcher uses `instanceof` underneath.
280
+ *
281
+ * @example
282
+ * expect(new Date()).toBeInstanceOf(Date);
283
+ */
124
284
  toBeInstanceOf: <E>(expected: E) => void;
125
- toBeCalledTimes: (times: number) => void;
285
+ /**
286
+ * Used to check that an object has a `.length` property
287
+ * and it is set to a certain numeric value.
288
+ *
289
+ * @example
290
+ * expect([1, 2, 3]).toHaveLength(3);
291
+ * expect('hello').toHaveLength(5);
292
+ */
126
293
  toHaveLength: (length: number) => void;
294
+ /**
295
+ * Use to check if a property at the specified path exists on an object.
296
+ * For checking deeply nested properties, you may use dot notation or an array containing
297
+ * the path segments for deep references.
298
+ *
299
+ * Optionally, you can provide a value to check if it matches the value present at the path
300
+ * on the target object. This matcher uses 'deep equality' (like `toEqual()`) and recursively checks
301
+ * the equality of all fields.
302
+ *
303
+ * @example
304
+ * expect(user).toHaveProperty('address.city', 'New York');
305
+ * expect(config).toHaveProperty(['settings', 'theme'], 'dark');
306
+ */
127
307
  toHaveProperty: <E>(property: string | (string | number)[], value?: E) => void;
308
+ /**
309
+ * Using exact equality with floating point numbers is a bad idea.
310
+ * Rounding means that intuitive things fail.
311
+ * The default for `precision` is 2.
312
+ *
313
+ * @example
314
+ * expect(price).toBeCloseTo(9.99, 2);
315
+ */
128
316
  toBeCloseTo: (number: number, numDigits?: number) => void;
317
+ /**
318
+ * Ensures that a mock function is called an exact number of times.
319
+ *
320
+ * Also under the alias `expect.toBeCalledTimes`.
321
+ *
322
+ * @example
323
+ * expect(mockFunc).toHaveBeenCalledTimes(2);
324
+ */
129
325
  toHaveBeenCalledTimes: (times: number) => void;
326
+ /**
327
+ * Ensures that a mock function is called an exact number of times.
328
+ *
329
+ * Alias for `expect.toHaveBeenCalledTimes`.
330
+ *
331
+ * @example
332
+ * expect(mockFunc).toBeCalledTimes(2);
333
+ */
334
+ toBeCalledTimes: (times: number) => void;
335
+ /**
336
+ * Ensures that a mock function is called.
337
+ *
338
+ * Also under the alias `expect.toBeCalled`.
339
+ *
340
+ * @example
341
+ * expect(mockFunc).toHaveBeenCalled();
342
+ */
130
343
  toHaveBeenCalled: () => void;
344
+ /**
345
+ * Ensures that a mock function is called.
346
+ *
347
+ * Alias for `expect.toHaveBeenCalled`.
348
+ *
349
+ * @example
350
+ * expect(mockFunc).toBeCalled();
351
+ */
131
352
  toBeCalled: () => void;
353
+ /**
354
+ * Ensure that a mock function is called with specific arguments.
355
+ *
356
+ * Also under the alias `expect.toBeCalledWith`.
357
+ *
358
+ * @example
359
+ * expect(mockFunc).toHaveBeenCalledWith('arg1', 42);
360
+ */
132
361
  toHaveBeenCalledWith: <E extends any[]>(...args: E) => void;
362
+ /**
363
+ * Ensure that a mock function is called with specific arguments.
364
+ *
365
+ * Alias for `expect.toHaveBeenCalledWith`.
366
+ *
367
+ * @example
368
+ * expect(mockFunc).toBeCalledWith('arg1', 42);
369
+ */
133
370
  toBeCalledWith: <E extends any[]>(...args: E) => void;
371
+ /**
372
+ * Ensure that a mock function is called with specific arguments on an Nth call.
373
+ *
374
+ * Also under the alias `expect.nthCalledWith`.
375
+ *
376
+ * @example
377
+ * expect(mockFunc).toHaveBeenNthCalledWith(2, 'secondArg');
378
+ */
134
379
  toHaveBeenNthCalledWith: <E extends any[]>(n: number, ...args: E) => void;
380
+ /**
381
+ * Ensure that a mock function is called with specific arguments on an Nth call.
382
+ *
383
+ * Alias for `expect.toHaveBeenNthCalledWith`.
384
+ *
385
+ * @example
386
+ * expect(mockFunc).nthCalledWith(2, 'secondArg');
387
+ */
135
388
  nthCalledWith: <E extends any[]>(nthCall: number, ...args: E) => void;
389
+ /**
390
+ * If you have a mock function, you can use `.toHaveBeenLastCalledWith`
391
+ * to test what arguments it was last called with.
392
+ *
393
+ * Also under the alias `expect.lastCalledWith`.
394
+ *
395
+ * @example
396
+ * expect(mockFunc).toHaveBeenLastCalledWith('lastArg');
397
+ */
136
398
  toHaveBeenLastCalledWith: <E extends any[]>(...args: E) => void;
399
+ /**
400
+ * If you have a mock function, you can use `.lastCalledWith`
401
+ * to test what arguments it was last called with.
402
+ *
403
+ * Alias for `expect.toHaveBeenLastCalledWith`.
404
+ *
405
+ * @example
406
+ * expect(mockFunc).lastCalledWith('lastArg');
407
+ */
137
408
  lastCalledWith: <E extends any[]>(...args: E) => void;
409
+ /**
410
+ * Used to test that a function throws when it is called.
411
+ *
412
+ * Also under the alias `expect.toThrowError`.
413
+ *
414
+ * @example
415
+ * expect(() => functionWithError()).toThrow('Error message');
416
+ * expect(() => parseJSON('invalid')).toThrow(SyntaxError);
417
+ */
138
418
  toThrow: (expected?: string | Constructable | RegExp | Error) => void;
419
+ /**
420
+ * Used to test that a function throws when it is called.
421
+ *
422
+ * Alias for `expect.toThrow`.
423
+ *
424
+ * @example
425
+ * expect(() => functionWithError()).toThrowError('Error message');
426
+ * expect(() => parseJSON('invalid')).toThrowError(SyntaxError);
427
+ */
139
428
  toThrowError: (expected?: string | Constructable | RegExp | Error) => void;
429
+ /**
430
+ * Use to test that the mock function successfully returned (i.e., did not throw an error) at least one time
431
+ *
432
+ * Alias for `expect.toHaveReturned`.
433
+ *
434
+ * @example
435
+ * expect(mockFunc).toReturn();
436
+ */
140
437
  toReturn: () => void;
438
+ /**
439
+ * Use to test that the mock function successfully returned (i.e., did not throw an error) at least one time
440
+ *
441
+ * Also under the alias `expect.toReturn`.
442
+ *
443
+ * @example
444
+ * expect(mockFunc).toHaveReturned();
445
+ */
141
446
  toHaveReturned: () => void;
447
+ /**
448
+ * Use to ensure that a mock function returned successfully (i.e., did not throw an error) an exact number of times.
449
+ * Any calls to the mock function that throw an error are not counted toward the number of times the function returned.
450
+ *
451
+ * Alias for `expect.toHaveReturnedTimes`.
452
+ *
453
+ * @example
454
+ * expect(mockFunc).toReturnTimes(3);
455
+ */
142
456
  toReturnTimes: (times: number) => void;
457
+ /**
458
+ * Use to ensure that a mock function returned successfully (i.e., did not throw an error) an exact number of times.
459
+ * Any calls to the mock function that throw an error are not counted toward the number of times the function returned.
460
+ *
461
+ * Also under the alias `expect.toReturnTimes`.
462
+ *
463
+ * @example
464
+ * expect(mockFunc).toHaveReturnedTimes(3);
465
+ */
143
466
  toHaveReturnedTimes: (times: number) => void;
467
+ /**
468
+ * Use to ensure that a mock function returned a specific value.
469
+ *
470
+ * Alias for `expect.toHaveReturnedWith`.
471
+ *
472
+ * @example
473
+ * expect(mockFunc).toReturnWith('returnValue');
474
+ */
144
475
  toReturnWith: <E>(value: E) => void;
476
+ /**
477
+ * Use to ensure that a mock function returned a specific value.
478
+ *
479
+ * Also under the alias `expect.toReturnWith`.
480
+ *
481
+ * @example
482
+ * expect(mockFunc).toHaveReturnedWith('returnValue');
483
+ */
145
484
  toHaveReturnedWith: <E>(value: E) => void;
485
+ /**
486
+ * Use to test the specific value that a mock function last returned.
487
+ * If the last call to the mock function threw an error, then this matcher will fail
488
+ * no matter what value you provided as the expected return value.
489
+ *
490
+ * Also under the alias `expect.lastReturnedWith`.
491
+ *
492
+ * @example
493
+ * expect(mockFunc).toHaveLastReturnedWith('lastValue');
494
+ */
146
495
  toHaveLastReturnedWith: <E>(value: E) => void;
496
+ /**
497
+ * Use to test the specific value that a mock function last returned.
498
+ * If the last call to the mock function threw an error, then this matcher will fail
499
+ * no matter what value you provided as the expected return value.
500
+ *
501
+ * Alias for `expect.toHaveLastReturnedWith`.
502
+ *
503
+ * @example
504
+ * expect(mockFunc).lastReturnedWith('lastValue');
505
+ */
147
506
  lastReturnedWith: <E>(value: E) => void;
507
+ /**
508
+ * Use to test the specific value that a mock function returned for the nth call.
509
+ * If the nth call to the mock function threw an error, then this matcher will fail
510
+ * no matter what value you provided as the expected return value.
511
+ *
512
+ * Also under the alias `expect.nthReturnedWith`.
513
+ *
514
+ * @example
515
+ * expect(mockFunc).toHaveNthReturnedWith(2, 'nthValue');
516
+ */
148
517
  toHaveNthReturnedWith: <E>(nthCall: number, value: E) => void;
518
+ /**
519
+ * Use to test the specific value that a mock function returned for the nth call.
520
+ * If the nth call to the mock function threw an error, then this matcher will fail
521
+ * no matter what value you provided as the expected return value.
522
+ *
523
+ * Alias for `expect.toHaveNthReturnedWith`.
524
+ *
525
+ * @example
526
+ * expect(mockFunc).nthReturnedWith(2, 'nthValue');
527
+ */
149
528
  nthReturnedWith: <E>(nthCall: number, value: E) => void;
150
529
  }
151
530
  type VitestAssertion<A, T> = {
@@ -156,15 +535,117 @@ type Promisify<O> = {
156
535
  };
157
536
  type PromisifyAssertion<T> = Promisify<Assertion<T>>;
158
537
  interface Assertion<T = any> extends VitestAssertion<Chai.Assertion, T>, JestAssertion<T> {
538
+ /**
539
+ * Ensures a value is of a specific type.
540
+ *
541
+ * @example
542
+ * expect(value).toBeTypeOf('string');
543
+ * expect(number).toBeTypeOf('number');
544
+ */
159
545
  toBeTypeOf: (expected: 'bigint' | 'boolean' | 'function' | 'number' | 'object' | 'string' | 'symbol' | 'undefined') => void;
546
+ /**
547
+ * Asserts that a mock function was called exactly once.
548
+ *
549
+ * @example
550
+ * expect(mockFunc).toHaveBeenCalledOnce();
551
+ */
160
552
  toHaveBeenCalledOnce: () => void;
553
+ /**
554
+ * Ensure that a mock function is called with specific arguments and called
555
+ * exactly once.
556
+ *
557
+ * @example
558
+ * expect(mockFunc).toHaveBeenCalledExactlyOnceWith('arg1', 42);
559
+ */
560
+ toHaveBeenCalledExactlyOnceWith: <E extends any[]>(...args: E) => void;
561
+ /**
562
+ * Checks that a value satisfies a custom matcher function.
563
+ *
564
+ * @param matcher - A function returning a boolean based on the custom condition
565
+ * @param message - Optional custom error message on failure
566
+ *
567
+ * @example
568
+ * expect(age).toSatisfy(val => val >= 18, 'Age must be at least 18');
569
+ */
161
570
  toSatisfy: <E>(matcher: (value: E) => boolean, message?: string) => void;
571
+ /**
572
+ * This assertion checks if a `Mock` was called before another `Mock`.
573
+ * @param mock - A mock function created by `vi.spyOn` or `vi.fn`
574
+ * @param failIfNoFirstInvocation - Fail if the first mock was never called
575
+ * @example
576
+ * const mock1 = vi.fn()
577
+ * const mock2 = vi.fn()
578
+ *
579
+ * mock1()
580
+ * mock2()
581
+ * mock1()
582
+ *
583
+ * expect(mock1).toHaveBeenCalledBefore(mock2)
584
+ */
585
+ toHaveBeenCalledBefore: (mock: MockInstance, failIfNoFirstInvocation?: boolean) => void;
586
+ /**
587
+ * This assertion checks if a `Mock` was called after another `Mock`.
588
+ * @param mock - A mock function created by `vi.spyOn` or `vi.fn`
589
+ * @param failIfNoFirstInvocation - Fail if the first mock was never called
590
+ * @example
591
+ * const mock1 = vi.fn()
592
+ * const mock2 = vi.fn()
593
+ *
594
+ * mock2()
595
+ * mock1()
596
+ * mock2()
597
+ *
598
+ * expect(mock1).toHaveBeenCalledAfter(mock2)
599
+ */
600
+ toHaveBeenCalledAfter: (mock: MockInstance, failIfNoFirstInvocation?: boolean) => void;
601
+ /**
602
+ * Checks that a promise resolves successfully at least once.
603
+ *
604
+ * @example
605
+ * await expect(promise).toHaveResolved();
606
+ */
162
607
  toHaveResolved: () => void;
608
+ /**
609
+ * Checks that a promise resolves to a specific value.
610
+ *
611
+ * @example
612
+ * await expect(promise).toHaveResolvedWith('success');
613
+ */
163
614
  toHaveResolvedWith: <E>(value: E) => void;
615
+ /**
616
+ * Ensures a promise resolves a specific number of times.
617
+ *
618
+ * @example
619
+ * expect(mockAsyncFunc).toHaveResolvedTimes(3);
620
+ */
164
621
  toHaveResolvedTimes: (times: number) => void;
622
+ /**
623
+ * Asserts that the last resolved value of a promise matches an expected value.
624
+ *
625
+ * @example
626
+ * await expect(mockAsyncFunc).toHaveLastResolvedWith('finalResult');
627
+ */
165
628
  toHaveLastResolvedWith: <E>(value: E) => void;
629
+ /**
630
+ * Ensures a specific value was returned by a promise on the nth resolution.
631
+ *
632
+ * @example
633
+ * await expect(mockAsyncFunc).toHaveNthResolvedWith(2, 'secondResult');
634
+ */
166
635
  toHaveNthResolvedWith: <E>(nthCall: number, value: E) => void;
636
+ /**
637
+ * Verifies that a promise resolves.
638
+ *
639
+ * @example
640
+ * await expect(someAsyncFunc).resolves.toBe(42);
641
+ */
167
642
  resolves: PromisifyAssertion<T>;
643
+ /**
644
+ * Verifies that a promise rejects.
645
+ *
646
+ * @example
647
+ * await expect(someAsyncFunc).rejects.toThrow('error');
648
+ */
168
649
  rejects: PromisifyAssertion<T>;
169
650
  }
170
651
  declare global {
package/dist/index.js CHANGED
@@ -908,9 +908,20 @@ const JestAsymmetricMatchers = (chai, utils) => {
908
908
  };
909
909
  };
910
910
 
911
- function recordAsyncExpect(test, promise) {
911
+ function createAssertionMessage(util, assertion, hasArgs) {
912
+ const not = util.flag(assertion, "negate") ? "not." : "";
913
+ const name = `${util.flag(assertion, "_name")}(${hasArgs ? "expected" : ""})`;
914
+ const promiseName = util.flag(assertion, "promise");
915
+ const promise = promiseName ? `.${promiseName}` : "";
916
+ return `expect(actual)${promise}.${not}${name}`;
917
+ }
918
+ function recordAsyncExpect(_test, promise, assertion, error) {
919
+ const test = _test;
912
920
  if (test && promise instanceof Promise) {
913
921
  promise = promise.finally(() => {
922
+ if (!test.promises) {
923
+ return;
924
+ }
914
925
  const index = test.promises.indexOf(promise);
915
926
  if (index !== -1) {
916
927
  test.promises.splice(index, 1);
@@ -920,13 +931,43 @@ function recordAsyncExpect(test, promise) {
920
931
  test.promises = [];
921
932
  }
922
933
  test.promises.push(promise);
934
+ let resolved = false;
935
+ test.onFinished ?? (test.onFinished = []);
936
+ test.onFinished.push(() => {
937
+ var _a;
938
+ if (!resolved) {
939
+ const processor = ((_a = globalThis.__vitest_worker__) == null ? void 0 : _a.onFilterStackTrace) || ((s) => s || "");
940
+ const stack = processor(error.stack);
941
+ console.warn([
942
+ `Promise returned by \`${assertion}\` was not awaited. `,
943
+ "Vitest currently auto-awaits hanging assertions at the end of the test, but this will cause the test to fail in Vitest 3. ",
944
+ "Please remember to await the assertion.\n",
945
+ stack
946
+ ].join(""));
947
+ }
948
+ });
949
+ return {
950
+ then(onFullfilled, onRejected) {
951
+ resolved = true;
952
+ return promise.then(onFullfilled, onRejected);
953
+ },
954
+ catch(onRejected) {
955
+ return promise.catch(onRejected);
956
+ },
957
+ finally(onFinally) {
958
+ return promise.finally(onFinally);
959
+ },
960
+ [Symbol.toStringTag]: "Promise"
961
+ };
923
962
  }
924
963
  return promise;
925
964
  }
926
965
  function wrapAssertion(utils, name, fn) {
927
966
  return function(...args) {
928
967
  var _a;
929
- utils.flag(this, "_name", name);
968
+ if (name !== "withTest") {
969
+ utils.flag(this, "_name", name);
970
+ }
930
971
  if (!utils.flag(this, "soft")) {
931
972
  return fn.apply(this, args);
932
973
  }
@@ -1424,6 +1465,25 @@ const JestChaiExpect = (chai, utils) => {
1424
1465
  throw new AssertionError(formatCalls(spy, msg, args));
1425
1466
  }
1426
1467
  });
1468
+ def("toHaveBeenCalledExactlyOnceWith", function(...args) {
1469
+ const spy = getSpy(this);
1470
+ const spyName = spy.getMockName();
1471
+ const callCount = spy.mock.calls.length;
1472
+ const hasCallWithArgs = spy.mock.calls.some(
1473
+ (callArg) => equals(callArg, args, [...customTesters, iterableEquality])
1474
+ );
1475
+ const pass = hasCallWithArgs && callCount === 1;
1476
+ const isNot = utils.flag(this, "negate");
1477
+ const msg = utils.getMessage(this, [
1478
+ pass,
1479
+ `expected "${spyName}" to be called once with arguments: #{exp}`,
1480
+ `expected "${spyName}" to not be called once with arguments: #{exp}`,
1481
+ args
1482
+ ]);
1483
+ if (pass && isNot || !pass && !isNot) {
1484
+ throw new AssertionError(formatCalls(spy, msg, args));
1485
+ }
1486
+ });
1427
1487
  def(
1428
1488
  ["toHaveBeenNthCalledWith", "nthCalledWith"],
1429
1489
  function(times, ...args) {
@@ -1461,6 +1521,61 @@ const JestChaiExpect = (chai, utils) => {
1461
1521
  );
1462
1522
  }
1463
1523
  );
1524
+ function isSpyCalledBeforeAnotherSpy(beforeSpy, afterSpy, failIfNoFirstInvocation) {
1525
+ const beforeInvocationCallOrder = beforeSpy.mock.invocationCallOrder;
1526
+ const afterInvocationCallOrder = afterSpy.mock.invocationCallOrder;
1527
+ if (beforeInvocationCallOrder.length === 0) {
1528
+ return !failIfNoFirstInvocation;
1529
+ }
1530
+ if (afterInvocationCallOrder.length === 0) {
1531
+ return false;
1532
+ }
1533
+ return beforeInvocationCallOrder[0] < afterInvocationCallOrder[0];
1534
+ }
1535
+ def(
1536
+ ["toHaveBeenCalledBefore"],
1537
+ function(resultSpy, failIfNoFirstInvocation = true) {
1538
+ const expectSpy = getSpy(this);
1539
+ if (!isMockFunction(resultSpy)) {
1540
+ throw new TypeError(
1541
+ `${utils.inspect(resultSpy)} is not a spy or a call to a spy`
1542
+ );
1543
+ }
1544
+ this.assert(
1545
+ isSpyCalledBeforeAnotherSpy(
1546
+ expectSpy,
1547
+ resultSpy,
1548
+ failIfNoFirstInvocation
1549
+ ),
1550
+ `expected "${expectSpy.getMockName()}" to have been called before "${resultSpy.getMockName()}"`,
1551
+ `expected "${expectSpy.getMockName()}" to not have been called before "${resultSpy.getMockName()}"`,
1552
+ resultSpy,
1553
+ expectSpy
1554
+ );
1555
+ }
1556
+ );
1557
+ def(
1558
+ ["toHaveBeenCalledAfter"],
1559
+ function(resultSpy, failIfNoFirstInvocation = true) {
1560
+ const expectSpy = getSpy(this);
1561
+ if (!isMockFunction(resultSpy)) {
1562
+ throw new TypeError(
1563
+ `${utils.inspect(resultSpy)} is not a spy or a call to a spy`
1564
+ );
1565
+ }
1566
+ this.assert(
1567
+ isSpyCalledBeforeAnotherSpy(
1568
+ resultSpy,
1569
+ expectSpy,
1570
+ failIfNoFirstInvocation
1571
+ ),
1572
+ `expected "${expectSpy.getMockName()}" to have been called after "${resultSpy.getMockName()}"`,
1573
+ `expected "${expectSpy.getMockName()}" to not have been called after "${resultSpy.getMockName()}"`,
1574
+ resultSpy,
1575
+ expectSpy
1576
+ );
1577
+ }
1578
+ );
1464
1579
  def(
1465
1580
  ["toThrow", "toThrowError"],
1466
1581
  function(expected) {
@@ -1724,6 +1839,7 @@ const JestChaiExpect = (chai, utils) => {
1724
1839
  return result instanceof chai.Assertion ? proxy : result;
1725
1840
  }
1726
1841
  return (...args) => {
1842
+ utils.flag(this, "_name", key);
1727
1843
  const promise = obj.then(
1728
1844
  (value) => {
1729
1845
  utils.flag(this, "object", value);
@@ -1744,7 +1860,12 @@ const JestChaiExpect = (chai, utils) => {
1744
1860
  throw _error;
1745
1861
  }
1746
1862
  );
1747
- return recordAsyncExpect(test, promise);
1863
+ return recordAsyncExpect(
1864
+ test,
1865
+ promise,
1866
+ createAssertionMessage(utils, this, !!args.length),
1867
+ error
1868
+ );
1748
1869
  };
1749
1870
  }
1750
1871
  });
@@ -1778,6 +1899,7 @@ const JestChaiExpect = (chai, utils) => {
1778
1899
  return result instanceof chai.Assertion ? proxy : result;
1779
1900
  }
1780
1901
  return (...args) => {
1902
+ utils.flag(this, "_name", key);
1781
1903
  const promise = wrapper.then(
1782
1904
  (value) => {
1783
1905
  const _error = new AssertionError(
@@ -1801,7 +1923,12 @@ const JestChaiExpect = (chai, utils) => {
1801
1923
  return result.call(this, ...args);
1802
1924
  }
1803
1925
  );
1804
- return recordAsyncExpect(test, promise);
1926
+ return recordAsyncExpect(
1927
+ test,
1928
+ promise,
1929
+ createAssertionMessage(utils, this, !!args.length),
1930
+ error
1931
+ );
1805
1932
  };
1806
1933
  }
1807
1934
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vitest/expect",
3
3
  "type": "module",
4
- "version": "2.1.4",
4
+ "version": "2.2.0-beta.1",
5
5
  "description": "Jest's expect matchers as a Chai plugin",
6
6
  "license": "MIT",
7
7
  "funding": "https://opencollective.com/vitest",
@@ -32,13 +32,13 @@
32
32
  "dependencies": {
33
33
  "chai": "^5.1.2",
34
34
  "tinyrainbow": "^1.2.0",
35
- "@vitest/spy": "2.1.4",
36
- "@vitest/utils": "2.1.4"
35
+ "@vitest/spy": "2.2.0-beta.1",
36
+ "@vitest/utils": "2.2.0-beta.1"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/chai": "4.3.6",
40
40
  "rollup-plugin-copy": "^3.5.0",
41
- "@vitest/runner": "2.1.4"
41
+ "@vitest/runner": "2.2.0-beta.1"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "rimraf dist && rollup -c",