mirror of https://github.com/nodejs/node.git
assert: add partialDeepStrictEqual
Fixes: https://github.com/nodejs/node/issues/50399 Co-Authored-By: Cristian Barlutiu <cristian.barlutiu@gmail.com> PR-URL: https://github.com/nodejs/node/pull/54630 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Jithil P Ponnan <jithil@outlook.com> Reviewed-By: Marco Ippolito <marcoippolito54@gmail.com>
This commit is contained in:
parent
a2a0c22fbf
commit
d0d52092cf
|
@ -2548,6 +2548,96 @@ assert.throws(throwingFirst, /Second$/);
|
||||||
Due to the confusing error-prone notation, avoid a string as the second
|
Due to the confusing error-prone notation, avoid a string as the second
|
||||||
argument.
|
argument.
|
||||||
|
|
||||||
|
## `assert.partialDeepStrictEqual(actual, expected[, message])`
|
||||||
|
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
> Stability: 1.0 - Early development
|
||||||
|
|
||||||
|
* `actual` {any}
|
||||||
|
* `expected` {any}
|
||||||
|
* `message` {string|Error}
|
||||||
|
|
||||||
|
[`assert.partialDeepStrictEqual()`][] Asserts the equivalence between the `actual` and `expected` parameters through a
|
||||||
|
deep comparison, ensuring that all properties in the `expected` parameter are
|
||||||
|
present in the `actual` parameter with equivalent values, not allowing type coercion.
|
||||||
|
The main difference with [`assert.deepStrictEqual()`][] is that [`assert.partialDeepStrictEqual()`][] does not require
|
||||||
|
all properties in the `actual` parameter to be present in the `expected` parameter.
|
||||||
|
This method should always pass the same test cases as [`assert.deepStrictEqual()`][], behaving as a super set of it.
|
||||||
|
|
||||||
|
```mjs
|
||||||
|
import assert from 'node:assert';
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual({ a: 1, b: 2 }, { a: 1, b: 2 });
|
||||||
|
// OK
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } });
|
||||||
|
// OK
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 });
|
||||||
|
// OK
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual(new Set(['value1', 'value2']), new Set(['value1', 'value2']));
|
||||||
|
// OK
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual(new Map([['key1', 'value1']]), new Map([['key1', 'value1']]));
|
||||||
|
// OK
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3]));
|
||||||
|
// OK
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual(/abc/, /abc/);
|
||||||
|
// OK
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual([{ a: 5 }, { b: 5 }], [{ a: 5 }]);
|
||||||
|
// OK
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual(new Set([{ a: 1 }, { b: 1 }]), new Set([{ a: 1 }]));
|
||||||
|
// OK
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual(new Date(0), new Date(0));
|
||||||
|
// OK
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual({ a: 1 }, { a: 1, b: 2 });
|
||||||
|
// AssertionError
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual({ a: 1, b: '2' }, { a: 1, b: 2 });
|
||||||
|
// AssertionError
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual({ a: { b: 2 } }, { a: { b: '2' } });
|
||||||
|
// AssertionError
|
||||||
|
```
|
||||||
|
|
||||||
|
```cjs
|
||||||
|
const assert = require('node:assert');
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual({ a: 1, b: 2 }, { a: 1, b: 2 });
|
||||||
|
// OK
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } });
|
||||||
|
// OK
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 });
|
||||||
|
// OK
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual([{ a: 5 }, { b: 5 }], [{ a: 5 }]);
|
||||||
|
// OK
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual(new Set([{ a: 1 }, { b: 1 }]), new Set([{ a: 1 }]));
|
||||||
|
// OK
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual({ a: 1 }, { a: 1, b: 2 });
|
||||||
|
// AssertionError
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual({ a: 1, b: '2' }, { a: 1, b: 2 });
|
||||||
|
// AssertionError
|
||||||
|
|
||||||
|
assert.partialDeepStrictEqual({ a: { b: 2 } }, { a: { b: '2' } });
|
||||||
|
// AssertionError
|
||||||
|
```
|
||||||
|
|
||||||
[Object wrappers]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive#Primitive_wrapper_objects_in_JavaScript
|
[Object wrappers]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive#Primitive_wrapper_objects_in_JavaScript
|
||||||
[Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring
|
[Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring
|
||||||
[`!=` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Inequality
|
[`!=` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Inequality
|
||||||
|
@ -2576,6 +2666,7 @@ argument.
|
||||||
[`assert.notEqual()`]: #assertnotequalactual-expected-message
|
[`assert.notEqual()`]: #assertnotequalactual-expected-message
|
||||||
[`assert.notStrictEqual()`]: #assertnotstrictequalactual-expected-message
|
[`assert.notStrictEqual()`]: #assertnotstrictequalactual-expected-message
|
||||||
[`assert.ok()`]: #assertokvalue-message
|
[`assert.ok()`]: #assertokvalue-message
|
||||||
|
[`assert.partialDeepStrictEqual()`]: #assertpartialdeepstrictequalactual-expected-message
|
||||||
[`assert.strictEqual()`]: #assertstrictequalactual-expected-message
|
[`assert.strictEqual()`]: #assertstrictequalactual-expected-message
|
||||||
[`assert.throws()`]: #assertthrowsfn-error-message
|
[`assert.throws()`]: #assertthrowsfn-error-message
|
||||||
[`getColorDepth()`]: tty.md#writestreamgetcolordepthenv
|
[`getColorDepth()`]: tty.md#writestreamgetcolordepthenv
|
||||||
|
|
212
lib/assert.js
212
lib/assert.js
|
@ -21,22 +21,35 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
ArrayFrom,
|
||||||
|
ArrayIsArray,
|
||||||
ArrayPrototypeIndexOf,
|
ArrayPrototypeIndexOf,
|
||||||
ArrayPrototypeJoin,
|
ArrayPrototypeJoin,
|
||||||
ArrayPrototypePush,
|
ArrayPrototypePush,
|
||||||
ArrayPrototypeSlice,
|
ArrayPrototypeSlice,
|
||||||
Error,
|
Error,
|
||||||
|
FunctionPrototypeCall,
|
||||||
|
MapPrototypeDelete,
|
||||||
|
MapPrototypeGet,
|
||||||
|
MapPrototypeHas,
|
||||||
|
MapPrototypeSet,
|
||||||
NumberIsNaN,
|
NumberIsNaN,
|
||||||
ObjectAssign,
|
ObjectAssign,
|
||||||
ObjectIs,
|
ObjectIs,
|
||||||
ObjectKeys,
|
ObjectKeys,
|
||||||
ObjectPrototypeIsPrototypeOf,
|
ObjectPrototypeIsPrototypeOf,
|
||||||
ReflectApply,
|
ReflectApply,
|
||||||
|
ReflectHas,
|
||||||
|
ReflectOwnKeys,
|
||||||
RegExpPrototypeExec,
|
RegExpPrototypeExec,
|
||||||
|
SafeMap,
|
||||||
|
SafeSet,
|
||||||
|
SafeWeakSet,
|
||||||
String,
|
String,
|
||||||
StringPrototypeIndexOf,
|
StringPrototypeIndexOf,
|
||||||
StringPrototypeSlice,
|
StringPrototypeSlice,
|
||||||
StringPrototypeSplit,
|
StringPrototypeSplit,
|
||||||
|
SymbolIterator,
|
||||||
} = primordials;
|
} = primordials;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -50,8 +63,18 @@ const {
|
||||||
} = require('internal/errors');
|
} = require('internal/errors');
|
||||||
const AssertionError = require('internal/assert/assertion_error');
|
const AssertionError = require('internal/assert/assertion_error');
|
||||||
const { inspect } = require('internal/util/inspect');
|
const { inspect } = require('internal/util/inspect');
|
||||||
const { isPromise, isRegExp } = require('internal/util/types');
|
const { Buffer } = require('buffer');
|
||||||
const { isError, deprecate } = require('internal/util');
|
const {
|
||||||
|
isKeyObject,
|
||||||
|
isPromise,
|
||||||
|
isRegExp,
|
||||||
|
isMap,
|
||||||
|
isSet,
|
||||||
|
isDate,
|
||||||
|
isWeakSet,
|
||||||
|
isWeakMap,
|
||||||
|
} = require('internal/util/types');
|
||||||
|
const { isError, deprecate, emitExperimentalWarning } = require('internal/util');
|
||||||
const { innerOk } = require('internal/assert/utils');
|
const { innerOk } = require('internal/assert/utils');
|
||||||
|
|
||||||
const CallTracker = require('internal/assert/calltracker');
|
const CallTracker = require('internal/assert/calltracker');
|
||||||
|
@ -341,6 +364,191 @@ assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function isSpecial(obj) {
|
||||||
|
return obj == null || typeof obj !== 'object' || isError(obj) || isRegExp(obj) || isDate(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
const typesToCallDeepStrictEqualWith = [
|
||||||
|
isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two objects or values recursively to check if they are equal.
|
||||||
|
* @param {any} actual - The actual value to compare.
|
||||||
|
* @param {any} expected - The expected value to compare.
|
||||||
|
* @param {Set} [comparedObjects=new Set()] - Set to track compared objects for handling circular references.
|
||||||
|
* @returns {boolean} - Returns `true` if the actual value matches the expected value, otherwise `false`.
|
||||||
|
* @example
|
||||||
|
* compareBranch({a: 1, b: 2, c: 3}, {a: 1, b: 2}); // true
|
||||||
|
*/
|
||||||
|
function compareBranch(
|
||||||
|
actual,
|
||||||
|
expected,
|
||||||
|
comparedObjects,
|
||||||
|
) {
|
||||||
|
// Check for Map object equality
|
||||||
|
if (isMap(actual) && isMap(expected)) {
|
||||||
|
if (actual.size !== expected.size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const safeIterator = FunctionPrototypeCall(SafeMap.prototype[SymbolIterator], actual);
|
||||||
|
|
||||||
|
comparedObjects ??= new SafeWeakSet();
|
||||||
|
|
||||||
|
for (const { 0: key, 1: val } of safeIterator) {
|
||||||
|
if (!MapPrototypeHas(expected, key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!compareBranch(val, MapPrototypeGet(expected, key), comparedObjects)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const type of typesToCallDeepStrictEqualWith) {
|
||||||
|
if (type(actual) || type(expected)) {
|
||||||
|
if (isDeepStrictEqual === undefined) lazyLoadComparison();
|
||||||
|
return isDeepStrictEqual(actual, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Set object equality
|
||||||
|
// TODO(aduh95): switch to `SetPrototypeIsSubsetOf` when it's available
|
||||||
|
if (isSet(actual) && isSet(expected)) {
|
||||||
|
if (expected.size > actual.size) {
|
||||||
|
return false; // `expected` can't be a subset if it has more elements
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDeepEqual === undefined) lazyLoadComparison();
|
||||||
|
|
||||||
|
const actualArray = ArrayFrom(actual);
|
||||||
|
const expectedArray = ArrayFrom(expected);
|
||||||
|
const usedIndices = new SafeSet();
|
||||||
|
|
||||||
|
for (let expectedIdx = 0; expectedIdx < expectedArray.length; expectedIdx++) {
|
||||||
|
const expectedItem = expectedArray[expectedIdx];
|
||||||
|
let found = false;
|
||||||
|
|
||||||
|
for (let actualIdx = 0; actualIdx < actualArray.length; actualIdx++) {
|
||||||
|
if (!usedIndices.has(actualIdx) && isDeepStrictEqual(actualArray[actualIdx], expectedItem)) {
|
||||||
|
usedIndices.add(actualIdx);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if expected array is a subset of actual array
|
||||||
|
if (ArrayIsArray(actual) && ArrayIsArray(expected)) {
|
||||||
|
if (expected.length > actual.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDeepEqual === undefined) lazyLoadComparison();
|
||||||
|
|
||||||
|
// Create a map to count occurrences of each element in the expected array
|
||||||
|
const expectedCounts = new SafeMap();
|
||||||
|
for (const expectedItem of expected) {
|
||||||
|
let found = false;
|
||||||
|
for (const { 0: key, 1: count } of expectedCounts) {
|
||||||
|
if (isDeepStrictEqual(key, expectedItem)) {
|
||||||
|
MapPrototypeSet(expectedCounts, key, count + 1);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
MapPrototypeSet(expectedCounts, expectedItem, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a map to count occurrences of relevant elements in the actual array
|
||||||
|
for (const actualItem of actual) {
|
||||||
|
for (const { 0: key, 1: count } of expectedCounts) {
|
||||||
|
if (isDeepStrictEqual(key, actualItem)) {
|
||||||
|
if (count === 1) {
|
||||||
|
MapPrototypeDelete(expectedCounts, key);
|
||||||
|
} else {
|
||||||
|
MapPrototypeSet(expectedCounts, key, count - 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !expectedCounts.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparison done when at least one of the values is not an object
|
||||||
|
if (isSpecial(actual) || isSpecial(expected)) {
|
||||||
|
if (isDeepEqual === undefined) {
|
||||||
|
lazyLoadComparison();
|
||||||
|
}
|
||||||
|
return isDeepStrictEqual(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use Reflect.ownKeys() instead of Object.keys() to include symbol properties
|
||||||
|
const keysExpected = ReflectOwnKeys(expected);
|
||||||
|
|
||||||
|
comparedObjects ??= new SafeWeakSet();
|
||||||
|
|
||||||
|
// Handle circular references
|
||||||
|
if (comparedObjects.has(actual)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
comparedObjects.add(actual);
|
||||||
|
|
||||||
|
// Check if all expected keys and values match
|
||||||
|
for (let i = 0; i < keysExpected.length; i++) {
|
||||||
|
const key = keysExpected[i];
|
||||||
|
assert(
|
||||||
|
ReflectHas(actual, key),
|
||||||
|
new AssertionError({ message: `Expected key ${String(key)} not found in actual object` }),
|
||||||
|
);
|
||||||
|
if (!compareBranch(actual[key], expected[key], comparedObjects)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The strict equivalence assertion test between two objects
|
||||||
|
* @param {any} actual
|
||||||
|
* @param {any} expected
|
||||||
|
* @param {string | Error} [message]
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
assert.partialDeepStrictEqual = function partialDeepStrictEqual(
|
||||||
|
actual,
|
||||||
|
expected,
|
||||||
|
message,
|
||||||
|
) {
|
||||||
|
emitExperimentalWarning('assert.partialDeepStrictEqual');
|
||||||
|
if (arguments.length < 2) {
|
||||||
|
throw new ERR_MISSING_ARGS('actual', 'expected');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!compareBranch(actual, expected)) {
|
||||||
|
innerFail({
|
||||||
|
actual,
|
||||||
|
expected,
|
||||||
|
message,
|
||||||
|
operator: 'partialDeepStrictEqual',
|
||||||
|
stackStartFn: partialDeepStrictEqual,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class Comparison {
|
class Comparison {
|
||||||
constructor(obj, keys, actual) {
|
constructor(obj, keys, actual) {
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
|
|
|
@ -119,6 +119,7 @@ function lazyAssertObject(harness) {
|
||||||
'notDeepStrictEqual',
|
'notDeepStrictEqual',
|
||||||
'notEqual',
|
'notEqual',
|
||||||
'notStrictEqual',
|
'notStrictEqual',
|
||||||
|
'partialDeepStrictEqual',
|
||||||
'rejects',
|
'rejects',
|
||||||
'strictEqual',
|
'strictEqual',
|
||||||
'throws',
|
'throws',
|
||||||
|
|
|
@ -0,0 +1,501 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
const vm = require('node:vm');
|
||||||
|
const assert = require('node:assert');
|
||||||
|
const { describe, it } = require('node:test');
|
||||||
|
|
||||||
|
function createCircularObject() {
|
||||||
|
const obj = {};
|
||||||
|
obj.self = obj;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDeepNestedObject() {
|
||||||
|
return { level1: { level2: { level3: 'deepValue' } } };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateCryptoKey() {
|
||||||
|
const { KeyObject } = require('node:crypto');
|
||||||
|
const { subtle } = globalThis.crypto;
|
||||||
|
|
||||||
|
const cryptoKey = await subtle.generateKey(
|
||||||
|
{
|
||||||
|
name: 'HMAC',
|
||||||
|
hash: 'SHA-256',
|
||||||
|
length: 256,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
['sign', 'verify']
|
||||||
|
);
|
||||||
|
|
||||||
|
const keyObject = KeyObject.from(cryptoKey);
|
||||||
|
|
||||||
|
return { cryptoKey, keyObject };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Object Comparison Tests', () => {
|
||||||
|
describe('partialDeepStrictEqual', () => {
|
||||||
|
describe('throws an error', () => {
|
||||||
|
const tests = [
|
||||||
|
{
|
||||||
|
description: 'throws when only one argument is provided',
|
||||||
|
actual: { a: 1 },
|
||||||
|
expected: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'throws when expected has more properties than actual',
|
||||||
|
actual: [1, 'two'],
|
||||||
|
expected: [1, 'two', true],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'throws because expected has seven 2 while actual has six one',
|
||||||
|
actual: [1, 2, 2, 2, 2, 2, 2, 3],
|
||||||
|
expected: [1, 2, 2, 2, 2, 2, 2, 2],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'throws when comparing two different sets with objects',
|
||||||
|
actual: new Set([{ a: 1 }]),
|
||||||
|
expected: new Set([{ a: 1 }, { b: 1 }]),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
description: 'throws when comparing two WeakSet objects',
|
||||||
|
actual: new WeakSet(),
|
||||||
|
expected: new WeakSet(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'throws when comparing two WeakMap objects',
|
||||||
|
actual: new WeakMap(),
|
||||||
|
expected: new WeakMap(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'throws when comparing two different objects',
|
||||||
|
actual: { a: 1, b: 'string' },
|
||||||
|
expected: { a: 2, b: 'string' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'throws when comparing two objects with different nested objects',
|
||||||
|
actual: createDeepNestedObject(),
|
||||||
|
expected: { level1: { level2: { level3: 'differentValue' } } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'throws when comparing two objects with different RegExp properties',
|
||||||
|
actual: { pattern: /abc/ },
|
||||||
|
expected: { pattern: /def/ },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'throws when comparing two arrays with different elements',
|
||||||
|
actual: [1, 'two', true],
|
||||||
|
expected: [1, 'two', false],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'throws when comparing two Date objects with different times',
|
||||||
|
actual: new Date(0),
|
||||||
|
expected: new Date(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'throws when comparing two objects with different large number of properties',
|
||||||
|
actual: Object.fromEntries(
|
||||||
|
Array.from({ length: 100 }, (_, i) => [`key${i}`, i])
|
||||||
|
),
|
||||||
|
expected: Object.fromEntries(
|
||||||
|
Array.from({ length: 100 }, (_, i) => [`key${i}`, i + 1])
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'throws when comparing two objects with different Symbols',
|
||||||
|
actual: { [Symbol('test')]: 'symbol' },
|
||||||
|
expected: { [Symbol('test')]: 'symbol' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'throws when comparing two objects with different array properties',
|
||||||
|
actual: { a: [1, 2, 3] },
|
||||||
|
expected: { a: [1, 2, 4] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'throws when comparing two objects with different function properties',
|
||||||
|
actual: { fn: () => {} },
|
||||||
|
expected: { fn: () => {} },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'throws when comparing two objects with different Error instances',
|
||||||
|
actual: { error: new Error('Test error 1') },
|
||||||
|
expected: { error: new Error('Test error 2') },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'throws when comparing two objects with different TypedArray instances and content',
|
||||||
|
actual: { typedArray: new Uint8Array([1, 2, 3]) },
|
||||||
|
expected: { typedArray: new Uint8Array([4, 5, 6]) },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'throws when comparing two Map objects with different entries',
|
||||||
|
actual: new Map([
|
||||||
|
['key1', 'value1'],
|
||||||
|
['key2', 'value2'],
|
||||||
|
]),
|
||||||
|
expected: new Map([
|
||||||
|
['key1', 'value1'],
|
||||||
|
['key3', 'value3'],
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'throws when comparing two Map objects with different keys',
|
||||||
|
actual: new Map([
|
||||||
|
['key1', 'value1'],
|
||||||
|
['key2', 'value2'],
|
||||||
|
]),
|
||||||
|
expected: new Map([
|
||||||
|
['key1', 'value1'],
|
||||||
|
['key3', 'value2'],
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'throws when comparing two Map objects with different length',
|
||||||
|
actual: new Map([
|
||||||
|
['key1', 'value1'],
|
||||||
|
['key2', 'value2'],
|
||||||
|
]),
|
||||||
|
expected: new Map([['key1', 'value1']]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'throws when comparing two TypedArray instances with different content',
|
||||||
|
actual: new Uint8Array(10),
|
||||||
|
expected: () => {
|
||||||
|
const typedArray2 = new Int8Array(10);
|
||||||
|
Object.defineProperty(typedArray2, Symbol.toStringTag, {
|
||||||
|
value: 'Uint8Array'
|
||||||
|
});
|
||||||
|
Object.setPrototypeOf(typedArray2, Uint8Array.prototype);
|
||||||
|
|
||||||
|
return typedArray2;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'throws when comparing two Set objects from different realms with different values',
|
||||||
|
actual: new vm.runInNewContext('new Set(["value1", "value2"])'),
|
||||||
|
expected: new Set(['value1', 'value3']),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'throws when comparing two Set objects with different values',
|
||||||
|
actual: new Set(['value1', 'value2']),
|
||||||
|
expected: new Set(['value1', 'value3']),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'throws when comparing one subset object with another',
|
||||||
|
actual: { a: 1, b: 2, c: 3 },
|
||||||
|
expected: { b: '2' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'throws when comparing one subset array with another',
|
||||||
|
actual: [1, 2, 3],
|
||||||
|
expected: ['2'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (common.hasCrypto) {
|
||||||
|
tests.push({
|
||||||
|
description:
|
||||||
|
'throws when comparing two objects with different CryptoKey instances objects',
|
||||||
|
actual: async () => {
|
||||||
|
return generateCryptoKey();
|
||||||
|
},
|
||||||
|
expected: async () => {
|
||||||
|
return generateCryptoKey();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { createSecretKey } = require('node:crypto');
|
||||||
|
|
||||||
|
tests.push({
|
||||||
|
description:
|
||||||
|
'throws when comparing two objects with different KeyObject instances objects',
|
||||||
|
actual: createSecretKey(Buffer.alloc(1, 0)),
|
||||||
|
expected: createSecretKey(Buffer.alloc(1, 1)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tests.forEach(({ description, actual, expected }) => {
|
||||||
|
it(description, () => {
|
||||||
|
assert.throws(() => assert.partialDeepStrictEqual(actual, expected), Error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('does not throw an error', () => {
|
||||||
|
const sym = Symbol('test');
|
||||||
|
const func = () => {};
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
description: 'compares two identical simple objects',
|
||||||
|
actual: { a: 1, b: 'string' },
|
||||||
|
expected: { a: 1, b: 'string' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two objects with different property order',
|
||||||
|
actual: { a: 1, b: 'string' },
|
||||||
|
expected: { b: 'string', a: 1 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two deeply nested objects with partial equality',
|
||||||
|
actual: { a: { nested: { property: true, some: 'other' } } },
|
||||||
|
expected: { a: { nested: { property: true } } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'compares plain objects from different realms',
|
||||||
|
actual: vm.runInNewContext(`({
|
||||||
|
a: 1,
|
||||||
|
b: 2n,
|
||||||
|
c: "3",
|
||||||
|
d: /4/,
|
||||||
|
e: new Set([5]),
|
||||||
|
f: [6],
|
||||||
|
g: new Uint8Array()
|
||||||
|
})`),
|
||||||
|
expected: { b: 2n, e: new Set([5]), f: [6], g: new Uint8Array() },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two integers',
|
||||||
|
actual: 1,
|
||||||
|
expected: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two strings',
|
||||||
|
actual: '1',
|
||||||
|
expected: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two objects with nested objects',
|
||||||
|
actual: createDeepNestedObject(),
|
||||||
|
expected: createDeepNestedObject(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two objects with circular references',
|
||||||
|
actual: createCircularObject(),
|
||||||
|
expected: createCircularObject(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two arrays with identical elements',
|
||||||
|
actual: [1, 'two', true],
|
||||||
|
expected: [1, 'two', true],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two Date objects with the same time',
|
||||||
|
actual: new Date(0),
|
||||||
|
expected: new Date(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two objects with large number of properties',
|
||||||
|
actual: Object.fromEntries(
|
||||||
|
Array.from({ length: 100 }, (_, i) => [`key${i}`, i])
|
||||||
|
),
|
||||||
|
expected: Object.fromEntries(
|
||||||
|
Array.from({ length: 100 }, (_, i) => [`key${i}`, i])
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two objects with Symbol properties',
|
||||||
|
actual: { [sym]: 'symbol' },
|
||||||
|
expected: { [sym]: 'symbol' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two objects with RegExp properties',
|
||||||
|
actual: { pattern: /abc/ },
|
||||||
|
expected: { pattern: /abc/ },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two objects with identical function properties',
|
||||||
|
actual: { fn: func },
|
||||||
|
expected: { fn: func },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two objects with mixed types of properties',
|
||||||
|
actual: { num: 1, str: 'test', bool: true, sym },
|
||||||
|
expected: { num: 1, str: 'test', bool: true, sym },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two objects with Buffers',
|
||||||
|
actual: { buf: Buffer.from('Node.js') },
|
||||||
|
expected: { buf: Buffer.from('Node.js') },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two objects with identical Error properties',
|
||||||
|
actual: { error: new Error('Test error') },
|
||||||
|
expected: { error: new Error('Test error') },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two objects with TypedArray instances with the same content',
|
||||||
|
actual: { typedArray: new Uint8Array([1, 2, 3]) },
|
||||||
|
expected: { typedArray: new Uint8Array([1, 2, 3]) },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two Map objects with identical entries',
|
||||||
|
actual: new Map([
|
||||||
|
['key1', 'value1'],
|
||||||
|
['key2', 'value2'],
|
||||||
|
]),
|
||||||
|
expected: new Map([
|
||||||
|
['key1', 'value1'],
|
||||||
|
['key2', 'value2'],
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
describe: 'compares two array of objects',
|
||||||
|
actual: [{ a: 5 }],
|
||||||
|
expected: [{ a: 5 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
describe: 'compares two array of objects where expected is a subset of actual',
|
||||||
|
actual: [{ a: 5 }, { b: 5 }],
|
||||||
|
expected: [{ a: 5 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two Set objects with identical objects',
|
||||||
|
actual: new Set([{ a: 1 }]),
|
||||||
|
expected: new Set([{ a: 1 }]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two Set objects where expected is a subset of actual',
|
||||||
|
actual: new Set([{ a: 1 }, { b: 1 }]),
|
||||||
|
expected: new Set([{ a: 1 }]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two Set objects with identical arrays',
|
||||||
|
actual: new Set(['value1', 'value2']),
|
||||||
|
expected: new Set(['value1', 'value2']),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two Set objects',
|
||||||
|
actual: new Set(['value1', 'value2', 'value3']),
|
||||||
|
expected: new Set(['value1', 'value2']),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'compares two Map objects from different realms with identical entries',
|
||||||
|
actual: new vm.runInNewContext(
|
||||||
|
'new Map([["key1", "value1"], ["key2", "value2"]])'
|
||||||
|
),
|
||||||
|
expected: new Map([
|
||||||
|
['key1', 'value1'],
|
||||||
|
['key2', 'value2'],
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'compares two objects with identical getter/setter properties',
|
||||||
|
actual: (() => {
|
||||||
|
let value = 'test';
|
||||||
|
return Object.defineProperty({}, 'prop', {
|
||||||
|
get: () => value,
|
||||||
|
set: (newValue) => {
|
||||||
|
value = newValue;
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
})(),
|
||||||
|
expected: (() => {
|
||||||
|
let value = 'test';
|
||||||
|
return Object.defineProperty({}, 'prop', {
|
||||||
|
get: () => value,
|
||||||
|
set: (newValue) => {
|
||||||
|
value = newValue;
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
});
|
||||||
|
})(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two objects with no prototype',
|
||||||
|
actual: { __proto__: null, prop: 'value' },
|
||||||
|
expected: { __proto__: null, prop: 'value' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'compares two objects with identical non-enumerable properties',
|
||||||
|
actual: (() => {
|
||||||
|
const obj = {};
|
||||||
|
Object.defineProperty(obj, 'hidden', {
|
||||||
|
value: 'secret',
|
||||||
|
enumerable: false,
|
||||||
|
});
|
||||||
|
return obj;
|
||||||
|
})(),
|
||||||
|
expected: (() => {
|
||||||
|
const obj = {};
|
||||||
|
Object.defineProperty(obj, 'hidden', {
|
||||||
|
value: 'secret',
|
||||||
|
enumerable: false,
|
||||||
|
});
|
||||||
|
return obj;
|
||||||
|
})(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two identical primitives, string',
|
||||||
|
actual: 'foo',
|
||||||
|
expected: 'foo',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two identical primitives, number',
|
||||||
|
actual: 1,
|
||||||
|
expected: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two identical primitives, boolean',
|
||||||
|
actual: false,
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two identical primitives, null',
|
||||||
|
actual: null,
|
||||||
|
expected: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two identical primitives, undefined',
|
||||||
|
actual: undefined,
|
||||||
|
expected: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'compares two identical primitives, Symbol',
|
||||||
|
actual: sym,
|
||||||
|
expected: sym,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'compares one subset object with another',
|
||||||
|
actual: { a: 1, b: 2, c: 3 },
|
||||||
|
expected: { b: 2 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
'compares one subset array with another',
|
||||||
|
actual: [1, 2, 3],
|
||||||
|
expected: [2],
|
||||||
|
},
|
||||||
|
].forEach(({ description, actual, expected }) => {
|
||||||
|
it(description, () => {
|
||||||
|
assert.partialDeepStrictEqual(actual, expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue