Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
assert: fix boxed primitives in deep*Equal
Unbox all primitives and compare them as well instead of
only comparing boxed strings.
  • Loading branch information
BridgeAR committed Sep 5, 2017
commit 66432d859b3868053147fcb8db2ad160766bd9d2
10 changes: 9 additions & 1 deletion doc/api/assert.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ changes:
* `expected` {any}
* `message` {any}

Generally identical to `assert.deepEqual()` with three exceptions:
Generally identical to `assert.deepEqual()` with a few exceptions:

1. Primitive values besides `NaN` are compared using the [Strict Equality
Comparison][] ( `===` ). Set and Map values, Map keys and `NaN` are compared
Expand All @@ -139,6 +139,7 @@ Generally identical to `assert.deepEqual()` with three exceptions:
2. [`[[Prototype]]`][prototype-spec] of objects are compared using
the [Strict Equality Comparison][] too.
3. [Type tags][Object.prototype.toString()] of objects should be the same.
4. [Object wrappers][] are compared both as objects and unwrapped values.

```js
const assert = require('assert');
Expand Down Expand Up @@ -168,8 +169,14 @@ assert.deepEqual(date, fakeDate);
assert.deepStrictEqual(date, fakeDate);
// AssertionError: 2017-03-11T14:25:31.849Z deepStrictEqual Date {}
// Different type tags

assert.deepStrictEqual(NaN, NaN);
// OK, because of the SameValueZero comparison

assert.deepStrictEqual(new Number(1), new Number(2));
// Fails because the wrapped number is unwrapped and compared as well.
assert.deepStrictEqual(new String('foo'), Object('foo'));
// OK because the object and the string are identical when unwrapped.
```

If the values are not equal, an `AssertionError` is thrown with a `message`
Expand Down Expand Up @@ -686,3 +693,4 @@ For more information, see
[enumerable "own" properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
[mdn-equality-guide]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
[prototype-spec]: https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
[Object wrappers]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive#Primitive_wrapper_objects_in_JavaScript
19 changes: 18 additions & 1 deletion lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,30 @@ function strictDeepEqual(actual, expected) {
if (!areSimilarTypedArrays(actual, expected)) {
return false;
}

// Buffer.compare returns true, so actual.length === expected.length
// if they both only contain numeric keys, we don't need to exam further
if (Object.keys(actual).length === actual.length &&
Object.keys(expected).length === expected.length) {
return true;
}
} else if (typeof actual.valueOf === 'function') {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IIUC this also affects objects with custom valueOf, so the documentation needs to be updated as well..

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You are right! I am going to add that to the documentation.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed

const actualValue = actual.valueOf();
// Note: Boxed string keys are going to be compared again by Object.keys
if (actualValue !== actual) {
if (!innerDeepEqual(actualValue, expected.valueOf(), true))
return false;
// Fast path for boxed primitives
var lengthActual = 0;
var lengthExpected = 0;
if (typeof actualValue === 'string') {
lengthActual = actual.length;
lengthExpected = expected.length;
}
if (Object.keys(actual).length === lengthActual &&
Object.keys(expected).length === lengthExpected) {
return true;
}
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion test/addons-napi/test_conversions/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ assert.deepStrictEqual(new Boolean(false), test.toObject(false));
assert.deepStrictEqual(new Boolean(true), test.toObject(true));
assert.deepStrictEqual(new String(''), test.toObject(''));
assert.deepStrictEqual(new Number(0), test.toObject(0));
assert.deepStrictEqual(new Number(Number.NaN), test.toObject(Number.NaN));
// TODO: Add test back in as soon as NaN is properly compared
// assert.deepStrictEqual(new Number(Number.NaN), test.toObject(Number.NaN));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we remove the todo and add the test back?

Copy link
Copy Markdown
Member Author

@BridgeAR BridgeAR Sep 2, 2017

Choose a reason for hiding this comment

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

Not before #15036 landed (still trying to figure out if that one should be semver-major or a patch though). Otherwise the test would fail.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

what's the depedency of this change to #15036?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Currently assert.deepStrictEqual(NaN, NaN) will fail. This test only worked because NaN was never compared as it was not unboxed before. #15036 will change that to not fail anymore in case NaN is compared to itself.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Have you pushed you changes to this PR? Looks like these lines are still commented?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks a lot for reminding me! It is now back in place.

assert.deepStrictEqual(new Object(testSym), test.toObject(testSym));
assert.notDeepStrictEqual(false, test.toObject(false));
assert.notDeepStrictEqual(true, test.toObject(true));
Expand Down
19 changes: 19 additions & 0 deletions test/parallel/test-assert-deep.js
Original file line number Diff line number Diff line change
Expand Up @@ -489,4 +489,23 @@ assert.doesNotThrow(() => { assert.deepStrictEqual({ a: NaN }, { a: NaN }); });
assert.doesNotThrow(
() => { assert.deepStrictEqual([ 1, 2, NaN, 4 ], [ 1, 2, NaN, 4 ]); });

// Handle boxed primitives
{
const boxedString = new String('test');
const boxedSymbol = Object(Symbol());
assertOnlyDeepEqual(new Boolean(true), Object(false));
assertOnlyDeepEqual(Object(true), new Number(1));
assertOnlyDeepEqual(new Number(2), new Number(1));
assertOnlyDeepEqual(boxedSymbol, Object(Symbol()));
assertOnlyDeepEqual(boxedSymbol, {});
assertDeepAndStrictEqual(boxedSymbol, boxedSymbol);
assertDeepAndStrictEqual(Object(true), Object(true));
assertDeepAndStrictEqual(Object(2), Object(2));
assertDeepAndStrictEqual(boxedString, Object('test'));
boxedString.slow = true;
assertNotDeepOrStrict(boxedString, Object('test'));
boxedSymbol.slow = true;
assertNotDeepOrStrict(boxedSymbol, {});
}

/* eslint-enable */