Repository: nodejs/node
Pull request: assert: add partialDeepStrictEqual

Fixes: https://github.com/nodejs/node/issues/50399

Took heavy inspiration from https://github.com/nodejs/node/pull/53415 , trying to push it to the finish line 🚀 

On top of it, I took the liberty of:

1. refactor the code a bit
2. wrote more documentation
3. added more tests and restructured old ones
4. implement previously reported comments / suggestions

<details open><summary>Notable change</summary>

#### Introducing experimental `assert.partialDeepStrictEqual`

Sometimes, when writing test, we want to validate that some specific properties
are present, and the mere presence of additional keys are not exactly relevant
for that specific test. For this use case, we can now use
`assert.partialDeepStrictEqual`, which should be familiar to those already using
`assert.deepStrictEqual`, with the main difference that it does not require all
properties in the `actual` parameter to be present in the `expected` parameter.

Here are a few examples of usage:

```js
assert.partialDeepStrictEqual(
  { a: 1, b: 2, c: 3 },
  { a: 1, b: 2 },
);

assert.partialDeepStrictEqual(
  [1, 2, 3, 4],
  [2, 3],
);

assert.partialDeepStrictEqual(
  { a: { b: { c: 1, d: 2 } }, e: 3 },
  { a: { b: { c: 1 } } },
);

assert.partialDeepStrictEqual(
  { a: { b: { c: 1, d: 2 } }, e: 3 },
  { a: { b: { c: 1 } } },
);

assert.partialDeepStrictEqual(
  new Set([{ a: 1 }, { b: 1 }]),
  new Set([{ a: 1 }]),
);

assert.partialDeepStrictEqual(
  { a: new Set([{ a: 1 }, { b: 1 }]), b: new Map(), c: [1, 2, 3] },
  { a: new Set([{ a: 1 }]), c: [2] },
);
```

Co-Authored-By: Cristian Barlutiu

</details>

Changed files:
filename: doc/api/assert.md
patch: @@ -2548,6 +2548,96 @@ assert.throws(throwingFirst, /Second$/);
 Due to the confusing error-prone notation, avoid a string as the second
 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.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring
 [`!=` operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Inequality
@@ -2576,6 +2666,7 @@ argument.
 [`assert.notEqual()`]: #assertnotequalactual-expected-message
 [`assert.notStrictEqual()`]: #assertnotstrictequalactual-expected-message
 [`assert.ok()`]: #assertokvalue-message
+[`assert.partialDeepStrictEqual()`]: #assertpartialdeepstrictequalactual-expected-message
 [`assert.strictEqual()`]: #assertstrictequalactual-expected-message
 [`assert.throws()`]: #assertthrowsfn-error-message
 [`getColorDepth()`]: tty.md#writestreamgetcolordepthenv

filename: lib/assert.js
patch: @@ -21,22 +21,35 @@
 'use strict';
 
 const {
+  ArrayFrom,
+  ArrayIsArray,
   ArrayPrototypeIndexOf,
   ArrayPrototypeJoin,
   ArrayPrototypePush,
   ArrayPrototypeSlice,
   Error,
+  FunctionPrototypeCall,
+  MapPrototypeDelete,
+  MapPrototypeGet,
+  MapPrototypeHas,
+  MapPrototypeSet,
   NumberIsNaN,
   ObjectAssign,
   ObjectIs,
   ObjectKeys,
   ObjectPrototypeIsPrototypeOf,
   ReflectApply,
+  ReflectHas,
+  ReflectOwnKeys,
   RegExpPrototypeExec,
+  SafeMap,
+  SafeSet,
+  SafeWeakSet,
   String,
   StringPrototypeIndexOf,
   StringPrototypeSlice,
   StringPrototypeSplit,
+  SymbolIterator,
 } = primordials;
 
 const {
@@ -50,8 +63,18 @@ const {
 } = require('internal/errors');
 const AssertionError = require('internal/assert/assertion_error');
 const { inspect } = require('internal/util/inspect');
-const { isPromise, isRegExp } = require('internal/util/types');
-const { isError, deprecate } = require('internal/util');
+const { Buffer } = require('buffer');
+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 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 {
   constructor(obj, keys, actual) {
     for (const key of keys) {

filename: lib/internal/test_runner/test.js
patch: @@ -119,6 +119,7 @@ function lazyAssertObject(harness) {
       'notDeepStrictEqual',
       'notEqual',
       'notStrictEqual',
+      'partialDeepStrictEqual',
       'rejects',
       'strictEqual',
       'throws',

filename: test/parallel/test-assert-objects.js
patch: @@ -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);
+      });
+    });
+  });
+});