Skip to content

Commit 7a27e77

Browse files
author
Gianfranco
committed
Fix subsetEquality: same referenced object on same level node of tree is regarded as circular reference
1 parent 72a1447 commit 7a27e77

File tree

3 files changed

+24
-6
lines changed

3 files changed

+24
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
- `[jest-utils]` Allow querying process.domain ([#9136](https://github.com/facebook/jest/pull/9136))
7474
- `[pretty-format]` Correctly detect memoized elements ([#9196](https://github.com/facebook/jest/pull/9196))
7575
- `[jest-fake-timers]` Support `util.promisify` on `setTimeout` ([#9180](https://github.com/facebook/jest/pull/9180))
76+
- `[expect]` Fix subsetEquality: fix circular reference handling logic ([#9322](https://github.com/facebook/jest/pull/9322))
7677

7778
### Chore & Maintenance
7879

packages/expect/src/__tests__/utils.test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,19 @@ describe('subsetEquality()', () => {
333333
expect(subsetEquality(primitiveInsteadOfRef, circularObjA1)).toBe(false);
334334
});
335335

336+
test('referenced object on same level should not regarded as circular reference', () => {
337+
const referencedObj = {abc: 'def'};
338+
const object = {
339+
a: {abc: 'def'},
340+
b: {abc: 'def', zzz: 'zzz'},
341+
};
342+
const thisIsNotCircular = {
343+
a: referencedObj,
344+
b: referencedObj,
345+
};
346+
expect(subsetEquality(object, thisIsNotCircular)).toBeTruthy();
347+
});
348+
336349
test('transitive circular references', () => {
337350
const transitiveCircularObjA1 = {a: 'hello'};
338351
transitiveCircularObjA1.nestedObj = {parentObj: transitiveCircularObjA1};

packages/expect/src/utils.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ export const iterableEquality = (
166166
if (a.constructor !== b.constructor) {
167167
return false;
168168
}
169-
170169
let length = aStack.length;
171170
while (length--) {
172171
// Linear search. Performance is inversely proportional to the number of
@@ -290,20 +289,25 @@ export const subsetEquality = (
290289

291290
return Object.keys(subset).every(key => {
292291
if (isObjectWithKeys(subset[key])) {
293-
if (seenReferences.get(subset[key])) {
292+
if (seenReferences.has(subset[key])) {
294293
return equals(object[key], subset[key], [iterableEquality]);
295294
}
296295
seenReferences.set(subset[key], true);
297296
}
298-
299-
return (
297+
const result =
300298
object != null &&
301299
hasOwnProperty(object, key) &&
302300
equals(object[key], subset[key], [
303301
iterableEquality,
304302
subsetEqualityWithContext(seenReferences),
305-
])
306-
);
303+
]);
304+
// The main goal of using seenReference is to avoid circular node on tree.
305+
// It will only happen within a parent and its child, not a node and nodes next to it (same level)
306+
// We should keep the reference for a parent and its child only
307+
// Thus we should delete the reference immediately so that it doesn't interfere
308+
// other nodes within the same level on tree.
309+
seenReferences.delete(subset[key]);
310+
return result;
307311
});
308312
};
309313

0 commit comments

Comments
 (0)