Skip to content

Commit 05d6227

Browse files
puskin94synapse
authored andcommittedJan 5, 2025
assert: add partialDeepStrictEqual
Fixes: #50399 Co-Authored-By: Cristian Barlutiu <cristian.barlutiu@gmail.com> PR-URL: #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>
1 parent 6352604 commit 05d6227

File tree

4 files changed

+793
-2
lines changed

4 files changed

+793
-2
lines changed
 

‎doc/api/assert.md

+91
Original file line numberDiff line numberDiff line change
@@ -2543,6 +2543,96 @@ assert.throws(throwingFirst, /Second$/);
25432543
Due to the confusing error-prone notation, avoid a string as the second
25442544
argument.
25452545

2546+
## `assert.partialDeepStrictEqual(actual, expected[, message])`
2547+
2548+
<!-- YAML
2549+
added: REPLACEME
2550+
-->
2551+
2552+
> Stability: 1.0 - Early development
2553+
2554+
* `actual` {any}
2555+
* `expected` {any}
2556+
* `message` {string|Error}
2557+
2558+
[`assert.partialDeepStrictEqual()`][] Asserts the equivalence between the `actual` and `expected` parameters through a
2559+
deep comparison, ensuring that all properties in the `expected` parameter are
2560+
present in the `actual` parameter with equivalent values, not allowing type coercion.
2561+
The main difference with [`assert.deepStrictEqual()`][] is that [`assert.partialDeepStrictEqual()`][] does not require
2562+
all properties in the `actual` parameter to be present in the `expected` parameter.
2563+
This method should always pass the same test cases as [`assert.deepStrictEqual()`][], behaving as a super set of it.
2564+
2565+
```mjs
2566+
import assert from 'node:assert';
2567+
2568+
assert.partialDeepStrictEqual({ a: 1, b: 2 }, { a: 1, b: 2 });
2569+
// OK
2570+
2571+
assert.partialDeepStrictEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } });
2572+
// OK
2573+
2574+
assert.partialDeepStrictEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 });
2575+
// OK
2576+
2577+
assert.partialDeepStrictEqual(new Set(['value1', 'value2']), new Set(['value1', 'value2']));
2578+
// OK
2579+
2580+
assert.partialDeepStrictEqual(new Map([['key1', 'value1']]), new Map([['key1', 'value1']]));
2581+
// OK
2582+
2583+
assert.partialDeepStrictEqual(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3]));
2584+
// OK
2585+
2586+
assert.partialDeepStrictEqual(/abc/, /abc/);
2587+
// OK
2588+
2589+
assert.partialDeepStrictEqual([{ a: 5 }, { b: 5 }], [{ a: 5 }]);
2590+
// OK
2591+
2592+
assert.partialDeepStrictEqual(new Set([{ a: 1 }, { b: 1 }]), new Set([{ a: 1 }]));
2593+
// OK
2594+
2595+
assert.partialDeepStrictEqual(new Date(0), new Date(0));
2596+
// OK
2597+
2598+
assert.partialDeepStrictEqual({ a: 1 }, { a: 1, b: 2 });
2599+
// AssertionError
2600+
2601+
assert.partialDeepStrictEqual({ a: 1, b: '2' }, { a: 1, b: 2 });
2602+
// AssertionError
2603+
2604+
assert.partialDeepStrictEqual({ a: { b: 2 } }, { a: { b: '2' } });
2605+
// AssertionError
2606+
```
2607+
2608+
```cjs
2609+
const assert = require('node:assert');
2610+
2611+
assert.partialDeepStrictEqual({ a: 1, b: 2 }, { a: 1, b: 2 });
2612+
// OK
2613+
2614+
assert.partialDeepStrictEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } });
2615+
// OK
2616+
2617+
assert.partialDeepStrictEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2 });
2618+
// OK
2619+
2620+
assert.partialDeepStrictEqual([{ a: 5 }, { b: 5 }], [{ a: 5 }]);
2621+
// OK
2622+
2623+
assert.partialDeepStrictEqual(new Set([{ a: 1 }, { b: 1 }]), new Set([{ a: 1 }]));
2624+
// OK
2625+
2626+
assert.partialDeepStrictEqual({ a: 1 }, { a: 1, b: 2 });
2627+
// AssertionError
2628+
2629+
assert.partialDeepStrictEqual({ a: 1, b: '2' }, { a: 1, b: 2 });
2630+
// AssertionError
2631+
2632+
assert.partialDeepStrictEqual({ a: { b: 2 } }, { a: { b: '2' } });
2633+
// AssertionError
2634+
```
2635+
25462636
[Object wrappers]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive#Primitive_wrapper_objects_in_JavaScript
25472637
[Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring
25482638
[`!=` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Inequality
@@ -2571,6 +2661,7 @@ argument.
25712661
[`assert.notEqual()`]: #assertnotequalactual-expected-message
25722662
[`assert.notStrictEqual()`]: #assertnotstrictequalactual-expected-message
25732663
[`assert.ok()`]: #assertokvalue-message
2664+
[`assert.partialDeepStrictEqual()`]: #assertpartialdeepstrictequalactual-expected-message
25742665
[`assert.strictEqual()`]: #assertstrictequalactual-expected-message
25752666
[`assert.throws()`]: #assertthrowsfn-error-message
25762667
[`getColorDepth()`]: tty.md#writestreamgetcolordepthenv

‎lib/assert.js

+210-2
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,35 @@
2121
'use strict';
2222

2323
const {
24+
ArrayFrom,
25+
ArrayIsArray,
2426
ArrayPrototypeIndexOf,
2527
ArrayPrototypeJoin,
2628
ArrayPrototypePush,
2729
ArrayPrototypeSlice,
2830
Error,
31+
FunctionPrototypeCall,
32+
MapPrototypeDelete,
33+
MapPrototypeGet,
34+
MapPrototypeHas,
35+
MapPrototypeSet,
2936
NumberIsNaN,
3037
ObjectAssign,
3138
ObjectIs,
3239
ObjectKeys,
3340
ObjectPrototypeIsPrototypeOf,
3441
ReflectApply,
42+
ReflectHas,
43+
ReflectOwnKeys,
3544
RegExpPrototypeExec,
45+
SafeMap,
46+
SafeSet,
47+
SafeWeakSet,
3648
String,
3749
StringPrototypeIndexOf,
3850
StringPrototypeSlice,
3951
StringPrototypeSplit,
52+
SymbolIterator,
4053
} = primordials;
4154

4255
const {
@@ -50,8 +63,18 @@ const {
5063
} = require('internal/errors');
5164
const AssertionError = require('internal/assert/assertion_error');
5265
const { inspect } = require('internal/util/inspect');
53-
const { isPromise, isRegExp } = require('internal/util/types');
54-
const { isError, deprecate } = require('internal/util');
66+
const { Buffer } = require('buffer');
67+
const {
68+
isKeyObject,
69+
isPromise,
70+
isRegExp,
71+
isMap,
72+
isSet,
73+
isDate,
74+
isWeakSet,
75+
isWeakMap,
76+
} = require('internal/util/types');
77+
const { isError, deprecate, emitExperimentalWarning } = require('internal/util');
5578
const { innerOk } = require('internal/assert/utils');
5679

5780
const CallTracker = require('internal/assert/calltracker');
@@ -341,6 +364,191 @@ assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
341364
}
342365
};
343366

367+
function isSpecial(obj) {
368+
return obj == null || typeof obj !== 'object' || isError(obj) || isRegExp(obj) || isDate(obj);
369+
}
370+
371+
const typesToCallDeepStrictEqualWith = [
372+
isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer,
373+
];
374+
375+
/**
376+
* Compares two objects or values recursively to check if they are equal.
377+
* @param {any} actual - The actual value to compare.
378+
* @param {any} expected - The expected value to compare.
379+
* @param {Set} [comparedObjects=new Set()] - Set to track compared objects for handling circular references.
380+
* @returns {boolean} - Returns `true` if the actual value matches the expected value, otherwise `false`.
381+
* @example
382+
* compareBranch({a: 1, b: 2, c: 3}, {a: 1, b: 2}); // true
383+
*/
384+
function compareBranch(
385+
actual,
386+
expected,
387+
comparedObjects,
388+
) {
389+
// Check for Map object equality
390+
if (isMap(actual) && isMap(expected)) {
391+
if (actual.size !== expected.size) {
392+
return false;
393+
}
394+
const safeIterator = FunctionPrototypeCall(SafeMap.prototype[SymbolIterator], actual);
395+
396+
comparedObjects ??= new SafeWeakSet();
397+
398+
for (const { 0: key, 1: val } of safeIterator) {
399+
if (!MapPrototypeHas(expected, key)) {
400+
return false;
401+
}
402+
if (!compareBranch(val, MapPrototypeGet(expected, key), comparedObjects)) {
403+
return false;
404+
}
405+
}
406+
return true;
407+
}
408+
409+
for (const type of typesToCallDeepStrictEqualWith) {
410+
if (type(actual) || type(expected)) {
411+
if (isDeepStrictEqual === undefined) lazyLoadComparison();
412+
return isDeepStrictEqual(actual, expected);
413+
}
414+
}
415+
416+
// Check for Set object equality
417+
// TODO(aduh95): switch to `SetPrototypeIsSubsetOf` when it's available
418+
if (isSet(actual) && isSet(expected)) {
419+
if (expected.size > actual.size) {
420+
return false; // `expected` can't be a subset if it has more elements
421+
}
422+
423+
if (isDeepEqual === undefined) lazyLoadComparison();
424+
425+
const actualArray = ArrayFrom(actual);
426+
const expectedArray = ArrayFrom(expected);
427+
const usedIndices = new SafeSet();
428+
429+
for (let expectedIdx = 0; expectedIdx < expectedArray.length; expectedIdx++) {
430+
const expectedItem = expectedArray[expectedIdx];
431+
let found = false;
432+
433+
for (let actualIdx = 0; actualIdx < actualArray.length; actualIdx++) {
434+
if (!usedIndices.has(actualIdx) && isDeepStrictEqual(actualArray[actualIdx], expectedItem)) {
435+
usedIndices.add(actualIdx);
436+
found = true;
437+
break;
438+
}
439+
}
440+
441+
if (!found) {
442+
return false;
443+
}
444+
}
445+
446+
return true;
447+
}
448+
449+
// Check if expected array is a subset of actual array
450+
if (ArrayIsArray(actual) && ArrayIsArray(expected)) {
451+
if (expected.length > actual.length) {
452+
return false;
453+
}
454+
455+
if (isDeepEqual === undefined) lazyLoadComparison();
456+
457+
// Create a map to count occurrences of each element in the expected array
458+
const expectedCounts = new SafeMap();
459+
for (const expectedItem of expected) {
460+
let found = false;
461+
for (const { 0: key, 1: count } of expectedCounts) {
462+
if (isDeepStrictEqual(key, expectedItem)) {
463+
MapPrototypeSet(expectedCounts, key, count + 1);
464+
found = true;
465+
break;
466+
}
467+
}
468+
if (!found) {
469+
MapPrototypeSet(expectedCounts, expectedItem, 1);
470+
}
471+
}
472+
473+
// Create a map to count occurrences of relevant elements in the actual array
474+
for (const actualItem of actual) {
475+
for (const { 0: key, 1: count } of expectedCounts) {
476+
if (isDeepStrictEqual(key, actualItem)) {
477+
if (count === 1) {
478+
MapPrototypeDelete(expectedCounts, key);
479+
} else {
480+
MapPrototypeSet(expectedCounts, key, count - 1);
481+
}
482+
break;
483+
}
484+
}
485+
}
486+
487+
return !expectedCounts.size;
488+
}
489+
490+
// Comparison done when at least one of the values is not an object
491+
if (isSpecial(actual) || isSpecial(expected)) {
492+
if (isDeepEqual === undefined) {
493+
lazyLoadComparison();
494+
}
495+
return isDeepStrictEqual(actual, expected);
496+
}
497+
498+
// Use Reflect.ownKeys() instead of Object.keys() to include symbol properties
499+
const keysExpected = ReflectOwnKeys(expected);
500+
501+
comparedObjects ??= new SafeWeakSet();
502+
503+
// Handle circular references
504+
if (comparedObjects.has(actual)) {
505+
return true;
506+
}
507+
comparedObjects.add(actual);
508+
509+
// Check if all expected keys and values match
510+
for (let i = 0; i < keysExpected.length; i++) {
511+
const key = keysExpected[i];
512+
assert(
513+
ReflectHas(actual, key),
514+
new AssertionError({ message: `Expected key ${String(key)} not found in actual object` }),
515+
);
516+
if (!compareBranch(actual[key], expected[key], comparedObjects)) {
517+
return false;
518+
}
519+
}
520+
521+
return true;
522+
}
523+
524+
/**
525+
* The strict equivalence assertion test between two objects
526+
* @param {any} actual
527+
* @param {any} expected
528+
* @param {string | Error} [message]
529+
* @returns {void}
530+
*/
531+
assert.partialDeepStrictEqual = function partialDeepStrictEqual(
532+
actual,
533+
expected,
534+
message,
535+
) {
536+
emitExperimentalWarning('assert.partialDeepStrictEqual');
537+
if (arguments.length < 2) {
538+
throw new ERR_MISSING_ARGS('actual', 'expected');
539+
}
540+
541+
if (!compareBranch(actual, expected)) {
542+
innerFail({
543+
actual,
544+
expected,
545+
message,
546+
operator: 'partialDeepStrictEqual',
547+
stackStartFn: partialDeepStrictEqual,
548+
});
549+
}
550+
};
551+
344552
class Comparison {
345553
constructor(obj, keys, actual) {
346554
for (const key of keys) {

‎lib/internal/test_runner/test.js

+1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ function lazyAssertObject(harness) {
119119
'notDeepStrictEqual',
120120
'notEqual',
121121
'notStrictEqual',
122+
'partialDeepStrictEqual',
122123
'rejects',
123124
'strictEqual',
124125
'throws',

‎test/parallel/test-assert-objects.js

+491
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,491 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const vm = require('node:vm');
5+
const assert = require('node:assert');
6+
const { describe, it } = require('node:test');
7+
8+
function createCircularObject() {
9+
const obj = {};
10+
obj.self = obj;
11+
return obj;
12+
}
13+
14+
function createDeepNestedObject() {
15+
return { level1: { level2: { level3: 'deepValue' } } };
16+
}
17+
18+
async function generateCryptoKey() {
19+
const { KeyObject } = require('node:crypto');
20+
const { subtle } = globalThis.crypto;
21+
22+
const cryptoKey = await subtle.generateKey(
23+
{
24+
name: 'HMAC',
25+
hash: 'SHA-256',
26+
length: 256,
27+
},
28+
true,
29+
['sign', 'verify']
30+
);
31+
32+
const keyObject = KeyObject.from(cryptoKey);
33+
34+
return { cryptoKey, keyObject };
35+
}
36+
37+
describe('Object Comparison Tests', () => {
38+
describe('partialDeepStrictEqual', () => {
39+
describe('throws an error', () => {
40+
const tests = [
41+
{
42+
description: 'throws when only one argument is provided',
43+
actual: { a: 1 },
44+
expected: undefined,
45+
},
46+
{
47+
description: 'throws when expected has more properties than actual',
48+
actual: [1, 'two'],
49+
expected: [1, 'two', true],
50+
},
51+
{
52+
description: 'throws because expected has seven 2 while actual has six one',
53+
actual: [1, 2, 2, 2, 2, 2, 2, 3],
54+
expected: [1, 2, 2, 2, 2, 2, 2, 2],
55+
},
56+
{
57+
description: 'throws when comparing two different sets with objects',
58+
actual: new Set([{ a: 1 }]),
59+
expected: new Set([{ a: 1 }, { b: 1 }]),
60+
},
61+
62+
{
63+
description: 'throws when comparing two different objects',
64+
actual: { a: 1, b: 'string' },
65+
expected: { a: 2, b: 'string' },
66+
},
67+
{
68+
description:
69+
'throws when comparing two objects with different nested objects',
70+
actual: createDeepNestedObject(),
71+
expected: { level1: { level2: { level3: 'differentValue' } } },
72+
},
73+
{
74+
description:
75+
'throws when comparing two objects with different RegExp properties',
76+
actual: { pattern: /abc/ },
77+
expected: { pattern: /def/ },
78+
},
79+
{
80+
description:
81+
'throws when comparing two arrays with different elements',
82+
actual: [1, 'two', true],
83+
expected: [1, 'two', false],
84+
},
85+
{
86+
description:
87+
'throws when comparing two Date objects with different times',
88+
actual: new Date(0),
89+
expected: new Date(1),
90+
},
91+
{
92+
description:
93+
'throws when comparing two objects with different large number of properties',
94+
actual: Object.fromEntries(
95+
Array.from({ length: 100 }, (_, i) => [`key${i}`, i])
96+
),
97+
expected: Object.fromEntries(
98+
Array.from({ length: 100 }, (_, i) => [`key${i}`, i + 1])
99+
),
100+
},
101+
{
102+
description:
103+
'throws when comparing two objects with different Symbols',
104+
actual: { [Symbol('test')]: 'symbol' },
105+
expected: { [Symbol('test')]: 'symbol' },
106+
},
107+
{
108+
description:
109+
'throws when comparing two objects with different array properties',
110+
actual: { a: [1, 2, 3] },
111+
expected: { a: [1, 2, 4] },
112+
},
113+
{
114+
description:
115+
'throws when comparing two objects with different function properties',
116+
actual: { fn: () => {} },
117+
expected: { fn: () => {} },
118+
},
119+
{
120+
description:
121+
'throws when comparing two objects with different Error instances',
122+
actual: { error: new Error('Test error 1') },
123+
expected: { error: new Error('Test error 2') },
124+
},
125+
{
126+
description:
127+
'throws when comparing two objects with different TypedArray instances and content',
128+
actual: { typedArray: new Uint8Array([1, 2, 3]) },
129+
expected: { typedArray: new Uint8Array([4, 5, 6]) },
130+
},
131+
{
132+
description:
133+
'throws when comparing two Map objects with different entries',
134+
actual: new Map([
135+
['key1', 'value1'],
136+
['key2', 'value2'],
137+
]),
138+
expected: new Map([
139+
['key1', 'value1'],
140+
['key3', 'value3'],
141+
]),
142+
},
143+
{
144+
description:
145+
'throws when comparing two Map objects with different keys',
146+
actual: new Map([
147+
['key1', 'value1'],
148+
['key2', 'value2'],
149+
]),
150+
expected: new Map([
151+
['key1', 'value1'],
152+
['key3', 'value2'],
153+
]),
154+
},
155+
{
156+
description:
157+
'throws when comparing two Map objects with different length',
158+
actual: new Map([
159+
['key1', 'value1'],
160+
['key2', 'value2'],
161+
]),
162+
expected: new Map([['key1', 'value1']]),
163+
},
164+
{
165+
description:
166+
'throws when comparing two TypedArray instances with different content',
167+
actual: new Uint8Array(10),
168+
expected: () => {
169+
const typedArray2 = new Int8Array(10);
170+
Object.defineProperty(typedArray2, Symbol.toStringTag, {
171+
value: 'Uint8Array'
172+
});
173+
Object.setPrototypeOf(typedArray2, Uint8Array.prototype);
174+
175+
return typedArray2;
176+
},
177+
},
178+
{
179+
description:
180+
'throws when comparing two Set objects from different realms with different values',
181+
actual: new vm.runInNewContext('new Set(["value1", "value2"])'),
182+
expected: new Set(['value1', 'value3']),
183+
},
184+
{
185+
description:
186+
'throws when comparing two Set objects with different values',
187+
actual: new Set(['value1', 'value2']),
188+
expected: new Set(['value1', 'value3']),
189+
},
190+
{
191+
description: 'throws when comparing one subset object with another',
192+
actual: { a: 1, b: 2, c: 3 },
193+
expected: { b: '2' },
194+
},
195+
{
196+
description: 'throws when comparing one subset array with another',
197+
actual: [1, 2, 3],
198+
expected: ['2'],
199+
},
200+
];
201+
202+
if (common.hasCrypto) {
203+
tests.push({
204+
description:
205+
'throws when comparing two objects with different CryptoKey instances objects',
206+
actual: async () => {
207+
return generateCryptoKey();
208+
},
209+
expected: async () => {
210+
return generateCryptoKey();
211+
},
212+
});
213+
214+
const { createSecretKey } = require('node:crypto');
215+
216+
tests.push({
217+
description:
218+
'throws when comparing two objects with different KeyObject instances objects',
219+
actual: createSecretKey(Buffer.alloc(1, 0)),
220+
expected: createSecretKey(Buffer.alloc(1, 1)),
221+
});
222+
}
223+
224+
tests.forEach(({ description, actual, expected }) => {
225+
it(description, () => {
226+
assert.throws(() => assert.partialDeepStrictEqual(actual, expected), Error);
227+
});
228+
});
229+
});
230+
});
231+
232+
describe('does not throw an error', () => {
233+
const sym = Symbol('test');
234+
const func = () => {};
235+
236+
[
237+
{
238+
description: 'compares two identical simple objects',
239+
actual: { a: 1, b: 'string' },
240+
expected: { a: 1, b: 'string' },
241+
},
242+
{
243+
description: 'compares two objects with different property order',
244+
actual: { a: 1, b: 'string' },
245+
expected: { b: 'string', a: 1 },
246+
},
247+
{
248+
description: 'compares two deeply nested objects with partial equality',
249+
actual: { a: { nested: { property: true, some: 'other' } } },
250+
expected: { a: { nested: { property: true } } },
251+
},
252+
{
253+
description:
254+
'compares plain objects from different realms',
255+
actual: vm.runInNewContext(`({
256+
a: 1,
257+
b: 2n,
258+
c: "3",
259+
d: /4/,
260+
e: new Set([5]),
261+
f: [6],
262+
g: new Uint8Array()
263+
})`),
264+
expected: { b: 2n, e: new Set([5]), f: [6], g: new Uint8Array() },
265+
},
266+
{
267+
description: 'compares two integers',
268+
actual: 1,
269+
expected: 1,
270+
},
271+
{
272+
description: 'compares two strings',
273+
actual: '1',
274+
expected: '1',
275+
},
276+
{
277+
description: 'compares two objects with nested objects',
278+
actual: createDeepNestedObject(),
279+
expected: createDeepNestedObject(),
280+
},
281+
{
282+
description: 'compares two objects with circular references',
283+
actual: createCircularObject(),
284+
expected: createCircularObject(),
285+
},
286+
{
287+
description: 'compares two arrays with identical elements',
288+
actual: [1, 'two', true],
289+
expected: [1, 'two', true],
290+
},
291+
{
292+
description: 'compares two Date objects with the same time',
293+
actual: new Date(0),
294+
expected: new Date(0),
295+
},
296+
{
297+
description: 'compares two objects with large number of properties',
298+
actual: Object.fromEntries(
299+
Array.from({ length: 100 }, (_, i) => [`key${i}`, i])
300+
),
301+
expected: Object.fromEntries(
302+
Array.from({ length: 100 }, (_, i) => [`key${i}`, i])
303+
),
304+
},
305+
{
306+
description: 'compares two objects with Symbol properties',
307+
actual: { [sym]: 'symbol' },
308+
expected: { [sym]: 'symbol' },
309+
},
310+
{
311+
description: 'compares two objects with RegExp properties',
312+
actual: { pattern: /abc/ },
313+
expected: { pattern: /abc/ },
314+
},
315+
{
316+
description: 'compares two objects with identical function properties',
317+
actual: { fn: func },
318+
expected: { fn: func },
319+
},
320+
{
321+
description: 'compares two objects with mixed types of properties',
322+
actual: { num: 1, str: 'test', bool: true, sym },
323+
expected: { num: 1, str: 'test', bool: true, sym },
324+
},
325+
{
326+
description: 'compares two objects with Buffers',
327+
actual: { buf: Buffer.from('Node.js') },
328+
expected: { buf: Buffer.from('Node.js') },
329+
},
330+
{
331+
description: 'compares two objects with identical Error properties',
332+
actual: { error: new Error('Test error') },
333+
expected: { error: new Error('Test error') },
334+
},
335+
{
336+
description: 'compares two objects with TypedArray instances with the same content',
337+
actual: { typedArray: new Uint8Array([1, 2, 3]) },
338+
expected: { typedArray: new Uint8Array([1, 2, 3]) },
339+
},
340+
{
341+
description: 'compares two Map objects with identical entries',
342+
actual: new Map([
343+
['key1', 'value1'],
344+
['key2', 'value2'],
345+
]),
346+
expected: new Map([
347+
['key1', 'value1'],
348+
['key2', 'value2'],
349+
]),
350+
},
351+
{
352+
describe: 'compares two array of objects',
353+
actual: [{ a: 5 }],
354+
expected: [{ a: 5 }],
355+
},
356+
{
357+
describe: 'compares two array of objects where expected is a subset of actual',
358+
actual: [{ a: 5 }, { b: 5 }],
359+
expected: [{ a: 5 }],
360+
},
361+
{
362+
description: 'compares two Set objects with identical objects',
363+
actual: new Set([{ a: 1 }]),
364+
expected: new Set([{ a: 1 }]),
365+
},
366+
{
367+
description: 'compares two Set objects where expected is a subset of actual',
368+
actual: new Set([{ a: 1 }, { b: 1 }]),
369+
expected: new Set([{ a: 1 }]),
370+
},
371+
{
372+
description: 'compares two Set objects with identical arrays',
373+
actual: new Set(['value1', 'value2']),
374+
expected: new Set(['value1', 'value2']),
375+
},
376+
{
377+
description: 'compares two Set objects',
378+
actual: new Set(['value1', 'value2', 'value3']),
379+
expected: new Set(['value1', 'value2']),
380+
},
381+
{
382+
description:
383+
'compares two Map objects from different realms with identical entries',
384+
actual: new vm.runInNewContext(
385+
'new Map([["key1", "value1"], ["key2", "value2"]])'
386+
),
387+
expected: new Map([
388+
['key1', 'value1'],
389+
['key2', 'value2'],
390+
]),
391+
},
392+
{
393+
description:
394+
'compares two objects with identical getter/setter properties',
395+
actual: (() => {
396+
let value = 'test';
397+
return Object.defineProperty({}, 'prop', {
398+
get: () => value,
399+
set: (newValue) => {
400+
value = newValue;
401+
},
402+
enumerable: true,
403+
configurable: true,
404+
});
405+
})(),
406+
expected: (() => {
407+
let value = 'test';
408+
return Object.defineProperty({}, 'prop', {
409+
get: () => value,
410+
set: (newValue) => {
411+
value = newValue;
412+
},
413+
enumerable: true,
414+
configurable: true,
415+
});
416+
})(),
417+
},
418+
{
419+
description: 'compares two objects with no prototype',
420+
actual: { __proto__: null, prop: 'value' },
421+
expected: { __proto__: null, prop: 'value' },
422+
},
423+
{
424+
description:
425+
'compares two objects with identical non-enumerable properties',
426+
actual: (() => {
427+
const obj = {};
428+
Object.defineProperty(obj, 'hidden', {
429+
value: 'secret',
430+
enumerable: false,
431+
});
432+
return obj;
433+
})(),
434+
expected: (() => {
435+
const obj = {};
436+
Object.defineProperty(obj, 'hidden', {
437+
value: 'secret',
438+
enumerable: false,
439+
});
440+
return obj;
441+
})(),
442+
},
443+
{
444+
description: 'compares two identical primitives, string',
445+
actual: 'foo',
446+
expected: 'foo',
447+
},
448+
{
449+
description: 'compares two identical primitives, number',
450+
actual: 1,
451+
expected: 1,
452+
},
453+
{
454+
description: 'compares two identical primitives, boolean',
455+
actual: false,
456+
expected: false,
457+
},
458+
{
459+
description: 'compares two identical primitives, null',
460+
actual: null,
461+
expected: null,
462+
},
463+
{
464+
description: 'compares two identical primitives, undefined',
465+
actual: undefined,
466+
expected: undefined,
467+
},
468+
{
469+
description: 'compares two identical primitives, Symbol',
470+
actual: sym,
471+
expected: sym,
472+
},
473+
{
474+
description:
475+
'compares one subset object with another',
476+
actual: { a: 1, b: 2, c: 3 },
477+
expected: { b: 2 },
478+
},
479+
{
480+
description:
481+
'compares one subset array with another',
482+
actual: [1, 2, 3],
483+
expected: [2],
484+
},
485+
].forEach(({ description, actual, expected }) => {
486+
it(description, () => {
487+
assert.partialDeepStrictEqual(actual, expected);
488+
});
489+
});
490+
});
491+
});

0 commit comments

Comments
 (0)
Please sign in to comment.