@vitest/expect 3.2.0-beta.2 → 3.2.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/dist/index.d.ts CHANGED
@@ -9,6 +9,8 @@ declare const JEST_MATCHERS_OBJECT: unique symbol;
9
9
  declare const GLOBAL_EXPECT: unique symbol;
10
10
  declare const ASYMMETRIC_MATCHERS_OBJECT: unique symbol;
11
11
 
12
+ /* eslint-disable unicorn/no-instanceof-builtins -- we check both */
13
+
12
14
  interface AsymmetricMatcherInterface {
13
15
  asymmetricMatch: (other: unknown) => boolean;
14
16
  toString: () => string;
@@ -21,6 +23,7 @@ declare abstract class AsymmetricMatcher<
21
23
  > implements AsymmetricMatcherInterface {
22
24
  protected sample: T;
23
25
  protected inverse: boolean;
26
+ // should have "jest" to be compatible with its ecosystem
24
27
  $$typeof: symbol;
25
28
  constructor(sample: T, inverse?: boolean);
26
29
  protected getMatcherContext(expect?: Chai.ExpectStatic): State;
@@ -126,7 +129,9 @@ interface MatcherState {
126
129
  isExpectingAssertions?: boolean;
127
130
  isExpectingAssertionsError?: Error | null;
128
131
  isNot: boolean;
132
+ // environment: VitestEnvironment
129
133
  promise: string;
134
+ // snapshotState: SnapshotState
130
135
  suppressedErrors: Array<Error>;
131
136
  testPath?: string;
132
137
  utils: ReturnType<typeof getMatcherUtils> & {
@@ -146,11 +151,18 @@ interface SyncExpectationResult {
146
151
  }
147
152
  type AsyncExpectationResult = Promise<SyncExpectationResult>;
148
153
  type ExpectationResult = SyncExpectationResult | AsyncExpectationResult;
149
- interface RawMatcherFn<T extends MatcherState = MatcherState> {
150
- (this: T, received: any, ...expected: Array<any>): ExpectationResult;
154
+ interface RawMatcherFn<
155
+ T extends MatcherState = MatcherState,
156
+ E extends Array<any> = Array<any>
157
+ > {
158
+ (this: T, received: any, ...expected: E): ExpectationResult;
151
159
  }
152
- type MatchersObject<T extends MatcherState = MatcherState> = Record<string, RawMatcherFn<T>> & ThisType<T>;
153
- interface ExpectStatic extends Chai.ExpectStatic, AsymmetricMatchersContaining {
160
+ // Allow unused `T` to preserve its name for extensions.
161
+ // Type parameter names must be identical when extending those types.
162
+ // eslint-disable-next-line
163
+ interface Matchers<T = any> {}
164
+ type MatchersObject<T extends MatcherState = MatcherState> = Record<string, RawMatcherFn<T>> & ThisType<T> & { [K in keyof Matchers<T>]? : RawMatcherFn<T, Parameters<Matchers<T>[K]>> };
165
+ interface ExpectStatic extends Chai.ExpectStatic, Matchers, AsymmetricMatchersContaining {
154
166
  <T>(actual: T, message?: string): Assertion<T>;
155
167
  extend: (expects: MatchersObject) => void;
156
168
  anything: () => AsymmetricMatcher<unknown>;
@@ -618,7 +630,7 @@ type VitestAssertion<
618
630
  > = { [K in keyof A] : A[K] extends Chai.Assertion ? Assertion<T> : A[K] extends (...args: any[]) => any ? A[K] : VitestAssertion<A[K], T> } & ((type: string, message?: string) => Assertion);
619
631
  type Promisify<O> = { [K in keyof O] : O[K] extends (...args: infer A) => infer R ? Promisify<O[K]> & ((...args: A) => Promise<R>) : O[K] };
620
632
  type PromisifyAssertion<T> = Promisify<Assertion<T>>;
621
- interface Assertion<T = any> extends VitestAssertion<Chai.Assertion, T>, JestAssertion<T> {
633
+ interface Assertion<T = any> extends VitestAssertion<Chai.Assertion, T>, JestAssertion<T>, Matchers<T> {
622
634
  /**
623
635
  * Ensures a value is of a specific type.
624
636
  *
@@ -723,20 +735,50 @@ interface Assertion<T = any> extends VitestAssertion<Chai.Assertion, T>, JestAss
723
735
  rejects: PromisifyAssertion<T>;
724
736
  }
725
737
  declare global {
738
+ // support augmenting jest.Matchers by other libraries
739
+ // eslint-disable-next-line ts/no-namespace
726
740
  namespace jest {
727
- interface Matchers<
741
+ // eslint-disable-next-line unused-imports/no-unused-vars, ts/no-empty-object-type
742
+ interface Matchers<
728
743
  R,
729
744
  T = {}
730
745
  > {}
731
746
  }
732
747
  }
733
748
 
749
+ // selectively ported from https://github.com/jest-community/jest-extended
734
750
  declare const customMatchers: MatchersObject;
735
751
 
752
+ // Jest Expect Compact
736
753
  declare const JestChaiExpect: ChaiPlugin;
737
754
 
738
755
  declare const JestExtend: ChaiPlugin;
739
756
 
757
+ /*
758
+ Copyright (c) 2008-2016 Pivotal Labs
759
+
760
+ Permission is hereby granted, free of charge, to any person obtaining
761
+ a copy of this software and associated documentation files (the
762
+ "Software"), to deal in the Software without restriction, including
763
+ without limitation the rights to use, copy, modify, merge, publish,
764
+ distribute, sublicense, and/or sell copies of the Software, and to
765
+ permit persons to whom the Software is furnished to do so, subject to
766
+ the following conditions:
767
+
768
+ The above copyright notice and this permission notice shall be
769
+ included in all copies or substantial portions of the Software.
770
+
771
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
772
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
773
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
774
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
775
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
776
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
777
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
778
+
779
+ */
780
+
781
+ // Extracted out of jasmine 2.5.2
740
782
  declare function equals(a: unknown, b: unknown, customTesters?: Array<Tester>, strictCheck?: boolean): boolean;
741
783
  declare function isAsymmetric(obj: any): boolean;
742
784
  declare function hasAsymmetric(obj: any, seen?: Set<any>): boolean;
@@ -762,4 +804,4 @@ declare function getState<State extends MatcherState = MatcherState>(expect: Exp
762
804
  declare function setState<State extends MatcherState = MatcherState>(state: Partial<State>, expect: ExpectStatic): void;
763
805
 
764
806
  export { ASYMMETRIC_MATCHERS_OBJECT, Any, Anything, ArrayContaining, AsymmetricMatcher, GLOBAL_EXPECT, JEST_MATCHERS_OBJECT, JestAsymmetricMatchers, JestChaiExpect, JestExtend, MATCHERS_OBJECT, ObjectContaining, StringContaining, StringMatching, addCustomEqualityTesters, arrayBufferEquality, customMatchers, equals, fnNameFor, generateToBeMessage, getObjectKeys, getObjectSubset, getState, hasAsymmetric, hasProperty, isA, isAsymmetric, isImmutableUnorderedKeyed, isImmutableUnorderedSet, iterableEquality, pluralize, setState, sparseArrayEquality, subsetEquality, typeEquality };
765
- export type { Assertion, AsymmetricMatcherInterface, AsymmetricMatchersContaining, AsyncExpectationResult, ChaiPlugin, DeeplyAllowMatchers, ExpectStatic, ExpectationResult, JestAssertion, MatcherHintOptions, MatcherState, MatchersObject, PromisifyAssertion, RawMatcherFn, SyncExpectationResult, Tester, TesterContext };
807
+ export type { Assertion, AsymmetricMatcherInterface, AsymmetricMatchersContaining, AsyncExpectationResult, ChaiPlugin, DeeplyAllowMatchers, ExpectStatic, ExpectationResult, JestAssertion, MatcherHintOptions, MatcherState, Matchers, MatchersObject, PromisifyAssertion, RawMatcherFn, SyncExpectationResult, Tester, TesterContext };
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ const JEST_MATCHERS_OBJECT = Symbol.for("$$jest-matchers-object");
10
10
  const GLOBAL_EXPECT = Symbol.for("expect-global");
11
11
  const ASYMMETRIC_MATCHERS_OBJECT = Symbol.for("asymmetric-matchers-object");
12
12
 
13
+ // selectively ported from https://github.com/jest-community/jest-extended
13
14
  const customMatchers = {
14
15
  toSatisfy(actual, expected, message) {
15
16
  const { printReceived, printExpected, matcherHint } = this.utils;
@@ -81,8 +82,11 @@ function matcherHint(matcherName, received = "received", expected = "expected",
81
82
  dimString = "";
82
83
  }
83
84
  if (matcherName.includes(".")) {
85
+ // Old format: for backward compatibility,
86
+ // especially without promise or isNot options
84
87
  dimString += matcherName;
85
88
  } else {
89
+ // New format: omit period from matcherName arg
86
90
  hint += DIM_COLOR(`${dimString}.`) + matcherName;
87
91
  dimString = "";
88
92
  }
@@ -104,6 +108,8 @@ function matcherHint(matcherName, received = "received", expected = "expected",
104
108
  return hint;
105
109
  }
106
110
  const SPACE_SYMBOL = "·";
111
+ // Instead of inverse highlight which now implies a change,
112
+ // replace common spaces with middle dot at the end of any line.
107
113
  function replaceTrailingSpaces(text) {
108
114
  return text.replace(/\s+$/gm, (spaces) => SPACE_SYMBOL.repeat(spaces.length));
109
115
  }
@@ -144,6 +150,7 @@ function getCustomEqualityTesters() {
144
150
  return globalThis[JEST_MATCHERS_OBJECT].customEqualityTesters;
145
151
  }
146
152
 
153
+ // Extracted out of jasmine 2.5.2
147
154
  function equals(a, b, customTesters, strictCheck) {
148
155
  customTesters = customTesters || [];
149
156
  return eq(a, b, [], [], customTesters, strictCheck ? hasKey : hasDefinedKey);
@@ -184,6 +191,8 @@ function asymmetricMatch(a, b) {
184
191
  return b.asymmetricMatch(a);
185
192
  }
186
193
  }
194
+ // Equality function lovingly adapted from isEqual in
195
+ // [Underscore](http://underscorejs.org)
187
196
  function eq(a, b, aStack, bStack, customTesters, hasKey) {
188
197
  let result = true;
189
198
  const asymmetricResult = asymmetricMatch(a, b);
@@ -203,6 +212,7 @@ function eq(a, b, aStack, bStack, customTesters, hasKey) {
203
212
  if (Object.is(a, b)) {
204
213
  return true;
205
214
  }
215
+ // A strict comparison is necessary because `null == undefined`.
206
216
  if (a === null || b === null) {
207
217
  return a === b;
208
218
  }
@@ -214,35 +224,58 @@ function eq(a, b, aStack, bStack, customTesters, hasKey) {
214
224
  case "[object Boolean]":
215
225
  case "[object String]":
216
226
  case "[object Number]": if (typeof a !== typeof b) {
227
+ // One is a primitive, one a `new Primitive()`
217
228
  return false;
218
229
  } else if (typeof a !== "object" && typeof b !== "object") {
230
+ // both are proper primitives
219
231
  return Object.is(a, b);
220
232
  } else {
233
+ // both are `new Primitive()`s
221
234
  return Object.is(a.valueOf(), b.valueOf());
222
235
  }
223
236
  case "[object Date]": {
224
237
  const numA = +a;
225
238
  const numB = +b;
239
+ // Coerce dates to numeric primitive values. Dates are compared by their
240
+ // millisecond representations. Note that invalid dates with millisecond representations
241
+ // of `NaN` are equivalent.
226
242
  return numA === numB || Number.isNaN(numA) && Number.isNaN(numB);
227
243
  }
228
244
  case "[object RegExp]": return a.source === b.source && a.flags === b.flags;
245
+ case "[object Temporal.Instant]":
246
+ case "[object Temporal.ZonedDateTime]":
247
+ case "[object Temporal.PlainDateTime]":
248
+ case "[object Temporal.PlainDate]":
249
+ case "[object Temporal.PlainTime]":
250
+ case "[object Temporal.PlainYearMonth]":
251
+ case "[object Temporal.PlainMonthDay]": return a.equals(b);
252
+ case "[object Temporal.Duration]": return a.toString() === b.toString();
229
253
  }
230
254
  if (typeof a !== "object" || typeof b !== "object") {
231
255
  return false;
232
256
  }
257
+ // Use DOM3 method isEqualNode (IE>=9)
233
258
  if (isDomNode(a) && isDomNode(b)) {
234
259
  return a.isEqualNode(b);
235
260
  }
261
+ // Used to detect circular references.
236
262
  let length = aStack.length;
237
263
  while (length--) {
264
+ // Linear search. Performance is inversely proportional to the number of
265
+ // unique nested structures.
266
+ // circular references at same depth are equal
267
+ // circular reference is not equal to non-circular one
238
268
  if (aStack[length] === a) {
239
269
  return bStack[length] === b;
240
270
  } else if (bStack[length] === b) {
241
271
  return false;
242
272
  }
243
273
  }
274
+ // Add the first object to the stack of traversed objects.
244
275
  aStack.push(a);
245
276
  bStack.push(b);
277
+ // Recursively compare objects and arrays.
278
+ // Compare array lengths to determine if a deep comparison is necessary.
246
279
  if (className === "[object Array]" && a.length !== b.length) {
247
280
  return false;
248
281
  }
@@ -254,31 +287,42 @@ function eq(a, b, aStack, bStack, customTesters, hasKey) {
254
287
  bStack.pop();
255
288
  }
256
289
  }
290
+ // Deep compare objects.
257
291
  const aKeys = keys(a, hasKey);
258
292
  let key;
259
293
  let size = aKeys.length;
294
+ // Ensure that both objects contain the same number of properties before comparing deep equality.
260
295
  if (keys(b, hasKey).length !== size) {
261
296
  return false;
262
297
  }
263
298
  while (size--) {
264
299
  key = aKeys[size];
300
+ // Deep compare each member
265
301
  result = hasKey(b, key) && eq(a[key], b[key], aStack, bStack, customTesters, hasKey);
266
302
  if (!result) {
267
303
  return false;
268
304
  }
269
305
  }
306
+ // Remove the first object from the stack of traversed objects.
270
307
  aStack.pop();
271
308
  bStack.pop();
272
309
  return result;
273
310
  }
274
311
  function isErrorEqual(a, b, aStack, bStack, customTesters, hasKey) {
312
+ // https://nodejs.org/docs/latest-v22.x/api/assert.html#comparison-details
313
+ // - [[Prototype]] of objects are compared using the === operator.
314
+ // - Only enumerable "own" properties are considered.
315
+ // - Error names, messages, causes, and errors are always compared, even if these are not enumerable properties. errors is also compared.
275
316
  let result = Object.getPrototypeOf(a) === Object.getPrototypeOf(b) && a.name === b.name && a.message === b.message;
317
+ // check Error.cause asymmetrically
276
318
  if (typeof b.cause !== "undefined") {
277
319
  result && (result = eq(a.cause, b.cause, aStack, bStack, customTesters, hasKey));
278
320
  }
321
+ // AggregateError.errors
279
322
  if (a instanceof AggregateError && b instanceof AggregateError) {
280
323
  result && (result = eq(a.errors, b.errors, aStack, bStack, customTesters, hasKey));
281
324
  }
325
+ // spread to compare enumerable properties
282
326
  result && (result = eq({ ...a }, { ...b }, aStack, bStack, customTesters, hasKey));
283
327
  return result;
284
328
  }
@@ -328,6 +372,7 @@ function hasProperty(obj, property) {
328
372
  }
329
373
  return hasProperty(getPrototype(obj), property);
330
374
  }
375
+ // SENTINEL constants are from https://github.com/facebook/immutable-js
331
376
  const IS_KEYED_SENTINEL = "@@__IMMUTABLE_KEYED__@@";
332
377
  const IS_SET_SENTINEL = "@@__IMMUTABLE_SET__@@";
333
378
  const IS_LIST_SENTINEL = "@@__IMMUTABLE_LIST__@@";
@@ -374,6 +419,10 @@ function iterableEquality(a, b, customTesters = [], aStack = [], bStack = []) {
374
419
  }
375
420
  let length = aStack.length;
376
421
  while (length--) {
422
+ // Linear search. Performance is inversely proportional to the number of
423
+ // unique nested structures.
424
+ // circular references at same depth are equal
425
+ // circular reference is not equal to non-circular one
377
426
  if (aStack[length] === a) {
378
427
  return bStack[length] === b;
379
428
  }
@@ -404,6 +453,7 @@ function iterableEquality(a, b, customTesters = [], aStack = [], bStack = []) {
404
453
  }
405
454
  }
406
455
  }
456
+ // Remove the first value from the stack of traversed values.
407
457
  aStack.pop();
408
458
  bStack.pop();
409
459
  return allFound;
@@ -428,6 +478,7 @@ function iterableEquality(a, b, customTesters = [], aStack = [], bStack = []) {
428
478
  }
429
479
  }
430
480
  }
481
+ // Remove the first value from the stack of traversed values.
431
482
  aStack.pop();
432
483
  bStack.pop();
433
484
  return allFound;
@@ -450,6 +501,7 @@ function iterableEquality(a, b, customTesters = [], aStack = [], bStack = []) {
450
501
  return false;
451
502
  }
452
503
  }
504
+ // Remove the first value from the stack of traversed values.
453
505
  aStack.pop();
454
506
  bStack.pop();
455
507
  return true;
@@ -469,6 +521,9 @@ function isObjectWithKeys(a) {
469
521
  }
470
522
  function subsetEquality(object, subset, customTesters = []) {
471
523
  const filteredCustomTesters = customTesters.filter((t) => t !== subsetEquality);
524
+ // subsetEquality needs to keep track of the references
525
+ // it has already visited to avoid infinite loops in case
526
+ // there are circular references in the subset passed to it.
472
527
  const subsetEqualityWithContext = (seenReferences = new WeakMap()) => (object, subset) => {
473
528
  if (!isObjectWithKeys(subset)) {
474
529
  return undefined;
@@ -481,6 +536,11 @@ function subsetEquality(object, subset, customTesters = []) {
481
536
  seenReferences.set(subset[key], true);
482
537
  }
483
538
  const result = object != null && hasPropertyInObject(object, key) && equals(object[key], subset[key], [...filteredCustomTesters, subsetEqualityWithContext(seenReferences)]);
539
+ // The main goal of using seenReference is to avoid circular node on tree.
540
+ // It will only happen within a parent and its child, not a node and nodes next to it (same level)
541
+ // We should keep the reference for a parent and its child only
542
+ // Thus we should delete the reference immediately so that it doesn't interfere
543
+ // other nodes within the same level on tree.
484
544
  seenReferences.delete(subset[key]);
485
545
  return result;
486
546
  });
@@ -507,9 +567,11 @@ function arrayBufferEquality(a, b) {
507
567
  return undefined;
508
568
  }
509
569
  }
570
+ // Buffers are not equal when they do not have the same byte length
510
571
  if (dataViewA.byteLength !== dataViewB.byteLength) {
511
572
  return false;
512
573
  }
574
+ // Check if every byte value is equal to each other
513
575
  for (let i = 0; i < dataViewA.byteLength; i++) {
514
576
  if (dataViewA.getUint8(i) !== dataViewB.getUint8(i)) {
515
577
  return false;
@@ -521,6 +583,7 @@ function sparseArrayEquality(a, b, customTesters = []) {
521
583
  if (!Array.isArray(a) || !Array.isArray(b)) {
522
584
  return undefined;
523
585
  }
586
+ // A sparse array [, , 1] will have keys ["2"] whereas [undefined, undefined, 1] will have keys ["0", "1", "2"]
524
587
  const aKeys = Object.keys(a);
525
588
  const bKeys = Object.keys(b);
526
589
  const filteredCustomTesters = customTesters.filter((t) => t !== sparseArrayEquality);
@@ -547,6 +610,7 @@ function getObjectSubset(object, subset, customTesters) {
547
610
  const getObjectSubsetWithContext = (seenReferences = new WeakMap()) => (object, subset) => {
548
611
  if (Array.isArray(object)) {
549
612
  if (Array.isArray(subset) && subset.length === object.length) {
613
+ // The map method returns correct subclass of subset.
550
614
  return subset.map((sub, i) => getObjectSubsetWithContext(seenReferences)(object[i], sub));
551
615
  }
552
616
  } else if (object instanceof Date) {
@@ -557,10 +621,12 @@ function getObjectSubset(object, subset, customTesters) {
557
621
  iterableEquality,
558
622
  subsetEquality
559
623
  ])) {
624
+ // return "expected" subset to avoid showing irrelevant toMatchObject diff
560
625
  return subset;
561
626
  }
562
627
  const trimmed = {};
563
628
  seenReferences.set(object, trimmed);
629
+ // preserve constructor for toMatchObject diff
564
630
  if (typeof object.constructor === "function" && typeof object.constructor.name === "string") {
565
631
  Object.defineProperty(trimmed, "constructor", {
566
632
  enumerable: false,
@@ -614,6 +680,7 @@ function getState(expect) {
614
680
  function setState(state, expect) {
615
681
  const map = globalThis[MATCHERS_OBJECT];
616
682
  const current = map.get(expect) || {};
683
+ // so it keeps getters from `testPath`
617
684
  const results = Object.defineProperties(current, {
618
685
  ...Object.getOwnPropertyDescriptors(current),
619
686
  ...Object.getOwnPropertyDescriptors(state)
@@ -622,6 +689,7 @@ function setState(state, expect) {
622
689
  }
623
690
 
624
691
  class AsymmetricMatcher {
692
+ // should have "jest" to be compatible with its ecosystem
625
693
  $$typeof = Symbol.for("jest.asymmetricMatcher");
626
694
  constructor(sample, inverse = false) {
627
695
  this.sample = sample;
@@ -643,7 +711,12 @@ class AsymmetricMatcher {
643
711
  };
644
712
  }
645
713
  }
714
+ // implement custom chai/loupe inspect for better AssertionError.message formatting
715
+ // https://github.com/chaijs/loupe/blob/9b8a6deabcd50adc056a64fb705896194710c5c6/src/index.ts#L29
716
+ // @ts-expect-error computed properties is not supported when isolatedDeclarations is enabled
717
+ // FIXME: https://github.com/microsoft/TypeScript/issues/61068
646
718
  AsymmetricMatcher.prototype[Symbol.for("chai/inspect")] = function(options) {
719
+ // minimal pretty-format with simple manual truncation
647
720
  const result = stringify(this, options.depth, { min: true });
648
721
  if (result.length <= options.truncate) {
649
722
  return result;
@@ -872,6 +945,7 @@ const JestAsymmetricMatchers = (chai, utils) => {
872
945
  utils.addMethod(chai.expect, "arrayContaining", (expected) => new ArrayContaining(expected));
873
946
  utils.addMethod(chai.expect, "stringMatching", (expected) => new StringMatching(expected));
874
947
  utils.addMethod(chai.expect, "closeTo", (expected, precision) => new CloseTo(expected, precision));
948
+ // defineProperty does not work
875
949
  chai.expect.not = {
876
950
  stringContaining: (expected) => new StringContaining(expected, true),
877
951
  objectContaining: (expected) => new ObjectContaining(expected, true),
@@ -890,7 +964,9 @@ function createAssertionMessage(util, assertion, hasArgs) {
890
964
  }
891
965
  function recordAsyncExpect(_test, promise, assertion, error) {
892
966
  const test = _test;
967
+ // record promise for test, that resolves before test ends
893
968
  if (test && promise instanceof Promise) {
969
+ // if promise is explicitly awaited, remove it from the list
894
970
  promise = promise.finally(() => {
895
971
  if (!test.promises) {
896
972
  return;
@@ -900,6 +976,7 @@ function recordAsyncExpect(_test, promise, assertion, error) {
900
976
  test.promises.splice(index, 1);
901
977
  }
902
978
  });
979
+ // record promise
903
980
  if (!test.promises) {
904
981
  test.promises = [];
905
982
  }
@@ -937,6 +1014,7 @@ function recordAsyncExpect(_test, promise, assertion, error) {
937
1014
  }
938
1015
  function wrapAssertion(utils, name, fn) {
939
1016
  return function(...args) {
1017
+ // private
940
1018
  if (name !== "withTest") {
941
1019
  utils.flag(this, "_name", name);
942
1020
  }
@@ -959,6 +1037,7 @@ function wrapAssertion(utils, name, fn) {
959
1037
  };
960
1038
  }
961
1039
 
1040
+ // Jest Expect Compact
962
1041
  const JestChaiExpect = (chai, utils) => {
963
1042
  const { AssertionError } = chai;
964
1043
  const customTesters = getCustomEqualityTesters();
@@ -1001,6 +1080,7 @@ const JestChaiExpect = (chai, utils) => {
1001
1080
  };
1002
1081
  });
1003
1082
  });
1083
+ // @ts-expect-error @internal
1004
1084
  def("withTest", function(test) {
1005
1085
  utils.flag(this, "vitest-test", test);
1006
1086
  return this;
@@ -1091,9 +1171,11 @@ const JestChaiExpect = (chai, utils) => {
1091
1171
  const expectedClassList = isNot ? actual.value.replace(item, "").trim() : `${actual.value} ${item}`;
1092
1172
  return this.assert(actual.contains(item), `expected "${actual.value}" to contain "${item}"`, `expected "${actual.value}" not to contain "${item}"`, expectedClassList, actual.value);
1093
1173
  }
1174
+ // handle simple case on our own using `this.assert` to include diff in error message
1094
1175
  if (typeof actual === "string" && typeof item === "string") {
1095
1176
  return this.assert(actual.includes(item), `expected #{this} to contain #{exp}`, `expected #{this} not to contain #{exp}`, item, actual);
1096
1177
  }
1178
+ // make "actual" indexable to have compatibility with jest
1097
1179
  if (actual != null && typeof actual !== "string") {
1098
1180
  utils.flag(this, "object", Array.from(actual));
1099
1181
  }
@@ -1165,6 +1247,7 @@ const JestChaiExpect = (chai, utils) => {
1165
1247
  def("toHaveLength", function(length) {
1166
1248
  return this.have.length(length);
1167
1249
  });
1250
+ // destructuring, because it checks `arguments` inside, and value is passing as `undefined`
1168
1251
  def("toHaveProperty", function(...args) {
1169
1252
  if (Array.isArray(args[0])) {
1170
1253
  args[0] = args[0].map((key) => String(key).replace(/([.[\]])/g, "\\$1")).join(".");
@@ -1243,6 +1326,8 @@ const JestChaiExpect = (chai, utils) => {
1243
1326
  throw new AssertionError(msg);
1244
1327
  }
1245
1328
  });
1329
+ // manually compare array elements since `jestEquals` cannot
1330
+ // apply asymmetric matcher to `undefined` array element.
1246
1331
  function equalsArgumentArray(a, b) {
1247
1332
  return a.length === b.length && a.every((aItem, i) => equals(aItem, b[i], [...customTesters, iterableEquality]));
1248
1333
  }
@@ -1322,6 +1407,7 @@ const JestChaiExpect = (chai, utils) => {
1322
1407
  });
1323
1408
  def(["toThrow", "toThrowError"], function(expected) {
1324
1409
  if (typeof expected === "string" || typeof expected === "undefined" || expected instanceof RegExp) {
1410
+ // Fixes the issue related to `chai` <https://github.com/vitest-dev/vitest/issues/6618>
1325
1411
  return this.throws(expected === "" ? /^$/ : expected);
1326
1412
  }
1327
1413
  const obj = this._obj;
@@ -1444,14 +1530,7 @@ const JestChaiExpect = (chai, utils) => {
1444
1530
  const results = action === "return" ? spy.mock.results : spy.mock.settledResults;
1445
1531
  const result = results[results.length - 1];
1446
1532
  const spyName = spy.getMockName();
1447
- this.assert(
1448
- condition(spy, value),
1449
- `expected last "${spyName}" call to ${action} #{exp}`,
1450
- `expected last "${spyName}" call to not ${action} #{exp}`,
1451
- value,
1452
- // for jest compat
1453
- result === null || result === void 0 ? void 0 : result.value
1454
- );
1533
+ this.assert(condition(spy, value), `expected last "${spyName}" call to ${action} #{exp}`, `expected last "${spyName}" call to not ${action} #{exp}`, value, result === null || result === void 0 ? void 0 : result.value);
1455
1534
  });
1456
1535
  });
1457
1536
  [{
@@ -1478,6 +1557,7 @@ const JestChaiExpect = (chai, utils) => {
1478
1557
  this.assert(condition(spy, nthCall, value), `expected ${ordinalCall} "${spyName}" call to ${action} #{exp}`, `expected ${ordinalCall} "${spyName}" call to not ${action} #{exp}`, value, result === null || result === void 0 ? void 0 : result.value);
1479
1558
  });
1480
1559
  });
1560
+ // @ts-expect-error @internal
1481
1561
  def("withContext", function(context) {
1482
1562
  for (const key in context) {
1483
1563
  utils.flag(this, key, context[key]);
@@ -1689,6 +1769,8 @@ function JestExtendPlugin(c, expect, matchers) {
1689
1769
  value: (...sample) => new CustomMatcher(true, ...sample),
1690
1770
  writable: true
1691
1771
  });
1772
+ // keep track of asymmetric matchers on global so that it can be copied over to local context's `expect`.
1773
+ // note that the negated variant is automatically shared since it's assigned on the single `expect.not` object.
1692
1774
  Object.defineProperty(globalThis[ASYMMETRIC_MATCHERS_OBJECT], expectAssertionName, {
1693
1775
  configurable: true,
1694
1776
  enumerable: true,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vitest/expect",
3
3
  "type": "module",
4
- "version": "3.2.0-beta.2",
4
+ "version": "3.2.0",
5
5
  "description": "Jest's expect matchers as a Chai plugin",
6
6
  "license": "MIT",
7
7
  "funding": "https://opencollective.com/vitest",
@@ -33,12 +33,12 @@
33
33
  "@types/chai": "^5.2.2",
34
34
  "chai": "^5.2.0",
35
35
  "tinyrainbow": "^2.0.0",
36
- "@vitest/spy": "3.2.0-beta.2",
37
- "@vitest/utils": "3.2.0-beta.2"
36
+ "@vitest/spy": "3.2.0",
37
+ "@vitest/utils": "3.2.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "rollup-plugin-copy": "^3.5.0",
41
- "@vitest/runner": "3.2.0-beta.2"
41
+ "@vitest/runner": "3.2.0"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "rimraf dist && rollup -c",