diff --git a/src/js/node/assert.ts b/src/js/node/assert.ts index 0507b5c620..3a19cd7c07 100644 --- a/src/js/node/assert.ts +++ b/src/js/node/assert.ts @@ -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 diff --git a/test/regression/issue/24338.test.ts b/test/regression/issue/24338.test.ts new file mode 100644 index 0000000000..d7ee09675e --- /dev/null +++ b/test/regression/issue/24338.test.ts @@ -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(); + actualMap.set("self", actualMap); + actualMap.set("other", "value"); + + const expectedMap = new Map(); + expectedMap.set("self", expectedMap); + + // Should not hang due to circular reference + assert.partialDeepStrictEqual(actualMap, expectedMap); +});