diff --git a/packages/expect/src/jest-asymmetric-matchers.ts b/packages/expect/src/jest-asymmetric-matchers.ts index 3bae88e1bfb3..38a539372c9b 100644 --- a/packages/expect/src/jest-asymmetric-matchers.ts +++ b/packages/expect/src/jest-asymmetric-matchers.ts @@ -1,6 +1,6 @@ /* eslint-disable unicorn/no-instanceof-builtins -- we check both */ -import type { ChaiPlugin, MatcherState } from './types' +import type { ChaiPlugin, MatcherState, Tester } from './types' import { GLOBAL_EXPECT } from './constants' import { diff, @@ -19,7 +19,7 @@ import { import { getState } from './state' export interface AsymmetricMatcherInterface { - asymmetricMatch: (other: unknown) => boolean + asymmetricMatch: (other: unknown, customTesters?: Array) => boolean toString: () => string getExpectedType?: () => string toAsymmetricMatcher?: () => string @@ -50,7 +50,7 @@ export abstract class AsymmetricMatcher< } } - abstract asymmetricMatch(other: unknown): boolean + abstract asymmetricMatch(other: unknown, customTesters?: Array): boolean abstract toString(): string getExpectedType?(): string toAsymmetricMatcher?(): string @@ -147,7 +147,7 @@ export class ObjectContaining extends AsymmetricMatcher< ] } - asymmetricMatch(other: any): boolean { + asymmetricMatch(other: any, customTesters?: Array): boolean { if (typeof this.sample !== 'object') { throw new TypeError( `You must provide an object to ${this.toString()}, not '${typeof this @@ -157,7 +157,6 @@ export class ObjectContaining extends AsymmetricMatcher< let result = true - const matcherContext = this.getMatcherContext() const properties = this.getProperties(this.sample) for (const property of properties) { if ( @@ -171,7 +170,7 @@ export class ObjectContaining extends AsymmetricMatcher< if (!equals( value, otherValue, - matcherContext.customTesters, + customTesters, ) ) { result = false @@ -196,7 +195,7 @@ export class ArrayContaining extends AsymmetricMatcher> { super(sample, inverse) } - asymmetricMatch(other: Array): boolean { + asymmetricMatch(other: Array, customTesters?: Array): boolean { if (!Array.isArray(this.sample)) { throw new TypeError( `You must provide an array to ${this.toString()}, not '${typeof this @@ -204,13 +203,12 @@ export class ArrayContaining extends AsymmetricMatcher> { ) } - const matcherContext = this.getMatcherContext() const result = this.sample.length === 0 || (Array.isArray(other) && this.sample.every(item => other.some(another => - equals(item, another, matcherContext.customTesters), + equals(item, another, customTesters), ), )) diff --git a/packages/expect/src/jest-utils.ts b/packages/expect/src/jest-utils.ts index de7eb3c0f741..f0b8cb8d3630 100644 --- a/packages/expect/src/jest-utils.ts +++ b/packages/expect/src/jest-utils.ts @@ -22,6 +22,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +import type { AsymmetricMatcher } from './jest-asymmetric-matchers' import type { Tester, TesterContext } from './types' import { isObject } from '@vitest/utils' @@ -38,7 +39,7 @@ export function equals( const functionToString = Function.prototype.toString -export function isAsymmetric(obj: any): boolean { +export function isAsymmetric(obj: any): obj is AsymmetricMatcher { return ( !!obj && typeof obj === 'object' @@ -67,7 +68,7 @@ export function hasAsymmetric(obj: any, seen: Set = new Set()): boolean { return false } -function asymmetricMatch(a: any, b: any) { +function asymmetricMatch(a: any, b: any, customTesters: Array) { const asymmetricA = isAsymmetric(a) const asymmetricB = isAsymmetric(b) @@ -76,11 +77,11 @@ function asymmetricMatch(a: any, b: any) { } if (asymmetricA) { - return a.asymmetricMatch(b) + return a.asymmetricMatch(b, customTesters) } if (asymmetricB) { - return b.asymmetricMatch(a) + return b.asymmetricMatch(a, customTesters) } } @@ -96,7 +97,7 @@ function eq( ): boolean { let result = true - const asymmetricResult = asymmetricMatch(a, b) + const asymmetricResult = asymmetricMatch(a, b, customTesters) if (asymmetricResult !== undefined) { return asymmetricResult } diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index be8ec92598e5..dba00dd2775b 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -203,6 +203,36 @@ describe('jest-expect', () => { }).toThrowErrorMatchingInlineSnapshot(`[AssertionError: expected { sum: 0.30000000000000004 } to deeply equal { sum: NumberCloseTo 0.4 (2 digits) }]`) }) + it('asymmetric matchers and equality testers', () => { + // iterable equality testers + expect([new Set(['x'])]).toEqual( + expect.arrayContaining([new Set(['x'])]), + ) + expect([new Set()]).not.toEqual( + expect.arrayContaining([new Set(['x'])]), + ) + expect({ foo: new Set(['x']) }).toEqual( + expect.objectContaining({ foo: new Set(['x']) }), + ) + expect({ foo: new Set() }).not.toEqual( + expect.objectContaining({ foo: new Set(['x']) }), + ) + + // `toStrictEqual` testers + class Stock { + constructor(public type: string) {} + } + expect([new Stock('x')]).toEqual( + expect.arrayContaining([{ type: 'x' }]), + ) + expect([new Stock('x')]).not.toStrictEqual( + expect.arrayContaining([{ type: 'x' }]), + ) + expect([new Stock('x')]).toStrictEqual( + expect.arrayContaining([new Stock('x')]), + ) + }) + it('asymmetric matchers negate', () => { expect('bar').toEqual(expect.not.stringContaining('zoo')) expect('bar').toEqual(expect.not.stringMatching(/zoo/))