Skip to content

Commit 4fa9ec7

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

File tree

2 files changed

+109
-20
lines changed

2 files changed

+109
-20
lines changed

lib/assert.js

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

500+
function isZeroOrMinusZero(item) {
501+
return ObjectIs(item, -0) || ObjectIs(item, 0);
502+
}
503+
504+
// Helper function to get a unique key for 0, -0 to avoid collisions
505+
function getZeroKey(item) {
506+
if (ObjectIs(item, 0)) return '0';
507+
if (ObjectIs(item, -0)) return '-0';
508+
return item;
509+
}
510+
500511
function partiallyCompareArrays(actual, expected, comparedObjects) {
501512
if (expected.length > actual.length) {
502513
return false;
@@ -507,38 +518,66 @@ function partiallyCompareArrays(actual, expected, comparedObjects) {
507518
// Create a map to count occurrences of each element in the expected array
508519
const expectedCounts = new SafeMap();
509520
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;
521+
// Check if the item is a zero or a -0, as these need to be handled separately
522+
if (isZeroOrMinusZero(expectedItem)) {
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+
// This is a very ugly solution to not make eslint valid-typeof rule whine
532+
const haveSameType = expectedType === (typeof expectedItem === 'number' ? 'number' : typeof expectedItem);
533+
if (isDeepStrictEqual(key, expectedItem) && haveSameType) {
534+
expectedCounts.set(key, { count: count + 1, expectedType });
535+
found = true;
536+
break;
537+
}
538+
}
539+
if (!found) {
540+
expectedCounts.set(expectedItem, { count: 1, expectedType: typeof expectedItem });
516541
}
517-
}
518-
if (!found) {
519-
expectedCounts.set(expectedItem, 1);
520542
}
521543
}
522544

523545
const safeActual = new SafeArrayIterator(actual);
524546

525-
// Create a map to count occurrences of relevant elements in the actual array
526547
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);
548+
// Check if the item is a zero or a -0, as these need to be handled separately
549+
if (isZeroOrMinusZero(actualItem)) {
550+
const zeroKey = getZeroKey(actualItem);
551+
552+
if (expectedCounts.has(zeroKey)) {
553+
const { count, expectedType } = expectedCounts.get(zeroKey);
554+
// This is a very ugly solution to not make eslint valid-typeof rule whine
555+
const haveSameType = expectedType === (typeof actualItem === 'number' ? 'number' : typeof actualItem);
556+
if (haveSameType) {
557+
if (count === 1) {
558+
expectedCounts.delete(zeroKey);
559+
} else {
560+
expectedCounts.set(zeroKey, { count: count - 1, expectedType });
561+
}
562+
}
563+
}
564+
} else {
565+
for (const { 0: expectedItem, 1: { count, expectedType } } of expectedCounts) {
566+
// This is a very ugly solution to not make eslint valid-typeof rule whine
567+
const haveSameType = expectedType === (typeof actualItem === 'number' ? 'number' : typeof actualItem);
568+
if (isDeepStrictEqual(expectedItem, actualItem) && haveSameType) {
569+
if (count === 1) {
570+
expectedCounts.delete(expectedItem);
571+
} else {
572+
expectedCounts.set(expectedItem, { count: count - 1, expectedType });
573+
}
574+
break;
533575
}
534-
break;
535576
}
536577
}
537578
}
538579

539-
const { size } = expectedCounts;
540-
expectedCounts.clear();
541-
return size === 0;
580+
return expectedCounts.size === 0;
542581
}
543582

544583
/**

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)