@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 +49 -7
- package/dist/index.js +90 -8
- package/package.json +4 -4
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<
|
150
|
-
|
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
|
-
|
153
|
-
|
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
|
-
|
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
|
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
|
37
|
-
"@vitest/utils": "3.2.0
|
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
|
41
|
+
"@vitest/runner": "3.2.0"
|
42
42
|
},
|
43
43
|
"scripts": {
|
44
44
|
"build": "rimraf dist && rollup -c",
|