Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

assert: introduce deepStrictEqual #639

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
assert: introduce deepStrictEqual
`deepStrictEqual` works the same way as `strictEqual`, but
uses `===` to compare primitives and requires prototypes of
equal objects to be the same object.

Fixes: nodejs/node-v0.x-archive#7161
vkurchatkin committed Feb 7, 2015

Verified

This commit was signed with the committer’s verified signature. The key has expired.
MylesBorins Myles Borins
commit fc2bbe5f994332ff01daceb133121b19d04a6c9e
17 changes: 14 additions & 3 deletions doc/api/assert.markdown
Original file line number Diff line number Diff line change
@@ -23,19 +23,30 @@ Tests shallow, coercive non-equality with the not equal comparison operator ( `!

## assert.deepEqual(actual, expected[, message])

Tests for deep equality.
Tests for deep equality. Primitive values are compared with the equal comparison
operator ( `==` ). Doesn't take object prototypes into account.

## assert.notDeepEqual(actual, expected[, message])

Tests for any deep inequality.
Tests for any deep inequality. Opposite of `assert.deepEqual`.

## assert.strictEqual(actual, expected[, message])

Tests strict equality, as determined by the strict equality operator ( `===` )

## assert.notStrictEqual(actual, expected[, message])

Tests strict non-equality, as determined by the strict not equal operator ( `!==` )
Tests strict non-equality, as determined by the strict not equal
operator ( `!==` )

## assert.deepStrictEqual(actual, expected[, message])

Tests for deep equality. Primitive values are compared with the strict equality
operator ( `===` ).

## assert.notDeepStrictEqual(actual, expected[, message])

Tests for deep inequality. Opposite of `assert.deepStrictEqual`.

## assert.throws(block[, error][, message])

36 changes: 26 additions & 10 deletions lib/assert.js
Original file line number Diff line number Diff line change
@@ -129,12 +129,18 @@ assert.notEqual = function notEqual(actual, expected, message) {
// assert.deepEqual(actual, expected, message_opt);

assert.deepEqual = function deepEqual(actual, expected, message) {
if (!_deepEqual(actual, expected)) {
if (!_deepEqual(actual, expected, false)) {
fail(actual, expected, message, 'deepEqual', assert.deepEqual);
}
};

function _deepEqual(actual, expected) {
assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
if (!_deepEqual(actual, expected, true)) {
fail(actual, expected, message, 'deepStrictEqual', assert.deepStrictEqual);
}
};

function _deepEqual(actual, expected, strict) {
// 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) {
return true;
@@ -166,7 +172,7 @@ function _deepEqual(actual, expected) {
// equivalence is determined by ==.
} else if ((actual === null || typeof actual !== 'object') &&
(expected === null || typeof expected !== 'object')) {
return actual == expected;
return strict ? actual === expected : actual == expected;

// 7.5 For all other Object pairs, including Array objects, equivalence is
// determined by having the same number of owned properties (as verified
@@ -175,49 +181,51 @@ function _deepEqual(actual, expected) {
// corresponding key, and an identical 'prototype' property. Note: this
// accounts for both named and indexed properties on Arrays.
} else {
return objEquiv(actual, expected);
return objEquiv(actual, expected, strict);
}
}

function isArguments(object) {
return Object.prototype.toString.call(object) == '[object Arguments]';
}

function objEquiv(a, b) {
function objEquiv(a, b, strict) {
if (a === null || a === undefined || b === null || b === undefined)
return false;
// if one is a primitive, the other must be same
if (util.isPrimitive(a) || util.isPrimitive(b))
return a === b;
if (strict && Object.getPrototypeOf(a) !== Object.getPrototypeOf(b))
return false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still going to suffer from the things discussed in #621

Are we sure this shouldn't just check primitives? Otherwise we might as well just do a regular strictEquals().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what you mean. recursively call deepStrictEqual on prototypes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looked at the tests and it seems as I would expect. I think I mis-understood points made in the previous thread.

var aIsArgs = isArguments(a),
bIsArgs = isArguments(b);
if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs))
return false;
if (aIsArgs) {
a = pSlice.call(a);
b = pSlice.call(b);
return _deepEqual(a, b);
return _deepEqual(a, b, strict);
}
var ka = Object.keys(a),
kb = Object.keys(b),
key, i;
// having the same number of owned properties (keys incorporates
// hasOwnProperty)
if (ka.length != kb.length)
if (ka.length !== kb.length)
return false;
//the same set of keys (although not necessarily the same order),
ka.sort();
kb.sort();
//~~~cheap key test
for (i = ka.length - 1; i >= 0; i--) {
if (ka[i] != kb[i])
if (ka[i] !== kb[i])
return false;
}
//equivalent values for every corresponding key, and
//~~~possibly expensive deep test
for (i = ka.length - 1; i >= 0; i--) {
key = ka[i];
if (!_deepEqual(a[key], b[key])) return false;
if (!_deepEqual(a[key], b[key], strict)) return false;
}
return true;
}
@@ -226,11 +234,19 @@ function objEquiv(a, b) {
// assert.notDeepEqual(actual, expected, message_opt);

assert.notDeepEqual = function notDeepEqual(actual, expected, message) {
if (_deepEqual(actual, expected)) {
if (_deepEqual(actual, expected, false)) {
fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual);
}
};

assert.notDeepStrictEqual = notDeepStrictEqual;
function notDeepStrictEqual(actual, expected, message) {
if (_deepEqual(actual, expected, true)) {
fail(actual, expected, message, 'notDeepStrictEqual', notDeepStrictEqual);
}
}


// 9. The strict equality assertion tests strict equality, as determined by ===.
// assert.strictEqual(actual, expected, message_opt);

115 changes: 115 additions & 0 deletions test/parallel/test-assert.js
Original file line number Diff line number Diff line change
@@ -149,6 +149,121 @@ assert.doesNotThrow(makeBlock(a.deepEqual, new String('a'), {0: 'a'}), a.Asserti
assert.doesNotThrow(makeBlock(a.deepEqual, new Number(1), {}), a.AssertionError);
assert.doesNotThrow(makeBlock(a.deepEqual, new Boolean(true), {}), a.AssertionError);

//deepStrictEqual
assert.doesNotThrow(makeBlock(a.deepStrictEqual, new Date(2000, 3, 14),
new Date(2000, 3, 14)), 'deepStrictEqual date');

assert.throws(makeBlock(a.deepStrictEqual, new Date(), new Date(2000, 3, 14)),
a.AssertionError,
'deepStrictEqual date');

// 7.3 - strict
assert.doesNotThrow(makeBlock(a.deepStrictEqual, /a/, /a/));
assert.doesNotThrow(makeBlock(a.deepStrictEqual, /a/g, /a/g));
assert.doesNotThrow(makeBlock(a.deepStrictEqual, /a/i, /a/i));
assert.doesNotThrow(makeBlock(a.deepStrictEqual, /a/m, /a/m));
assert.doesNotThrow(makeBlock(a.deepStrictEqual, /a/igm, /a/igm));
assert.throws(makeBlock(a.deepStrictEqual, /ab/, /a/));
assert.throws(makeBlock(a.deepStrictEqual, /a/g, /a/));
assert.throws(makeBlock(a.deepStrictEqual, /a/i, /a/));
assert.throws(makeBlock(a.deepStrictEqual, /a/m, /a/));
assert.throws(makeBlock(a.deepStrictEqual, /a/igm, /a/im));

var re1 = /a/;
re1.lastIndex = 3;
assert.throws(makeBlock(a.deepStrictEqual, re1, /a/));


// 7.4 - strict
assert.throws(makeBlock(a.deepStrictEqual, 4, '4'),
a.AssertionError,
'deepStrictEqual === check');

assert.throws(makeBlock(a.deepStrictEqual, true, 1),
a.AssertionError,
'deepStrictEqual === check');

assert.throws(makeBlock(a.deepStrictEqual, 4, '5'),
a.AssertionError,
'deepStrictEqual === check');

// 7.5 - strict
// having the same number of owned properties && the same set of keys
assert.doesNotThrow(makeBlock(a.deepStrictEqual, {a: 4}, {a: 4}));
assert.doesNotThrow(makeBlock(a.deepStrictEqual,
{a: 4, b: '2'},
{a: 4, b: '2'}));
assert.throws(makeBlock(a.deepStrictEqual, [4], ['4']));
assert.throws(makeBlock(a.deepStrictEqual, {a: 4}, {a: 4, b: true}),
a.AssertionError);
assert.throws(makeBlock(a.deepStrictEqual, ['a'], {0: 'a'}));
//(although not necessarily the same order),
assert.doesNotThrow(makeBlock(a.deepStrictEqual,
{a: 4, b: '1'},
{b: '1', a: 4}));

assert.throws(makeBlock(a.deepStrictEqual,
[0, 1, 2, 'a', 'b'],
[0, 1, 2, 'b', 'a']),
a.AssertionError);

assert.doesNotThrow(makeBlock(a.deepStrictEqual, a1, a2));

// Prototype check
function Constructor1(first, last) {
this.first = first;
this.last = last;
}

function Constructor2(first, last) {
this.first = first;
this.last = last;
}

var obj1 = new Constructor1('Ryan', 'Dahl');
var obj2 = new Constructor2('Ryan', 'Dahl');

assert.throws(makeBlock(a.deepStrictEqual, obj1, obj2), a.AssertionError);

Constructor2.prototype = Constructor1.prototype;
obj2 = new Constructor2('Ryan', 'Dahl');

assert.doesNotThrow(makeBlock(a.deepStrictEqual, obj1, obj2));

// primitives
assert.throws(makeBlock(assert.deepStrictEqual, 4, '4'),
a.AssertionError);
assert.throws(makeBlock(assert.deepStrictEqual, true, 1),
a.AssertionError);
assert.throws(makeBlock(assert.deepStrictEqual, Symbol(), Symbol()),
a.AssertionError);

var s = Symbol();
assert.doesNotThrow(makeBlock(assert.deepStrictEqual, s, s));


// primitives and object
assert.throws(makeBlock(a.deepStrictEqual, null, {}), a.AssertionError);
assert.throws(makeBlock(a.deepStrictEqual, undefined, {}), a.AssertionError);
assert.throws(makeBlock(a.deepStrictEqual, 'a', ['a']), a.AssertionError);
assert.throws(makeBlock(a.deepStrictEqual, 'a', {0: 'a'}), a.AssertionError);
assert.throws(makeBlock(a.deepStrictEqual, 1, {}), a.AssertionError);
assert.throws(makeBlock(a.deepStrictEqual, true, {}), a.AssertionError);
assert.throws(makeBlock(assert.deepStrictEqual, Symbol(), {}),
a.AssertionError);


// primitive wrappers and object
assert.throws(makeBlock(a.deepStrictEqual, new String('a'), ['a']),
a.AssertionError);
assert.throws(makeBlock(a.deepStrictEqual, new String('a'), {0: 'a'}),
a.AssertionError);
assert.throws(makeBlock(a.deepStrictEqual, new Number(1), {}),
a.AssertionError);
assert.throws(makeBlock(a.deepStrictEqual, new Boolean(true), {}),
a.AssertionError);


// Testing the throwing
function thrower(errorConstructor) {
throw new errorConstructor('test');