Skip to content

Commit

Permalink
Fix circular references in iterable equality
Browse files Browse the repository at this point in the history
  • Loading branch information
mattphillips committed Mar 19, 2019
1 parent ce65aac commit 2402b09
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 7 deletions.
52 changes: 52 additions & 0 deletions packages/expect/src/__tests__/utils.test.js
Expand Up @@ -15,6 +15,7 @@ const {
getPath,
hasOwnProperty,
subsetEquality,
iterableEquality,
} = require('../utils');

describe('getPath()', () => {
Expand Down Expand Up @@ -202,3 +203,54 @@ describe('subsetEquality()', () => {
expect(subsetEquality(undefined, {foo: 'bar'})).not.toBeTruthy();
});
});

describe('iterableEquality', () => {
test('returns true when given circular iterators', () => {
class Iter {
*[Symbol.iterator]() {
yield this;
}
}

const a = new Iter();
const b = new Iter();

expect(iterableEquality(a, b)).toBe(true);
});

test('returns true when given circular Set', () => {
const a = new Set();
a.add(a);
const b = new Set();
b.add(b);

expect(iterableEquality(a, b)).toBe(true);
});

test('returns true when given circular key in Map', () => {
const a = new Map();
a.set(a, 'a');
const b = new Map();
b.set(b, 'a');

expect(iterableEquality(a, b)).toBe(true);
});

test('returns true when given circular key and value in Map', () => {
const a = new Map();
a.set(a, a);
const b = new Map();
b.set(b, b);

expect(iterableEquality(a, b)).toBe(true);
});

test('returns true when given circular value in Map', () => {
const a = new Map();
a.set('a', a);
const b = new Map();
b.set('a', b);

expect(iterableEquality(a, b)).toBe(true);
});
});
44 changes: 37 additions & 7 deletions packages/expect/src/utils.ts
Expand Up @@ -137,7 +137,13 @@ const IteratorSymbol = Symbol.iterator;

const hasIterator = (object: any) =>
!!(object != null && object[IteratorSymbol]);
export const iterableEquality = (a: any, b: any) => {

export const iterableEquality = (
a: any,
b: any,
aStack: Array<any> = [],
bStack: Array<any> = [],
) => {
if (
typeof a !== 'object' ||
typeof b !== 'object' ||
Expand All @@ -152,6 +158,24 @@ export const iterableEquality = (a: any, b: any) => {
return false;
}

let length = aStack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
// 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;
}
}
aStack.push(a);
bStack.push(b);

const iterableEqualityWithStack = (a: any, b: any) =>
iterableEquality(a, b, aStack, bStack);

if (a.size !== undefined) {
if (a.size !== b.size) {
return false;
Expand All @@ -161,7 +185,7 @@ export const iterableEquality = (a: any, b: any) => {
if (!b.has(aValue)) {
let has = false;
for (const bValue of b) {
const isEqual = equals(aValue, bValue, [iterableEquality]);
const isEqual = equals(aValue, bValue, [iterableEqualityWithStack]);
if (isEqual === true) {
has = true;
}
Expand All @@ -181,17 +205,20 @@ export const iterableEquality = (a: any, b: any) => {
for (const aEntry of a) {
if (
!b.has(aEntry[0]) ||
!equals(aEntry[1], b.get(aEntry[0]), [iterableEquality])
!equals(aEntry[1], b.get(aEntry[0]), [iterableEqualityWithStack])
) {
let has = false;
for (const bEntry of b) {
const matchedKey = equals(aEntry[0], bEntry[0], [iterableEquality]);
const matchedKey = equals(aEntry[0], bEntry[0], [
iterableEqualityWithStack,
]);

let matchedValue = false;
if (matchedKey === true) {
matchedValue = equals(aEntry[1], bEntry[1], [iterableEquality]);
matchedValue = equals(aEntry[1], bEntry[1], [
iterableEqualityWithStack,
]);
}

if (matchedValue === true) {
has = true;
}
Expand All @@ -213,7 +240,10 @@ export const iterableEquality = (a: any, b: any) => {

for (const aValue of a) {
const nextB = bIterator.next();
if (nextB.done || !equals(aValue, nextB.value, [iterableEquality])) {
if (
nextB.done ||
!equals(aValue, nextB.value, [iterableEqualityWithStack])
) {
return false;
}
}
Expand Down

0 comments on commit 2402b09

Please sign in to comment.