diff --git a/CHANGELOG.md b/CHANGELOG.md index c28550f79bee..c95aad438a34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,7 @@ - `[jest-runtime]` Exclude setup/teardown files from coverage report ([#7790](https://github.com/facebook/jest/pull/7790) - `[babel-jest]` Throw an error if `babel-jest` tries to transform a file ignored by Babel ([#7797](https://github.com/facebook/jest/pull/7797)) - `[babel-plugin-jest-hoist]` Ignore TS type references when looking for out-of-scope references ([#7799](https://github.com/facebook/jest/pull/7799) +- `[expect]` fixed asymmetrical equality of cyclic objects ([#7730](https://github.com/facebook/jest/pull/7730)) ### Chore & Maintenance diff --git a/packages/expect/src/__tests__/matchers.test.js b/packages/expect/src/__tests__/matchers.test.js index 4e2cf535b6b3..cff90804df79 100644 --- a/packages/expect/src/__tests__/matchers.test.js +++ b/packages/expect/src/__tests__/matchers.test.js @@ -606,6 +606,59 @@ describe('.toEqual()', () => { }); expect(actual).toEqual({x: 3}); }); + + describe('cyclic object equality', () => { + test('properties with the same circularity are equal', () => { + const a = {}; + a.x = a; + const b = {}; + b.x = b; + expect(a).toEqual(b); + expect(b).toEqual(a); + + const c = {}; + c.x = a; + const d = {}; + d.x = b; + expect(c).toEqual(d); + expect(d).toEqual(c); + }); + + test('properties with different circularity are not equal', () => { + const a = {}; + a.x = {y: a}; + const b = {}; + const bx = {}; + b.x = bx; + bx.y = bx; + expect(a).not.toEqual(b); + expect(b).not.toEqual(a); + + const c = {}; + c.x = a; + const d = {}; + d.x = b; + expect(c).not.toEqual(d); + expect(d).not.toEqual(c); + }); + + test('are not equal if circularity is not on the same property', () => { + const a = {}; + const b = {}; + a.a = a; + b.a = {}; + b.a.a = a; + expect(a).not.toEqual(b); + expect(b).not.toEqual(a); + + const c = {}; + c.x = {x: c}; + const d = {}; + d.x = d; + expect(c).not.toEqual(d); + expect(d).not.toEqual(c); + }); + }); }); describe('.toBeInstanceOf()', () => { diff --git a/packages/expect/src/jasmineUtils.ts b/packages/expect/src/jasmineUtils.ts index 11e368d11001..f16bc5aeaa64 100644 --- a/packages/expect/src/jasmineUtils.ts +++ b/packages/expect/src/jasmineUtils.ts @@ -64,9 +64,9 @@ function asymmetricMatch(a: any, b: any) { function eq( a: any, b: any, - aStack: any, - bStack: any, - customTesters: any, + aStack: Array, + bStack: Array, + customTesters: Array, hasKey: any, ): boolean { var result = true; @@ -149,14 +149,17 @@ function eq( return false; } - // Assume equality for cyclic structures. The algorithm for detecting cyclic - // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + // Used to detect circular references. var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. - if (aStack[length] == a) { - return bStack[length] == b; + // circular references at same depth are equal + // circular reference is not equal to non-circular one + if (aStack[length] === a) { + return bStack[length] === b; + } else if (bStack[length] === b) { + return false; } } // Add the first object to the stack of traversed objects.