Skip to content

Commit a0d1929

Browse files
committed
assert: make partialDeepStrictEqual throw when comparing [0] with [-0]
Fixes: #56230
1 parent a1d980c commit a0d1929

File tree

2 files changed

+104
-20
lines changed

2 files changed

+104
-20
lines changed

lib/assert.js

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,15 @@ function partiallyCompareSets(actual, expected, comparedObjects) {
497497
return true;
498498
}
499499

500+
// Helper function to get a unique key for 0, -0 to avoid collisions
501+
function getZeroKey(item) {
502+
if (item === 0) {
503+
if (ObjectIs(item, -0)) return '-0';
504+
return '0';
505+
}
506+
return item;
507+
}
508+
500509
function partiallyCompareArrays(actual, expected, comparedObjects) {
501510
if (expected.length > actual.length) {
502511
return false;
@@ -507,38 +516,63 @@ function partiallyCompareArrays(actual, expected, comparedObjects) {
507516
// Create a map to count occurrences of each element in the expected array
508517
const expectedCounts = new SafeMap();
509518
for (const expectedItem of expected) {
510-
let found = false;
511-
for (const { 0: key, 1: count } of expectedCounts) {
512-
if (isDeepStrictEqual(key, expectedItem)) {
513-
expectedCounts.set(key, count + 1);
514-
found = true;
515-
break;
519+
// Check if the item is a zero or a -0, as these need to be handled separately
520+
if (expectedItem === 0) {
521+
const zeroKey = getZeroKey(expectedItem);
522+
expectedCounts.set(zeroKey, {
523+
count: (expectedCounts.get(zeroKey)?.count || 0) + 1,
524+
expectedType: typeof expectedItem,
525+
});
526+
} else {
527+
let found = false;
528+
for (const { 0: key, 1: { count, expectedType } } of expectedCounts) {
529+
// eslint-disable-next-line valid-typeof
530+
if (isDeepStrictEqual(key, expectedItem) && expectedType === typeof expectedItem) {
531+
expectedCounts.set(key, { count: count + 1, expectedType });
532+
found = true;
533+
break;
534+
}
535+
}
536+
if (!found) {
537+
expectedCounts.set(expectedItem, { count: 1, expectedType: typeof expectedItem });
516538
}
517-
}
518-
if (!found) {
519-
expectedCounts.set(expectedItem, 1);
520539
}
521540
}
522541

523542
const safeActual = new SafeArrayIterator(actual);
524543

525-
// Create a map to count occurrences of relevant elements in the actual array
526544
for (const actualItem of safeActual) {
527-
for (const { 0: key, 1: count } of expectedCounts) {
528-
if (isDeepStrictEqual(key, actualItem)) {
529-
if (count === 1) {
530-
expectedCounts.delete(key);
531-
} else {
532-
expectedCounts.set(key, count - 1);
545+
// Check if the item is a zero or a -0, as these need to be handled separately
546+
if (actualItem === 0) {
547+
const zeroKey = getZeroKey(actualItem);
548+
549+
if (expectedCounts.has(zeroKey)) {
550+
const { count, expectedType } = expectedCounts.get(zeroKey);
551+
// eslint-disable-next-line valid-typeof
552+
if (expectedType === typeof actualItem) {
553+
if (count === 1) {
554+
expectedCounts.delete(zeroKey);
555+
} else {
556+
expectedCounts.set(zeroKey, { count: count - 1, expectedType });
557+
}
558+
}
559+
}
560+
} else {
561+
for (const { 0: expectedItem, 1: { count, expectedType } } of expectedCounts) {
562+
// eslint-disable-next-line valid-typeof
563+
if (isDeepStrictEqual(expectedItem, actualItem) && expectedType === typeof actualItem) {
564+
if (count === 1) {
565+
expectedCounts.delete(expectedItem);
566+
} else {
567+
expectedCounts.set(expectedItem, { count: count - 1, expectedType });
568+
}
569+
break;
533570
}
534-
break;
535571
}
536572
}
537573
}
538574

539-
const { size } = expectedCounts;
540-
expectedCounts.clear();
541-
return size === 0;
575+
return expectedCounts.size === 0;
542576
}
543577

544578
/**

test/parallel/test-assert-objects.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,41 @@ describe('Object Comparison Tests', () => {
9797
actual: [1, 'two', true],
9898
expected: [1, 'two', false],
9999
},
100+
{
101+
description: 'throws when comparing [0] with [-0]',
102+
actual: [0],
103+
expected: [-0],
104+
},
105+
{
106+
description: 'throws when comparing [0, 0, 0] with [0, -0]',
107+
actual: [0, 0, 0],
108+
expected: [0, -0],
109+
},
110+
{
111+
description: 'throws when comparing [-0] with [0]',
112+
actual: [0],
113+
expected: [-0],
114+
},
115+
{
116+
description: 'throws when comparing ["-0"] with [-0]',
117+
actual: ['-0'],
118+
expected: [-0],
119+
},
120+
{
121+
description: 'throws when comparing [-0] with ["-0"]',
122+
actual: [-0],
123+
expected: ['-0'],
124+
},
125+
{
126+
description: 'throws when comparing ["0"] with [0]',
127+
actual: ['0'],
128+
expected: [0],
129+
},
130+
{
131+
description: 'throws when comparing [0] with ["0"]',
132+
actual: [0],
133+
expected: ['0'],
134+
},
100135
{
101136
description:
102137
'throws when comparing two Date objects with different times',
@@ -385,6 +420,21 @@ describe('Object Comparison Tests', () => {
385420
actual: [1, 'two', true],
386421
expected: [1, 'two', true],
387422
},
423+
{
424+
description: 'compares [0] with [0]',
425+
actual: [0],
426+
expected: [0],
427+
},
428+
{
429+
description: 'compares [-0] with [-0]',
430+
actual: [-0],
431+
expected: [-0],
432+
},
433+
{
434+
description: 'compares [0, -0, 0] with [0, 0]',
435+
actual: [0, -0, 0],
436+
expected: [0, 0],
437+
},
388438
{
389439
description: 'compares two Date objects with the same time',
390440
actual: new Date(0),

0 commit comments

Comments
 (0)