fix(assert): partialDeepStrictEqual now correctly handles Map subset checking (#26257)

## Summary

- Fixed `assert.partialDeepStrictEqual` to correctly handle Map subset
checking
- Previously, Map comparison used `Bun.deepEquals` which required exact
equality
- Now properly checks that all entries in the expected Map exist in the
actual Map with matching values

Fixes #24338

## Test plan

- Added comprehensive test suite in
`test/regression/issue/24338.test.ts` covering:
  - Basic subset checking (key2 in Map with key1 and key2)
  - Exact match cases
  - Empty expected Map
  - Multiple matching entries
  - Nested objects as values
  - Failure cases when expected has more keys
  - Failure cases when key is missing in actual
  - Failure cases when values differ
  - Nested Map values
  - Non-string keys

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
robobun
2026-01-19 17:10:47 -08:00
committed by GitHub
parent 362839c987
commit 04f441453d
2 changed files with 144 additions and 2 deletions

View File

@@ -377,6 +377,9 @@ function isSpecial(obj) {
const typesToCallDeepStrictEqualWith = [isKeyObject, isWeakSet, isWeakMap, Buffer.isBuffer];
const SafeSetPrototypeIterator = SafeSet.prototype[SymbolIterator];
const SafeMapPrototypeIterator = SafeMap.prototype[SymbolIterator];
const SafeMapPrototypeHas = SafeMap.prototype.has;
const SafeMapPrototypeGet = SafeMap.prototype.get;
/**
* Compares two objects or values recursively to check if they are equal.
@@ -388,9 +391,33 @@ const SafeSetPrototypeIterator = SafeSet.prototype[SymbolIterator];
* compareBranch({a: 1, b: 2, c: 3}, {a: 1, b: 2}); // true
*/
function compareBranch(actual, expected, comparedObjects?) {
// Check for Map object equality
// Check for Map object equality (subset check for partialDeepStrictEqual)
if (isMap(actual) && isMap(expected)) {
return Bun.deepEquals(actual, expected, true);
if (expected.size > actual.size) {
return false; // `expected` can't be a subset if it has more elements
}
comparedObjects ??= new SafeWeakSet();
// Handle circular references
if (comparedObjects.has(actual)) {
return true;
}
comparedObjects.add(actual);
const expectedIterator = SafeMapPrototypeIterator.$call(expected);
for (const { 0: key, 1: expectedValue } of expectedIterator) {
if (!SafeMapPrototypeHas.$call(actual, key)) {
return false;
}
const actualValue = SafeMapPrototypeGet.$call(actual, key);
if (!compareBranch(actualValue, expectedValue, comparedObjects)) {
return false;
}
}
return true;
}
// Check for ArrayBuffer object equality

View File

@@ -0,0 +1,115 @@
import assert from "node:assert";
import { test } from "node:test";
// https://github.com/oven-sh/bun/issues/24338
// assert.partialDeepStrictEqual should support Map subset checking
test("partialDeepStrictEqual with Map subset - basic case", () => {
// The expected Map is a subset of actual Map
assert.partialDeepStrictEqual(
new Map([
["key1", "value1"],
["key2", "value2"],
]),
new Map([["key2", "value2"]]),
);
});
test("partialDeepStrictEqual with Map - exact match", () => {
assert.partialDeepStrictEqual(new Map([["key1", "value1"]]), new Map([["key1", "value1"]]));
});
test("partialDeepStrictEqual with Map - empty expected", () => {
assert.partialDeepStrictEqual(new Map([["key1", "value1"]]), new Map());
});
test("partialDeepStrictEqual with Map - multiple matching entries", () => {
assert.partialDeepStrictEqual(
new Map([
["a", 1],
["b", 2],
["c", 3],
]),
new Map([
["a", 1],
["c", 3],
]),
);
});
test("partialDeepStrictEqual with Map - nested objects as values", () => {
assert.partialDeepStrictEqual(
new Map([
["config", { debug: true, verbose: false }],
["data", { items: [1, 2, 3] }],
]),
new Map([["config", { debug: true }]]),
);
});
test("partialDeepStrictEqual with Map - should fail when expected has more keys", () => {
assert.throws(
() =>
assert.partialDeepStrictEqual(
new Map([["key1", "value1"]]),
new Map([
["key1", "value1"],
["key2", "value2"],
]),
),
assert.AssertionError,
);
});
test("partialDeepStrictEqual with Map - should fail when key missing in actual", () => {
assert.throws(
() => assert.partialDeepStrictEqual(new Map([["key1", "value1"]]), new Map([["key2", "value2"]])),
assert.AssertionError,
);
});
test("partialDeepStrictEqual with Map - should fail when value differs", () => {
assert.throws(
() => assert.partialDeepStrictEqual(new Map([["key1", "value1"]]), new Map([["key1", "different"]])),
assert.AssertionError,
);
});
test("partialDeepStrictEqual with Map - nested Map values", () => {
assert.partialDeepStrictEqual(
new Map([
[
"outer",
new Map([
["inner1", 1],
["inner2", 2],
]),
],
]),
new Map([["outer", new Map([["inner1", 1]])]]),
);
});
test("partialDeepStrictEqual with Map - non-string keys", () => {
const objKey = { id: 1 };
assert.partialDeepStrictEqual(
new Map([
[1, "one"],
[objKey, "object"],
[true, "boolean"],
]),
new Map([[1, "one"]]),
);
});
test("partialDeepStrictEqual with Map - circular reference", () => {
const actualMap = new Map<string, unknown>();
actualMap.set("self", actualMap);
actualMap.set("other", "value");
const expectedMap = new Map<string, unknown>();
expectedMap.set("self", expectedMap);
// Should not hang due to circular reference
assert.partialDeepStrictEqual(actualMap, expectedMap);
});