Skip to content

Commit 954a964

Browse files
Create test helper that does deepEqual for circular references
Since the objects returned by `htmlparser2.parseDOM` have circular references, this makes `assert.deepEqual` testing difficult. As a result, a test helper was created to handle this issue. `deepEqualCircular` walks through the objects and taints the visited ones to prevent the call stack from being overwhelmed. A unit test was also created to check that the helper logic is consistent.
1 parent bde933e commit 954a964

File tree

2 files changed

+110
-0
lines changed

2 files changed

+110
-0
lines changed

test/helpers.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
'use strict';
2+
3+
/**
4+
* Module dependencies.
5+
*/
6+
var assert = require('assert');
7+
var helpers = require('./helpers/');
8+
9+
/**
10+
* Tests for the test helpers.
11+
*/
12+
describe('test helper', function() {
13+
14+
describe('`deepEqualCircular`', function() {
15+
var deepEqualCircular = helpers.deepEqualCircular;
16+
17+
it('works like `assert.deepStrictEqual`', function() {
18+
var obj1;
19+
var obj2;
20+
21+
obj1 = { foo: 'bar', baz: ['qux', 42, null], obj: {} };
22+
obj2 = { obj: {}, baz: ['qux', 42, null], foo: 'bar' };
23+
deepEqualCircular(obj1, obj2);
24+
25+
obj1 = { foo: 'bar', baz: ['qux', 42, null], obj: { ject: 'ion!' } };
26+
assert.throws(function() { deepEqualCircular(obj1, obj2); });
27+
});
28+
29+
it('does not break with circular references', function() {
30+
var obj1;
31+
var obj2;
32+
33+
var obj1 = { foo: 'bar', baz: ['qux', 42, null], obj: {} };
34+
obj1.ref = obj1.obj;
35+
var obj2 = { obj: {}, baz: ['qux', 42, null], foo: 'bar' };
36+
obj2.ref = obj2.obj;
37+
deepEqualCircular(obj1, obj2);
38+
});
39+
40+
});
41+
42+
});

test/helpers/index.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
'use strict';
2+
3+
/**
4+
* Module dependencies.
5+
*/
6+
var assert = require('assert');
7+
8+
/**
9+
* Test for deep equality between objects that have circular references.
10+
*
11+
* @param {Object} actual - The actual object.
12+
* @param {Object} expected - The expected object.
13+
*/
14+
function deepEqualCircular(actual, expected) {
15+
var IS_VISITED_KEY = '_isVisited';
16+
17+
var actualKeys = Object.keys(actual).sort();
18+
var expectedKeys = Object.keys(expected).sort();
19+
20+
// remove extraneous properties (if applicable)
21+
var isVisitedKeyIndex = actualKeys.indexOf(IS_VISITED_KEY);
22+
if (isVisitedKeyIndex > -1) {
23+
actualKeys.splice(isVisitedKeyIndex, 1);
24+
}
25+
26+
// compare object keys (sanity check)
27+
assert.deepStrictEqual(actualKeys, expectedKeys);
28+
29+
// compare actual against expected
30+
expectedKeys.forEach(function(key) {
31+
var actualValue = actual[key];
32+
var expectedValue = expected[key];
33+
34+
if (actualValue !== null && typeof actualValue === 'object') {
35+
// actual and expected are not the same type
36+
if (expectedValue !== null && typeof expectedValue.constructor === 'object') {
37+
throw new Error(
38+
'Actual value: ' + util.inspect(actualValue) +
39+
'\n\nExpected value: ' +
40+
util.inspect(expectedValue)
41+
);
42+
}
43+
44+
// no need to revisit an already visited object
45+
// this is to mitigate exceeding maximum call stack with circular reference
46+
if (actualValue[IS_VISITED_KEY]) {
47+
return;
48+
49+
// otherwise, walk through it
50+
} else {
51+
// taint the object to denote that it's been visited
52+
actualValue[IS_VISITED_KEY] = true;
53+
return deepEqualCircular(actualValue, expectedValue);
54+
}
55+
56+
// compare the values as is
57+
} else {
58+
assert.deepStrictEqual(actualValue, expectedValue);
59+
}
60+
});
61+
}
62+
63+
/**
64+
* Export assert helpers.
65+
*/
66+
module.exports = {
67+
deepEqualCircular: deepEqualCircular
68+
};

0 commit comments

Comments
 (0)