From 9e6417c994eb3f11b5aa1cf90d3a82e2050e54be Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 17 Apr 2024 21:47:40 +0900 Subject: [PATCH] fix(expect): fix `toEqual` and `toMatchObject` with circular references (#5535) --- packages/expect/src/jest-expect.ts | 21 ++++----- packages/expect/src/jest-utils.ts | 4 +- test/core/test/expect-circular.test.ts | 60 ++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 test/core/test/expect-circular.test.ts diff --git a/packages/expect/src/jest-expect.ts b/packages/expect/src/jest-expect.ts index 14d03771c12b..cb377ead6bf8 100644 --- a/packages/expect/src/jest-expect.ts +++ b/packages/expect/src/jest-expect.ts @@ -164,17 +164,18 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { const pass = jestEquals(actual, expected, [...customTesters, iterableEquality, subsetEquality]) const isNot = utils.flag(this, 'negate') as boolean const { subset: actualSubset, stripped } = getObjectSubset(actual, expected) - const msg = utils.getMessage( - this, - [ - pass, - 'expected #{this} to match object #{exp}', - 'expected #{this} to not match object #{exp}', - expected, - actualSubset, - ], - ) if ((pass && isNot) || (!pass && !isNot)) { + const msg = utils.getMessage( + this, + [ + pass, + 'expected #{this} to match object #{exp}', + 'expected #{this} to not match object #{exp}', + expected, + actualSubset, + false, + ], + ) const message = stripped === 0 ? msg : `${msg}\n(${stripped} matching ${stripped === 1 ? 'property' : 'properties'} omitted from actual)` throw new AssertionError(message, { showDiff: true, expected, actual: actualSubset }) } diff --git a/packages/expect/src/jest-utils.ts b/packages/expect/src/jest-utils.ts index 0d22002b0aac..7fe4cc706534 100644 --- a/packages/expect/src/jest-utils.ts +++ b/packages/expect/src/jest-utils.ts @@ -334,7 +334,7 @@ export function iterableEquality(a: any, b: any, customTesters: Array = return iterableEquality( a, b, - [...filteredCustomTesters], + [...customTesters], [...aStack], [...bStack], ) @@ -452,7 +452,7 @@ export function subsetEquality(object: unknown, subset: unknown, customTesters: return undefined return Object.keys(subset).every((key) => { - if (isObjectWithKeys(subset[key])) { + if (typeof subset[key] === 'object') { if (seenReferences.has(subset[key])) return equals(object[key], subset[key], filteredCustomTesters) diff --git a/test/core/test/expect-circular.test.ts b/test/core/test/expect-circular.test.ts new file mode 100644 index 000000000000..f8f23ce5a13b --- /dev/null +++ b/test/core/test/expect-circular.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, test } from 'vitest' + +describe('circular equality', () => { + test('object, set, map', () => { + // https://github.com/vitest-dev/vitest/issues/5533 + function gen() { + const obj = { + a: new Set(), + b: new Map(), + } + obj.a.add(obj) + obj.b.set('k', obj) + return obj + } + expect(gen()).toEqual(gen()) + expect(gen()).toMatchObject(gen()) + }) + + test('object, set', () => { + function gen() { + const obj = { + a: new Set(), + b: new Set(), + } + obj.a.add(obj) + obj.b.add(obj) + return obj + } + expect(gen()).toEqual(gen()) + expect(gen()).toMatchObject(gen()) + }) + + test('array, set', () => { + function gen() { + const obj = [new Set(), new Set()] + obj[0].add(obj) + obj[1].add(obj) + return obj + } + expect(gen()).toEqual(gen()) + expect(gen()).toMatchObject(gen()) + }) + + test('object, array', () => { + // https://github.com/jestjs/jest/issues/14734 + function gen() { + const a: any = { + v: 1, + } + const c1: any = { + ref: [], + } + c1.ref.push(c1) + a.ref = c1 + return a + } + expect(gen()).toEqual(gen()) + expect(gen()).toMatchObject(gen()) + }) +})