Skip to content

Commit 730f9b6

Browse files
committed
feat: enhances deepEqual with comprehensive type support
Adds support for NaN comparison, typed arrays, circular reference detection, symbol keys, constructor validation, and improved Map key comparison using deep equality Extends test coverage with edge cases including sparse arrays, invalid dates, cross-references, and getter properties • Version bump to 0.8.1
1 parent 02c1621 commit 730f9b6

4 files changed

Lines changed: 344 additions & 14 deletions

File tree

jsr.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sillvva/utils",
3-
"version": "0.8.0",
3+
"version": "0.8.1",
44
"exports": {
55
".": "./src/entry.ts"
66
},

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sillvva/utils",
3-
"version": "0.8.0",
3+
"version": "0.8.1",
44
"description": "A module containing general purpose utility functions and types.",
55
"author": "Matt DeKok",
66
"license": "MIT",

src/deepEqual.test.ts

Lines changed: 269 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe("deepEqual", () => {
2727
});
2828

2929
it("handles NaN", () => {
30-
expect(deepEqual(NaN, NaN)).toBe(false); // NaN === NaN is false
30+
expect(deepEqual(NaN, NaN)).toBe(true); // NaN === NaN is false
3131
expect(deepEqual(NaN, 0)).toBe(false);
3232
expect(deepEqual(NaN, "NaN")).toBe(false);
3333
});
@@ -93,6 +93,49 @@ describe("deepEqual", () => {
9393
const obj2 = { a: null, b: undefined };
9494
expect(deepEqual(obj1, obj2)).toBe(true);
9595
});
96+
97+
it("handles objects with symbol keys", () => {
98+
const sym = Symbol("test");
99+
const obj1 = { [sym]: "value", a: 1 };
100+
const obj2 = { [sym]: "value", a: 1 };
101+
expect(deepEqual(obj1, obj2)).toBe(true);
102+
});
103+
104+
it("returns false for objects with different symbol values", () => {
105+
const sym = Symbol("test");
106+
const obj1 = { [sym]: "value1", a: 1 };
107+
const obj2 = { [sym]: "value2", a: 1 };
108+
expect(deepEqual(obj1, obj2)).toBe(false);
109+
});
110+
111+
it("returns false for objects with different symbol keys", () => {
112+
const sym1 = Symbol("test1");
113+
const sym2 = Symbol("test2");
114+
const obj1 = { [sym1]: "value" };
115+
const obj2 = { [sym2]: "value" };
116+
expect(deepEqual(obj1, obj2)).toBe(false);
117+
});
118+
119+
it("returns false for objects with different constructors", () => {
120+
class ClassA {
121+
constructor(public value: number) {}
122+
}
123+
class ClassB {
124+
constructor(public value: number) {}
125+
}
126+
const obj1 = new ClassA(1);
127+
const obj2 = new ClassB(1);
128+
expect(deepEqual(obj1, obj2)).toBe(false);
129+
});
130+
131+
it("returns true for objects with same constructor", () => {
132+
class MyClass {
133+
constructor(public value: number) {}
134+
}
135+
const obj1 = new MyClass(1);
136+
const obj2 = new MyClass(1);
137+
expect(deepEqual(obj1, obj2)).toBe(true);
138+
});
96139
});
97140

98141
describe("arrays", () => {
@@ -154,6 +197,12 @@ describe("deepEqual", () => {
154197
const arr2 = [null, undefined, 1];
155198
expect(deepEqual(arr1, arr2)).toBe(true);
156199
});
200+
201+
it("returns false for arrays with different order", () => {
202+
const arr1 = [1, 2, 3];
203+
const arr2 = [3, 2, 1];
204+
expect(deepEqual(arr1, arr2)).toBe(false);
205+
});
157206
});
158207

159208
describe("Date objects", () => {
@@ -174,6 +223,12 @@ describe("deepEqual", () => {
174223
expect(deepEqual(date, "2023-01-01T00:00:00Z")).toBe(false);
175224
expect(deepEqual(date, 1672531200000)).toBe(false);
176225
});
226+
227+
it("handles invalid dates", () => {
228+
const invalid1 = new Date("invalid");
229+
const invalid2 = new Date("invalid");
230+
expect(deepEqual(invalid1, invalid2)).toBe(true); // Both are NaN timestamps
231+
});
177232
});
178233

179234
describe("RegExp objects", () => {
@@ -243,6 +298,18 @@ describe("deepEqual", () => {
243298
const set = new Set([1, 2, 3]);
244299
expect(deepEqual(set, [1, 2, 3])).toBe(false);
245300
});
301+
302+
it("handles sets with nested objects", () => {
303+
const set1 = new Set([{ a: { b: 1 } }]);
304+
const set2 = new Set([{ a: { b: 1 } }]);
305+
expect(deepEqual(set1, set2)).toBe(true);
306+
});
307+
308+
it("handles sets with NaN", () => {
309+
const set1 = new Set([NaN, 1, 2]);
310+
const set2 = new Set([NaN, 1, 2]);
311+
expect(deepEqual(set1, set2)).toBe(true);
312+
});
246313
});
247314

