From d17ef5ff6dcacb7ddb3e60512d40d66202e14ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominique=20J=C3=A4ggi?= <1872195+solaris007@users.noreply.github.com> Date: Thu, 4 Jul 2024 12:08:19 +0200 Subject: [PATCH 1/2] feat: utils deepEqual (#282) --- .../spacecat-shared-utils/src/functions.js | 40 +++++- packages/spacecat-shared-utils/src/index.d.ts | 2 + .../test/functions.test.js | 124 ++++++++++++++++++ 3 files changed, 165 insertions(+), 1 deletion(-) diff --git a/packages/spacecat-shared-utils/src/functions.js b/packages/spacecat-shared-utils/src/functions.js index e616c6052..ca93cfb32 100644 --- a/packages/spacecat-shared-utils/src/functions.js +++ b/packages/spacecat-shared-utils/src/functions.js @@ -63,7 +63,7 @@ function isNumber(value) { * @returns {boolean} True if the parameter is an object, false otherwise. */ function isObject(value) { - return !isArray(value) && value !== null && typeof value === 'object'; + return value !== null && typeof value === 'object' && !isArray(value); } /** @@ -75,6 +75,43 @@ function isNonEmptyObject(value) { return isObject(value) && Object.keys(value).length > 0; } +/** + * Deeply compares two objects or arrays for equality. Supports nested objects and arrays. + * Does not support circular references. Does not compare functions. + * @param {unknown} x - The first object or array to compare. + * @param {unknown} y - The second object or array to compare. + * @return {boolean} True if the objects or arrays are equal, false otherwise. + */ +function deepEqual(x, y) { + if (x === y) return true; + + if (isArray(x) && isArray(y)) { + if (x.length !== y.length) return false; + for (let i = 0; i < x.length; i += 1) { + if (!deepEqual(x[i], y[i])) return false; + } + return true; + } + + if (!isObject(x) || !isObject(y)) return false; + + if (x.constructor !== y.constructor) return false; + + if (x instanceof Date) return x.getTime() === y.getTime(); + if (x instanceof RegExp) return x.toString() === y.toString(); + + const xKeys = Object.keys(x).filter((key) => typeof x[key] !== 'function'); + const yKeys = Object.keys(y).filter((key) => typeof y[key] !== 'function'); + + if (xKeys.length !== yKeys.length) return false; + + for (const key of xKeys) { + if (!Object.prototype.hasOwnProperty.call(y, key) || !deepEqual(x[key], y[key])) return false; + } + + return true; +} + /** * Determines if the given parameter is a string. * @@ -210,4 +247,5 @@ export { toBoolean, isValidUrl, dateAfterDays, + deepEqual, }; diff --git a/packages/spacecat-shared-utils/src/index.d.ts b/packages/spacecat-shared-utils/src/index.d.ts index 42e6a0cd1..db02683e4 100644 --- a/packages/spacecat-shared-utils/src/index.d.ts +++ b/packages/spacecat-shared-utils/src/index.d.ts @@ -39,6 +39,8 @@ export function isValidUrl(urlString: string): boolean; export function dateAfterDays(days: number, dateString: string): Date; +export function deepEqual(a: unknown, b: unknown): boolean; + export function sqsWrapper(fn: (message: object, context: object) => Promise): (request: object, context: object) => Promise; diff --git a/packages/spacecat-shared-utils/test/functions.test.js b/packages/spacecat-shared-utils/test/functions.test.js index a409be201..fd25e695f 100644 --- a/packages/spacecat-shared-utils/test/functions.test.js +++ b/packages/spacecat-shared-utils/test/functions.test.js @@ -32,6 +32,7 @@ import { arrayEquals, isValidUrl, dateAfterDays, + deepEqual, } from '../src/functions.js'; describe('Shared functions', () => { @@ -334,4 +335,127 @@ describe('Shared functions', () => { expect(sevenDaysEarlier.toISOString()).to.equal(sevenDaysEarlierExpected); }); }); + + describe('deepEqual', () => { + it('returns true for two identical primitive values', () => { + expect(deepEqual(1, 1)).to.be.true; + expect(deepEqual('hello', 'hello')).to.be.true; + expect(deepEqual(true, true)).to.be.true; + }); + + it('returns false for two different primitive values', () => { + expect(deepEqual(1, 2)).to.be.false; + expect(deepEqual('hello', 'world')).to.be.false; + expect(deepEqual(true, false)).to.be.false; + }); + + it('returns true for two identical arrays', () => { + expect(deepEqual([1, 2, 3], [1, 2, 3])).to.be.true; + expect(deepEqual(['a', 'b', 'c'], ['a', 'b', 'c'])).to.be.true; + }); + + it('returns false for two different arrays', () => { + expect(deepEqual([1, 2, 3], [1, 2, 4])).to.be.false; + expect(deepEqual(['a', 'b', 'c'], ['a', 'b', 'd'])).to.be.false; + }); + + it('returns true for two identical objects', () => { + expect(deepEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).to.be.true; + expect(deepEqual({ x: 'hello', y: 'world' }, { x: 'hello', y: 'world' })).to.be.true; + }); + + it('returns false for two different objects', () => { + expect(deepEqual({ a: 1, b: 2 }, { a: 1, b: 3 })).to.be.false; + expect(deepEqual({ x: 'hello', y: 'world' }, { x: 'hello', y: 'earth' })).to.be.false; + }); + + it('returns true for deeply nested identical objects', () => { + const obj1 = { a: { b: { c: 1 } }, d: 2 }; + const obj2 = { a: { b: { c: 1 } }, d: 2 }; + expect(deepEqual(obj1, obj2)).to.be.true; + }); + + it('returns false for deeply nested different objects', () => { + const obj1 = { a: { b: { c: 1 } }, d: 2 }; + const obj2 = { a: { b: { c: 2 } }, d: 2 }; + expect(deepEqual(obj1, obj2)).to.be.false; + }); + + it('returns true for two identical Date objects', () => { + const date1 = new Date('2021-01-01'); + const date2 = new Date('2021-01-01'); + expect(deepEqual(date1, date2)).to.be.true; + }); + + it('returns false for two different Date objects', () => { + const date1 = new Date('2021-01-01'); + const date2 = new Date('2022-01-01'); + expect(deepEqual(date1, date2)).to.be.false; + }); + + it('returns true for two identical RegExp objects', () => { + const regex1 = /test/i; + const regex2 = /test/i; + expect(deepEqual(regex1, regex2)).to.be.true; + }); + + it('returns false for two different RegExp objects', () => { + const regex1 = /test/i; + const regex2 = /test/g; + expect(deepEqual(regex1, regex2)).to.be.false; + }); + + it('returns false for objects with different constructors', () => { + function Person(name) { + this.name = name; + } + const person1 = new Person('John'); + const person2 = { name: 'John' }; + expect(deepEqual(person1, person2)).to.be.false; + }); + + it('returns true for nested arrays', () => { + const arr1 = [1, [2, [3, 4]]]; + const arr2 = [1, [2, [3, 4]]]; + expect(deepEqual(arr1, arr2)).to.be.true; + }); + + it('returns false for different nested arrays', () => { + const arr1 = [1, [2, [3, 4]]]; + const arr2 = [1, [2, [4, 3]]]; + expect(deepEqual(arr1, arr2)).to.be.false; + }); + + it('returns false for arrays of different lengths', () => { + const arr1 = [1, 2, 3]; + const arr2 = [1, 2, 3, 4]; + expect(deepEqual(arr1, arr2)).to.be.false; + }); + + it('returns false for objects with different number of keys', () => { + const obj1 = { a: 1, b: 2 }; + const obj2 = { a: 1 }; + expect(deepEqual(obj1, obj2)).to.be.false; + }); + + it('returns true for objects with identical keys and values in different order', () => { + const obj1 = { a: 1, b: 2, c: 3 }; + const obj2 = { c: 3, b: 2, a: 1 }; + expect(deepEqual(obj1, obj2)).to.be.true; + }); + + it('returns true for objects with function properties when functions are ignored', () => { + const obj1 = { a: 1, b: () => 2 }; + const obj2 = { a: 1, b: () => 3 }; + expect(deepEqual(obj1, obj2)).to.be.true; + }); + + it('returns true for objects with different function references when functions are ignored', () => { + const func1 = () => 2; + const func2 = () => 3; + const obj1 = { a: 1, b: func1 }; + const obj2 = { a: 1, b: func2 }; + expect(deepEqual(obj1, obj2)).to.be.true; + }); + }); }); From d206ee3e3f62efe64fac92088bd022140aef2cd8 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 4 Jul 2024 10:11:15 +0000 Subject: [PATCH 2/2] chore(release): 1.18.0 [skip ci] # [@adobe/spacecat-shared-utils-v1.18.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.17.1...@adobe/spacecat-shared-utils-v1.18.0) (2024-07-04) ### Features * utils deepEqual ([#282](https://github.com/adobe/spacecat-shared/issues/282)) ([d17ef5f](https://github.com/adobe/spacecat-shared/commit/d17ef5ff6dcacb7ddb3e60512d40d66202e14ffc)) --- packages/spacecat-shared-utils/CHANGELOG.md | 7 +++++++ packages/spacecat-shared-utils/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/spacecat-shared-utils/CHANGELOG.md b/packages/spacecat-shared-utils/CHANGELOG.md index 81314c39d..7d716a812 100644 --- a/packages/spacecat-shared-utils/CHANGELOG.md +++ b/packages/spacecat-shared-utils/CHANGELOG.md @@ -1,3 +1,10 @@ +# [@adobe/spacecat-shared-utils-v1.18.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.17.1...@adobe/spacecat-shared-utils-v1.18.0) (2024-07-04) + + +### Features + +* utils deepEqual ([#282](https://github.com/adobe/spacecat-shared/issues/282)) ([d17ef5f](https://github.com/adobe/spacecat-shared/commit/d17ef5ff6dcacb7ddb3e60512d40d66202e14ffc)) + # [@adobe/spacecat-shared-utils-v1.17.1](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-utils-v1.17.0...@adobe/spacecat-shared-utils-v1.17.1) (2024-07-02) diff --git a/packages/spacecat-shared-utils/package.json b/packages/spacecat-shared-utils/package.json index 7f4bc06fb..1037e9749 100644 --- a/packages/spacecat-shared-utils/package.json +++ b/packages/spacecat-shared-utils/package.json @@ -1,6 +1,6 @@ { "name": "@adobe/spacecat-shared-utils", - "version": "1.17.1", + "version": "1.18.0", "description": "Shared modules of the Spacecat Services - utils", "type": "module", "main": "src/index.js",