diff --git a/package-lock.json b/package-lock.json index 2229c6b..32773b5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@types/jest": "^24.0.23", "@typescript-eslint/eslint-plugin": "^2.11.0", "@typescript-eslint/parser": "^2.11.0", + "dequal": "^2.0.3", "eslint": "^6.8.0", "eslint-plugin-jest": "^23.1.1", "eslint-plugin-json": "^2.0.1", @@ -5572,6 +5573,15 @@ "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", "dev": true }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", @@ -21862,6 +21872,12 @@ "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", "dev": true }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true + }, "detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", diff --git a/package.json b/package.json index 1c5829b..bc7fda9 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "@types/jest": "^24.0.23", "@typescript-eslint/eslint-plugin": "^2.11.0", "@typescript-eslint/parser": "^2.11.0", + "dequal": "^2.0.3", "eslint": "^6.8.0", "eslint-plugin-jest": "^23.1.1", "eslint-plugin-json": "^2.0.1", diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index bfb75f6..9a90124 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -1,5 +1,6 @@ +import { dequal } from "dequal"; import { validArrayValue } from "../arrays"; -import { shallowEqual, shallowEqualArrays } from "../index"; +import { eq, shallowEqual, shallowEqualArrays } from "../index"; import shallowEqualObjects, { validObjectValue } from "../objects"; const arr = [1, 2, 3]; @@ -117,34 +118,101 @@ const objTests: { }, ]; -describe("shallowEqual on arrays", () => { - arrTests.forEach((test) => { - it("should " + test.should, () => { - expect(shallowEqual(test.arrA, test.arrB)).toEqual(test.result); +describe("shallow-equal", () => { + describe("shallowEqual on arrays", () => { + arrTests.forEach((test) => { + it("should " + test.should, () => { + expect(shallowEqual(test.arrA, test.arrB)).toEqual(test.result); + }); }); }); -}); -describe("shallowEqual on objects", () => { - objTests.forEach((test) => { - it("should " + test.should, () => { - expect(shallowEqual(test.objA, test.objB)).toEqual(test.result); + describe("shallowEqual on objects", () => { + objTests.forEach((test) => { + it("should " + test.should, () => { + expect(shallowEqual(test.objA, test.objB)).toEqual(test.result); + }); }); }); -}); -describe("shallowEqualObjects", () => { - objTests.forEach((test) => { - it("should " + test.should, () => { - expect(shallowEqualObjects(test.objA, test.objB)).toEqual(test.result); + describe("shallowEqualObjects", () => { + objTests.forEach((test) => { + it("should " + test.should, () => { + expect(shallowEqualObjects(test.objA, test.objB)).toEqual(test.result); + }); }); }); -}); -describe("shallowEqualArrays", () => { - arrTests.forEach((test) => { - it("should " + test.should, () => { - expect(shallowEqualArrays(test.arrA, test.arrB)).toEqual(test.result); + describe("shallowEqualArrays", () => { + arrTests.forEach((test) => { + it("should " + test.should, () => { + expect(shallowEqualArrays(test.arrA, test.arrB)).toEqual(test.result); + }); + }); + }); + + describe("custom comparators", () => { + it("should use a custom comparator", () => { + const comparator = jest.fn((a: any, b: any) => a === b); + const arrA = [1, 2, 3]; + const arrB = [1, 2, 3]; + + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore: this'll go away when #33 lands + expect(shallowEqual(arrA, arrB, comparator)).toEqual(true); + expect(comparator).toHaveBeenCalledTimes(3); + + const badComparator = jest.fn((a: any, b: any) => a !== b); + + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore: this'll go away when #33 lands + expect(shallowEqual(arrA, arrB, badComparator)).toEqual(false); }); }); + + it("should use deep-equal equivalents correctly on objects", () => { + const objA = { first: obj1, second: obj2 }; + const objB = { second: { language: "elm" }, first: obj1 }; + const objC = { second: { hi: "there", what: { depth: 3 } }, first: obj1 }; + const objD = { second: { hi: "there", what: { depth: 3 } }, first: obj1 }; + + expect(eq(objA, objB)).toEqual(false); + expect(dequal(objA, objB)).toEqual(true); + + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore: this'll go away when #33 lands + expect(shallowEqual(objA, objB, dequal)).toEqual(true); + expect(shallowEqualObjects(objA, objB, dequal)).toEqual(true); + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore: this'll go away when #33 lands + expect(shallowEqualObjects(objA, objC, dequal)).toEqual(false); + expect(shallowEqualObjects(objC, objD)).toEqual(false); + expect(shallowEqualObjects(objC, objD, dequal)).toEqual(true); + }); + + it("should use deep-equal equivalents correctly on arrays", () => { + const arrA = [{ first: obj1, second: obj2 }]; + const arrB = [{ second: { language: "elm" }, first: obj1 }]; + const arrC = [ + arrA, + { second: { hi: "there", what: { depth: 3 } }, first: obj1 }, + ]; + const arrD = [ + arrB, + { second: { hi: "there", what: { depth: 3 } }, first: obj1 }, + ]; + + expect(eq(arrA, arrB)).toEqual(false); + expect(dequal(arrA, arrB)).toEqual(true); + + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore: this'll go away when #33 lands + expect(shallowEqual(arrA, arrB, dequal)).toEqual(true); + expect(shallowEqualArrays(arrA, arrB, dequal)).toEqual(true); + // eslint-disable-next-line @typescript-eslint/ban-ts-ignore + // @ts-ignore: this'll go away when #33 lands + expect(shallowEqualArrays(arrA, arrC, dequal)).toEqual(false); + expect(shallowEqualArrays(arrC, arrD)).toEqual(false); + expect(shallowEqualArrays(arrC, arrD, dequal)).toEqual(true); + }); }); diff --git a/src/arrays.ts b/src/arrays.ts index 7360018..7f01353 100644 --- a/src/arrays.ts +++ b/src/arrays.ts @@ -1,8 +1,11 @@ +import { Comparator, eq } from "./index"; + export type validArrayValue = T[] | null | undefined; export default function shallowEqualArrays( arrA: validArrayValue, - arrB: validArrayValue + arrB: validArrayValue, + comparator: Comparator = eq ): boolean { if (arrA === arrB) { return true; @@ -19,7 +22,7 @@ export default function shallowEqualArrays( } for (let i = 0; i < len; i++) { - if (arrA[i] !== arrB[i]) { + if (!comparator(arrA[i], arrB[i])) { return false; } } diff --git a/src/index.ts b/src/index.ts index 87a412b..c886268 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,14 +3,28 @@ import shallowEqualObjects, { validObjectValue } from "./objects"; type Comparable = Record | T[] | null | undefined; -function shallowEqual>(a: T, b: T): boolean { +export type Comparator = (a: any, b: any) => boolean; +export function eq(a: T, b: T): boolean { + return a === b; +} + +function shallowEqual>( + a: T, + b: T, + comparator: Comparator = eq +): boolean { if (Array.isArray(a) || Array.isArray(b)) { - return shallowEqualArrays(a as validArrayValue, b as validArrayValue); + return shallowEqualArrays( + a as validArrayValue, + b as validArrayValue, + comparator + ); } return shallowEqualObjects( a as validObjectValue, - b as validObjectValue + b as validObjectValue, + comparator ); } diff --git a/src/objects.ts b/src/objects.ts index 5356839..1a3c04c 100644 --- a/src/objects.ts +++ b/src/objects.ts @@ -1,8 +1,11 @@ +import { Comparator, eq } from "./index"; + export type validObjectValue = Record | null | undefined; export default function shallowEqualObjects( objA: validObjectValue, - objB: validObjectValue + objB: validObjectValue, + comparator: Comparator = eq ): boolean { if (objA === objB) { return true; @@ -20,11 +23,9 @@ export default function shallowEqualObjects( return false; } - for (let i = 0; i < len; i++) { - const key = aKeys[i]; - + for (const key of aKeys) { if ( - objA[key] !== objB[key] || + !comparator(objA[key], objB[key]) || !Object.prototype.hasOwnProperty.call(objB, key) ) { return false;