248315
describe("Map objects", () => {
@@ -309,7 +376,7 @@ describe("deepEqual", () => {
309376
const key2 = { id: 1 };
310377
const map1 = new Map([[key1, "value"]]);
311378
const map2 = new Map([[key2, "value"]]);
312-
expect(deepEqual(map1, map2)).toBe(false); // Map uses reference equality for keys
379+
expect(deepEqual(map1, map2)).toBe(true); // Map uses deep equality for keys
313380
});
314381

315382
it("returns false when comparing map with non-map", () => {
@@ -319,13 +386,141 @@ describe("deepEqual", () => {
319386
]);
320387
expect(deepEqual(map, { a: 1, b: 2 })).toBe(false);
321388
});
389+
390+
it("handles maps with nested objects as keys", () => {
391+
const key1 = { nested: { id: 1 } };
392+
const key2 = { nested: { id: 1 } };
393+
const map1 = new Map([[key1, "value"]]);
394+
const map2 = new Map([[key2, "value"]]);
395+
expect(deepEqual(map1, map2)).toBe(true);
396+
});
397+
398+
it("handles maps with NaN as keys", () => {
399+
const map1 = new Map([[NaN, "value"]]);
400+
const map2 = new Map([[NaN, "value"]]);
401+
expect(deepEqual(map1, map2)).toBe(true);
402+
});
403+
});
404+
405+
describe("TypedArray objects", () => {
406+
it("returns true for identical Int8Array", () => {
407+
const arr1 = new Int8Array([1, 2, 3]);
408+
const arr2 = new Int8Array([1, 2, 3]);
409+
expect(deepEqual(arr1, arr2)).toBe(true);
410+
});
411+
412+
it("returns true for identical Uint8Array", () => {
413+
const arr1 = new Uint8Array([1, 2, 3]);
414+
const arr2 = new Uint8Array([1, 2, 3]);
415+
expect(deepEqual(arr1, arr2)).toBe(true);
416+
});
417+
418+
it("returns true for identical Float32Array", () => {
419+
const arr1 = new Float32Array([1.1, 2.2, 3.3]);
420+
const arr2 = new Float32Array([1.1, 2.2, 3.3]);
421+
expect(deepEqual(arr1, arr2)).toBe(true);
422+
});
423+
424+
it("returns false for different TypedArray values", () => {
425+
const arr1 = new Uint8Array([1, 2, 3]);
426+
const arr2 = new Uint8Array([1, 2, 4]);
427+
expect(deepEqual(arr1, arr2)).toBe(false);
428+
});
429+
430+
it("returns false for TypedArrays with different lengths", () => {
431+
const arr1 = new Uint8Array([1, 2, 3]);
432+
const arr2 = new Uint8Array([1, 2]);
433+
expect(deepEqual(arr1, arr2)).toBe(false);
434+
});
435+
436+
it("returns false for different TypedArray types", () => {
437+
const arr1 = new Int8Array([1, 2, 3]);
438+
const arr2 = new Uint8Array([1, 2, 3]);
439+
expect(deepEqual(arr1, arr2)).toBe(false);
440+
});
441+
442+
it("returns false when comparing TypedArray with regular Array", () => {
443+
const arr1 = new Uint8Array([1, 2, 3]);
444+
const arr2 = [1, 2, 3];
445+
expect(deepEqual(arr1, arr2)).toBe(false);
446+
});
447+
448+
it("handles empty TypedArrays", () => {
449+
const arr1 = new Uint8Array([]);
450+
const arr2 = new Uint8Array([]);
451+
expect(deepEqual(arr1, arr2)).toBe(true);
452+
});
453+
454+
it("handles Int16Array", () => {
455+
const arr1 = new Int16Array([100, 200, 300]);
456+
const arr2 = new Int16Array([100, 200, 300]);
457+
expect(deepEqual(arr1, arr2)).toBe(true);
458+
});
459+
460+
it("handles Uint32Array", () => {
461+
const arr1 = new Uint32Array([1000, 2000, 3000]);
462+
const arr2 = new Uint32Array([1000, 2000, 3000]);
463+
expect(deepEqual(arr1, arr2)).toBe(true);
464+
});
465+
466+
it("handles Float64Array", () => {
467+
const arr1 = new Float64Array([1.123456789, 2.987654321]);
468+
const arr2 = new Float64Array([1.123456789, 2.987654321]);
469+
expect(deepEqual(arr1, arr2)).toBe(true);
470+
});
471+
});
472+
473+
describe("circular references", () => {
474+
it("handles circular references in objects", () => {
475+
const obj1: any = { a: 1 };
476+
obj1.self = obj1;
477+
const obj2: any = { a: 1 };
478+
obj2.self = obj2;
479+
expect(deepEqual(obj1, obj2)).toBe(true);
480+
});
481+
482+
it("handles circular references in arrays", () => {
483+
const arr1: any = [1, 2];
484+
arr1.push(arr1);
485+
const arr2: any = [1, 2];
486+
arr2.push(arr2);
487+
expect(deepEqual(arr1, arr2)).toBe(true);
488+
});
489+
490+
it("handles nested circular references", () => {
491+
const obj1: any = { a: { b: {} } };
492+
obj1.a.b.c = obj1;
493+
const obj2: any = { a: { b: {} } };
494+
obj2.a.b.c = obj2;
495+
expect(deepEqual(obj1, obj2)).toBe(true);
496+
});
497+
498+
it("returns false for different circular structures", () => {
499+
const obj1: any = { a: 1 };
500+
obj1.self = obj1;
501+
const obj2: any = { a: 2 };
502+
obj2.self = obj2;
503+
expect(deepEqual(obj1, obj2)).toBe(false);
504+
});
505+
506+
it("handles cross-referencing objects", () => {
507+
const obj1a: any = { name: "a" };
508+
const obj1b: any = { name: "b", ref: obj1a };
509+
obj1a.ref = obj1b;
510+
511+
const obj2a: any = { name: "a" };
512+
const obj2b: any = { name: "b", ref: obj2a };
513+
obj2a.ref = obj2b;
514+
515+
expect(deepEqual(obj1a, obj2a)).toBe(true);
516+
});
322517
});
323518

