assert: introduce `deepStrictEqual`
authorVladimir Kurchatkin <vladimir.kurchatkin@gmail.com>
Wed, 28 Jan 2015 16:48:56 +0000 (19:48 +0300)
committerVladimir Kurchatkin <vladimir.kurchatkin@gmail.com>
Mon, 9 Feb 2015 11:14:20 +0000 (14:14 +0300)
`deepStrictEqual` works the same way as `strictEqual`, but
uses `===` to compare primitives and requires prototypes of
equal objects to be the same object.

Fixes: https://github.com/joyent/node/issues/7161
Fixes: https://github.com/iojs/io.js/issues/620
PR-URL: https://github.com/iojs/io.js/pull/639
Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-by: Rod Vagg <rod@vagg.org>
doc/api/assert.markdown
lib/assert.js
test/parallel/test-assert.js

index 0fb7f0b..9162850 100644 (file)
@@ -23,11 +23,12 @@ 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])
 
@@ -35,7 +36,17 @@ 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])
 
index e3a14bd..7524a62 100644 (file)
@@ -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,7 +181,7 @@ 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);
   }
 }
 
@@ -183,12 +189,14 @@ 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;
   var aIsArgs = isArguments(a),
       bIsArgs = isArguments(b);
   if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs))
@@ -196,28 +204,28 @@ function objEquiv(a, b) {
   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);
 
index 9109fa8..1e2e8e6 100644 (file)
@@ -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');