Skip to content

Commit

Permalink
feat(assert): Allow circular references
Browse files Browse the repository at this point in the history
assert.deepEqual() and assert.deepStrictEqual() will no longer throw a
RangeError if passed objects with circular references.

fix: twada#3
refs:nodejs/node#6416
       nodejs/node#6432
  • Loading branch information
azu committed May 6, 2016
1 parent 8297979 commit e11a573
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 5 deletions.
22 changes: 17 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ var objectKeys = (function () {
};
})();

function _deepEqual(actual, expected, strict) {
function _deepEqual(actual, expected, strict, memos) {
// 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) {
return true;
Expand All @@ -126,7 +126,7 @@ function _deepEqual(actual, expected, strict) {
return actual.getTime() === expected.getTime();

// 7.3 If the expected value is a RegExp object, the actual value is
// equivalent if it is also a RegExp object with the same source and
// equivalejnt if it is also a RegExp object with the same source and
// properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
} else if (isRegExp(actual) && isRegExp(expected)) {
return actual.source === expected.source &&
Expand Down Expand Up @@ -162,11 +162,22 @@ function _deepEqual(actual, expected, strict) {
// corresponding key, and an identical 'prototype' property. Note: this
// accounts for both named and indexed properties on Arrays.
} else {
return objEquiv(actual, expected, strict);
memos = memos || {actual: [], expected: []};
var actualIndex = memos.actual.indexOf(actual);
if (actualIndex !== -1) {
if (actualIndex === memos.expected.indexOf(expected)) {
return true;
}
}

memos.actual.push(actual);
memos.expected.push(expected);

return objEquiv(actual, expected, strict, memos)
}
}

function objEquiv(a, b, strict) {
function objEquiv(a, b, strict, actualVisitedObjects) {
if (a === null || a === undefined || b === null || b === undefined)
return false;
// if one is a primitive, the other must be same
Expand Down Expand Up @@ -202,7 +213,8 @@ function objEquiv(a, b, strict) {
//~~~possibly expensive deep test
for (i = ka.length - 1; i >= 0; i--) {
key = ka[i];
if (!_deepEqual(a[key], b[key], strict)) return false;
if (!_deepEqual(a[key], b[key], strict, actualVisitedObjects))
return false;
}
return true;
}
Expand Down
22 changes: 22 additions & 0 deletions test/test-deep-equal.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,28 @@ assert.throws(makeBlock(deepEqual, true, {}), assert.AssertionError);
if (typeof Symbol !== 'undefined') {
assert.throws(makeBlock(deepEqual, Symbol(), {}), assert.AssertionError);
}
});
// https://github.com/nodejs/node/issues/6416
it("Make sure circular refs don't throw", function(){
var b = {};
b.b = b;

var c = {};
c.b = c;

assert.doesNotThrow(makeBlock(deepEqual, b, c));
assert.doesNotThrow(makeBlock(deepEqual, b, c));

var d = {};
d.a = 1;
d.b = d;

var e = {};
e.a = 1;
e.b = e.a;

assert.throws(makeBlock(deepEqual, d, e), /AssertionError/);
assert.throws(makeBlock(deepEqual, d, e), /AssertionError/);
});

describe('primitive wrappers and object', function () {
Expand Down
21 changes: 21 additions & 0 deletions test/test-deep-strict-equal.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,24 @@ assert.throws(makeBlock(deepStrictEqual, new Boolean(true), {}),
});

});

// https://github.com/nodejs/node/issues/6416
it("Make sure circular refs don't throw", function(){
var b = {};
b.b = b;

var c = {};
c.b = c;

assert.doesNotThrow(makeBlock(deepStrictEqual, b, c));

var d = {};
d.a = 1;
d.b = d;

var e = {};
e.a = 1;
e.b = e.a;

assert.throws(makeBlock(deepStrictEqual, d, e), /AssertionError/);
});

0 comments on commit e11a573

Please sign in to comment.