324519
describe("mixed types", () => {
325520
it("returns false when comparing different types", () => {
326521
expect(deepEqual(1, "1")).toBe(false);
327522
expect(deepEqual(true, 1)).toBe(false);
328-
expect(deepEqual([], {})).toBe(true); // Empty array vs empty object - both have no enumerable keys
523+
expect(deepEqual([], {})).toBe(false);
329524
expect(deepEqual(new Date(), "2023-01-01")).toBe(false);
330525
expect(deepEqual(/test/, "test")).toBe(false);
331526
});
@@ -409,5 +604,76 @@ describe("deepEqual", () => {
409604
Object.defineProperty(obj2, "hidden", { value: "different", enumerable: false });
410605
expect(deepEqual(obj1, obj2)).toBe(true); // Non-enumerable properties are ignored
411606
});
607+
608+
it("handles same object reference", () => {
609+
const obj = { a: 1 };
610+
expect(deepEqual(obj, obj)).toBe(true);
611+
});
612+
613+
it("handles arrays with sparse elements", () => {
614+
const arr1 = [1, , 3]; // eslint-disable-line no-sparse-arrays
615+
const arr2 = [1, , 3]; // eslint-disable-line no-sparse-arrays
616+
expect(deepEqual(arr1, arr2)).toBe(true);
617+
});
618+
619+
it("returns false for sparse arrays with different undefined", () => {
620+
const arr1 = [1, , 3]; // eslint-disable-line no-sparse-arrays
621+
const arr2 = [1, undefined, 3];
622+
// Sparse arrays have "holes" that are different from explicit undefined
623+
expect(deepEqual(arr1, arr2)).toBe(true); // Both will be treated the same by our implementation
624+
});
625+
626+
it("handles Buffer objects", () => {
627+
if (typeof Buffer !== "undefined") {
628+
const buf1 = Buffer.from([1, 2, 3]);
629+
const buf2 = Buffer.from([1, 2, 3]);
630+
expect(deepEqual(buf1, buf2)).toBe(true);
631+
}
632+
});
633+
634+
it("handles empty strings vs falsy values", () => {
635+
expect(deepEqual("", 0)).toBe(false);
636+
expect(deepEqual("", false)).toBe(false);
637+
expect(deepEqual("", null)).toBe(false);
638+
expect(deepEqual("", undefined)).toBe(false);
639+
});
640+
641+
it("handles zero and negative zero", () => {
642+
expect(deepEqual(0, -0)).toBe(true); // 0 === -0 is true
643+
expect(deepEqual(+0, -0)).toBe(true);
644+
});
645+
646+
it("handles objects with getter properties", () => {
647+
const obj1 = {
648+
get value() {
649+
return 42;
650+
}
651+
};
652+
const obj2 = {
653+
get value() {
654+
return 42;
655+
}
656+
};
657+
expect(deepEqual(obj1, obj2)).toBe(true); // Compares the returned values
658+
});
659+
660+
it("handles Error objects", () => {
661+
const err1 = new Error("test");
662+
const err2 = new Error("test");
663+
// Errors are objects, so they'll be compared by their enumerable properties
664+
expect(deepEqual(err1, err2)).toBe(true);
665+
});
666+
667+
it("handles objects with numeric string keys", () => {
668+
const obj1 = { "0": "a", "1": "b", "2": "c" };
669+
const obj2 = { "0": "a", "1": "b", "2": "c" };
670+
expect(deepEqual(obj1, obj2)).toBe(true);
671+
});
672+
673+
it("returns false when comparing object with array with same numeric keys", () => {
674+
const obj = { "0": "a", "1": "b", "2": "c" };
675+
const arr = ["a", "b", "c"];
676+
expect(deepEqual(obj, arr)).toBe(false);
677+
});
412678
});
413679
});

0 commit comments

Comments
 (0)