Skip to content

Commit 8f997a5

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

File tree

2 files changed

+107
-21
lines changed

2 files changed

+107
-21
lines changed

lib/assert.js

Lines changed: 57 additions & 21 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;
@@ -506,39 +515,66 @@ function partiallyCompareArrays(actual, expected, comparedObjects) {
506515

507516
// Create a map to count occurrences of each element in the expected array
508517
const expectedCounts = new SafeMap();
509-
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;
518+
const safeExpected = new SafeArrayIterator(expected);
519+
520+
for (const expectedItem of safeExpected) {
521+
// Check if the item is a zero or a -0, as these need to be handled separately
522+
if (expectedItem === 0) {
523+
const zeroKey = getZeroKey(expectedItem);
524+
expectedCounts.set(zeroKey, {
525+
count: (expectedCounts.get(zeroKey)?.count || 0) + 1,
526+
expectedType: typeof expectedItem,
527+
});
528+
} else {
529+
let found = false;
530+
for (const { 0: key, 1: { count, expectedType } } of expectedCounts) {
531+
// eslint-disable-next-line valid-typeof
532+
if (isDeepStrictEqual(key, expectedItem) && expectedType === typeof expectedItem) {
533+
expectedCounts.set(key, { count: count + 1, expectedType });
534+
found = true;
535+
break;
536+
}
537+
}
538+
if (!found) {
539+
expectedCounts.set(expectedItem, { count: 1, expectedType: typeof expectedItem });
516540
}
517-
}
518-
if (!found) {
519-
expectedCounts.set(expectedItem, 1);
520541
}
521542
}
522543

523544
const safeActual = new SafeArrayIterator(actual);
524545

525-
// Create a map to count occurrences of relevant elements in the actual array
526546
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);
547+
// Check if the item is a zero or a -0, as these need to be handled separately
548+
if (actualItem === 0) {
549+
const zeroKey = getZeroKey(actualItem);
550+
551+
if (expectedCounts.has(zeroKey)) {
552+
const { count, expectedType } = expectedCounts.get(zeroKey);
553+
// eslint-disable-next-line valid-typeof
554+
if (expectedType === typeof actualItem) {
555+
if (count === 1) {
556+
expectedCounts.delete(zeroKey);
557+
} else {
558+
expectedCounts.set(zeroKey, { count: count - 1, expectedType });
559+
}
560+
}
561+
}
562+
} else {
563+
for (const { 0: expectedItem, 1: { count, expectedType } } of expectedCounts) {
564+
// eslint-disable-next-line valid-typeof
565+
if (isDeepStrictEqual(expectedItem, actualItem) && expectedType === typeof actualItem) {
566+
if (count === 1) {
567+
expectedCounts.delete(expectedItem);
568+
} else {
569+
expectedCounts.set(expectedItem, { count: count - 1, expectedType });
570+
}
571+
break;
533572
}
534-
break;
535573
}
536574
}
537575
}
538576

539-
const { size } = expectedCounts;
540-
expectedCounts.clear();
541-
return size === 0;
577+
return expectedCounts.size === 0;
542578
}
543579

544580
/**

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)