diff --git a/docs/guide/extending-matchers.md b/docs/guide/extending-matchers.md index 55440cfc3..3756b6ec5 100644 --- a/docs/guide/extending-matchers.md +++ b/docs/guide/extending-matchers.md @@ -150,6 +150,24 @@ If matcher was called on `resolved/rejected`, this value will contain the name o This is a utility function that allows you to compare two values. It will return `true` if values are equal, `false` otherwise. This function is used internally for almost every matcher. It supports objects with asymmetric matchers by default. +## `customTesters` + +Equality testers to pass to `equals`, including ones registered with [`expect.addEqualityTesters`](/api/expect#expect-addequalitytesters). + +`equals` alone does not compare the contents of `Map` and `Set`. To get the same equality as `toEqual`, add `this.utils.iterableEquality`: + +```ts +expect.extend({ + toMyEqual(received: unknown, expected: unknown) { + const pass = this.equals(received, expected, [...this.customTesters, this.utils.iterableEquality]) + return { + pass, + message: () => `expected ${this.utils.printReceived(received)} to equal ${this.utils.printExpected(expected)}`, + } + }, +}) +``` + ## `utils` This contains a set of utility functions that you can use to display messages. diff --git a/packages/expect/src/jest-extend.ts b/packages/expect/src/jest-extend.ts index 2ccd3be13..45d7f7ce9 100644 --- a/packages/expect/src/jest-extend.ts +++ b/packages/expect/src/jest-extend.ts @@ -6,6 +6,7 @@ import type { MatchersObject, MatcherState, SyncExpectationResult, + Tester, } from './types' import { use, util } from 'chai' import { ASYMMETRIC_MATCHERS_OBJECT, JEST_MATCHERS_OBJECT } from './constants' @@ -134,9 +135,10 @@ function JestExtendPlugin( super(sample, inverse) } - asymmetricMatch(other: unknown) { + asymmetricMatch(other: unknown, customTesters?: Array) { + const context = this.getMatcherContext(expect) const { pass } = expectAssertion.call( - this.getMatcherContext(expect), + customTesters ? { ...context, customTesters } : context, other, ...this.sample, ) as SyncExpectationResult diff --git a/test/unit/test/jest-expect.test.ts b/test/unit/test/jest-expect.test.ts index 5ef02a6e7..9b404ed6d 100644 --- a/test/unit/test/jest-expect.test.ts +++ b/test/unit/test/jest-expect.test.ts @@ -226,6 +226,25 @@ describe('jest-expect', () => { expect([new Stock('x')]).toStrictEqual(expect.arrayContaining([new Stock('x')])) }) + it('expect.extend asymmetric matchers and equality testers', () => { + // iterable equality testers + expect(new Set(['x'])).toEqual(expect.toBeOneOf([new Set(['x'])])) + expect(new Set()).not.toEqual(expect.toBeOneOf([new Set(['x'])])) + + // `toStrictEqual` testers + class Stock { + constructor(public type: string) {} + } + expect(new Stock('x')).toEqual(expect.toBeOneOf([{ type: 'x' }])) + expect(new Stock('x')).not.toStrictEqual(expect.toBeOneOf([{ type: 'x' }])) + expect(new Stock('x')).toStrictEqual(expect.toBeOneOf([new Stock('x')])) + + // non-asymmetric usage only sees registered testers + // TODO: https://github.com/vitest-dev/vitest/issues/11296 + expect(new Set(['x'])).toBeOneOf([new Set(['x'])]) + expect(new Set()).toBeOneOf([new Set(['x'])]) + }) + // https://github.com/vitest-dev/vitest/issues/11071 it('asymmetric matchers and toMatchObject equality', () => { expect([{ id: 1 }]).not.toMatchObject(expect.arrayContaining([{ id: 1, required: 'x' }]))