From 7c0e49b4f3c5d65d81c6cec0f05248c742de7e9b Mon Sep 17 00:00:00 2001 From: William Thorenfeldt <48119543+wrt95@users.noreply.github.com> Date: Wed, 30 Oct 2024 22:18:38 +0100 Subject: [PATCH] chore: move objectUtils to @studio/pure-functions (#13938) --- .../src/ObjectUtils/ObjectUtils.test.ts | 88 ++++++++++++++++--- .../src/ObjectUtils/ObjectUtils.ts | 44 ++++++++++ .../src/types/KeyValuePairs.ts | 3 + .../packages/shared/src/utils/dndUtils.ts | 4 +- .../shared/src/utils/objectUtils.test.ts | 48 ---------- .../packages/shared/src/utils/objectUtils.ts | 43 --------- .../externalLayoutToInternal.ts | 6 +- .../externalLayoutToInternal.ts | 6 +- .../ux-editor/src/utils/formLayoutUtils.ts | 9 +- 9 files changed, 133 insertions(+), 118 deletions(-) create mode 100644 frontend/libs/studio-pure-functions/src/types/KeyValuePairs.ts delete mode 100644 frontend/packages/shared/src/utils/objectUtils.test.ts delete mode 100644 frontend/packages/shared/src/utils/objectUtils.ts diff --git a/frontend/libs/studio-pure-functions/src/ObjectUtils/ObjectUtils.test.ts b/frontend/libs/studio-pure-functions/src/ObjectUtils/ObjectUtils.test.ts index ee831e65fdf..a797b0cfbbb 100644 --- a/frontend/libs/studio-pure-functions/src/ObjectUtils/ObjectUtils.test.ts +++ b/frontend/libs/studio-pure-functions/src/ObjectUtils/ObjectUtils.test.ts @@ -1,24 +1,84 @@ import { ObjectUtils } from './ObjectUtils'; -describe('deepCopy', () => { - it('should create a deep copy of an object', () => { - const originalObject = { - test: 'Test', - test2: 25, - }; +describe('objectUtils', () => { + describe('deepCopy', () => { + it('should create a deep copy of an object', () => { + const originalObject = { + test: 'Test', + test2: 25, + }; - const copiedObject = ObjectUtils.deepCopy(originalObject); + const copiedObject = ObjectUtils.deepCopy(originalObject); - expect(copiedObject).toEqual(originalObject); - expect(copiedObject).not.toBe(originalObject); + expect(copiedObject).toEqual(originalObject); + expect(copiedObject).not.toBe(originalObject); + }); + + it('should create a deep copy of an array', () => { + const originalArray = [1, 2, [3, 4]]; + + const copiedArray = ObjectUtils.deepCopy(originalArray); + + expect(copiedArray).toEqual(originalArray); + expect(copiedArray).not.toBe(originalArray); + }); + }); + + describe('areObjectsEqual', () => { + it('Returns true if objects are equal', () => { + expect(ObjectUtils.areObjectsEqual({}, {})).toBe(true); + expect(ObjectUtils.areObjectsEqual({ a: 1 }, { a: 1 })).toBe(true); + expect(ObjectUtils.areObjectsEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true); + expect(ObjectUtils.areObjectsEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 })).toBe(true); + }); + + it('Returns false if objects are not equal', () => { + expect(ObjectUtils.areObjectsEqual({ a: 1 }, { a: 2 })).toBe(false); + expect(ObjectUtils.areObjectsEqual({ a: 1, b: 2 }, { a: 1, b: 3 })).toBe(false); + expect(ObjectUtils.areObjectsEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 4 })).toBe(false); + }); + + it('should return true for two empty objects', () => { + expect(ObjectUtils.areObjectsEqual({}, {})).toBe(true); + }); + + it('should return true for identical objects (reference equality)', () => { + const obj1 = { a: 1, b: 'test' }; + expect(ObjectUtils.areObjectsEqual(obj1, obj1)).toBe(true); + }); + + it('should return false if the length of the objects are not equally length', () => { + expect(ObjectUtils.areObjectsEqual({ a: 1, b: 2 }, { a: 1 })).toBe(false); + }); }); - it('should create a deep copy of an array', () => { - const originalArray = [1, 2, [3, 4]]; + describe('mapByProperty', () => { + const property = 'id'; + const value1 = 'value1'; + const value2 = 'value2'; + const value3 = 'value3'; + const object1 = { [property]: value1 }; + const object2 = { [property]: value2, otherProperty: 'Some irrelevant value' }; + const object3 = { [property]: value3, otherProperty: 'Another irrelevant value' }; - const copiedArray = ObjectUtils.deepCopy(originalArray); + it('Maps an array of objects to a key-value pair object, where the key is the value of the property', () => { + const objectList = [object1, object2, object3]; + expect(ObjectUtils.mapByProperty(objectList, property)).toEqual({ + [value1]: object1, + [value2]: object2, + [value3]: object3, + }); + }); + }); - expect(copiedArray).toEqual(originalArray); - expect(copiedArray).not.toBe(originalArray); + describe('flattenObjectValues', () => { + it('Flattens the values of an object', () => { + const object = { + a: 'value1', + b: 'value2', + c: 'value3', + }; + expect(ObjectUtils.flattenObjectValues(object)).toEqual(['value1', 'value2', 'value3']); + }); }); }); diff --git a/frontend/libs/studio-pure-functions/src/ObjectUtils/ObjectUtils.ts b/frontend/libs/studio-pure-functions/src/ObjectUtils/ObjectUtils.ts index dee5f16b116..e911317a8f5 100644 --- a/frontend/libs/studio-pure-functions/src/ObjectUtils/ObjectUtils.ts +++ b/frontend/libs/studio-pure-functions/src/ObjectUtils/ObjectUtils.ts @@ -1,3 +1,47 @@ +import { type KeyValuePairs } from '../types/KeyValuePairs'; + export class ObjectUtils { static deepCopy = (value: T) => JSON.parse(JSON.stringify(value)) as T; + + /** + * Checks if two objects are equal (shallow comparison). + * @param obj1 The first object. + * @param obj2 The second object. + * @returns True if the objects are equal and false otherwise. + */ + static areObjectsEqual = (obj1: T, obj2: T): boolean => { + if (Object.keys(obj1).length !== Object.keys(obj2).length) return false; + for (const key in obj1) { + if (obj1[key] !== obj2[key]) { + return false; + } + } + return true; + }; + + /** + * Maps an array of objects to a key-value pair object, where the key is the value of the given property. + * @param objectList + * @param property + * @returns An object with the values of the given property as keys and the objects as values. + */ + static mapByProperty = ( + objectList: T[], + property: keyof T, + ): KeyValuePairs => { + return Object.fromEntries(objectList.map((object) => [object[property], object])); + }; + + /** + * Flattens the values of an object. + * @param object The object to flatten. + * @returns An array of the values of the object. + */ + static flattenObjectValues = (object: T): string[] => { + return Object.entries(object) + .map(([, value]) => { + return value; + }) + .flat(); + }; } diff --git a/frontend/libs/studio-pure-functions/src/types/KeyValuePairs.ts b/frontend/libs/studio-pure-functions/src/types/KeyValuePairs.ts new file mode 100644 index 00000000000..8d2431ab107 --- /dev/null +++ b/frontend/libs/studio-pure-functions/src/types/KeyValuePairs.ts @@ -0,0 +1,3 @@ +export interface KeyValuePairs { + [key: string]: T; +} diff --git a/frontend/packages/shared/src/utils/dndUtils.ts b/frontend/packages/shared/src/utils/dndUtils.ts index e9405bb7400..44971450f96 100644 --- a/frontend/packages/shared/src/utils/dndUtils.ts +++ b/frontend/packages/shared/src/utils/dndUtils.ts @@ -2,7 +2,7 @@ import type { DropTargetMonitor } from 'react-dnd'; import type { DndItem, ExistingDndItem, ItemPosition } from 'app-shared/types/dndTypes'; import { DragCursorPosition } from 'app-shared/types/dndTypes'; import type { RefObject } from 'react'; -import { areObjectsEqual } from 'app-shared/utils/objectUtils'; +import { ObjectUtils } from '@studio/pure-functions'; /** * Calculates the position of the dragged item relative to the drop target. @@ -27,7 +27,7 @@ export const getDragCursorPosition = ( ): DragCursorPosition => { if (!monitor) return DragCursorPosition.Idle; - if (dragItem.isNew === false && areObjectsEqual(dragItem.position, dropItem.position)) + if (dragItem.isNew === false && ObjectUtils.areObjectsEqual(dragItem.position, dropItem.position)) return DragCursorPosition.Self; const clientOffset = monitor.getClientOffset(); diff --git a/frontend/packages/shared/src/utils/objectUtils.test.ts b/frontend/packages/shared/src/utils/objectUtils.test.ts deleted file mode 100644 index 811cb426854..00000000000 --- a/frontend/packages/shared/src/utils/objectUtils.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { areObjectsEqual, mapByProperty, flattenObjectValues } from 'app-shared/utils/objectUtils'; - -describe('objectUtils', () => { - describe('areObjectsEqual', () => { - it('Returns true if objects are equal', () => { - expect(areObjectsEqual({}, {})).toBe(true); - expect(areObjectsEqual({ a: 1 }, { a: 1 })).toBe(true); - expect(areObjectsEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true); - expect(areObjectsEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 3 })).toBe(true); - }); - - it('Returns false if objects are not equal', () => { - expect(areObjectsEqual({ a: 1 }, { a: 2 })).toBe(false); - expect(areObjectsEqual({ a: 1, b: 2 }, { a: 1, b: 3 })).toBe(false); - expect(areObjectsEqual({ a: 1, b: 2, c: 3 }, { a: 1, b: 2, c: 4 })).toBe(false); - }); - }); - - describe('mapByProperty', () => { - const property = 'id'; - const value1 = 'value1'; - const value2 = 'value2'; - const value3 = 'value3'; - const object1 = { [property]: value1 }; - const object2 = { [property]: value2, otherProperty: 'Some irrelevant value' }; - const object3 = { [property]: value3, otherProperty: 'Another irrelevant value' }; - - it('Maps an array of objects to a key-value pair object, where the key is the value of the property', () => { - const objectList = [object1, object2, object3]; - expect(mapByProperty(objectList, property)).toEqual({ - [value1]: object1, - [value2]: object2, - [value3]: object3, - }); - }); - }); - - describe('flattenObjectValues', () => { - it('Flattens the values of an object', () => { - const object = { - a: 'value1', - b: 'value2', - c: 'value3', - }; - expect(flattenObjectValues(object)).toEqual(['value1', 'value2', 'value3']); - }); - }); -}); diff --git a/frontend/packages/shared/src/utils/objectUtils.ts b/frontend/packages/shared/src/utils/objectUtils.ts deleted file mode 100644 index 0c061aed290..00000000000 --- a/frontend/packages/shared/src/utils/objectUtils.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { KeyValuePairs } from 'app-shared/types/KeyValuePairs'; - -/** - * Checks if two objects are equal (shallow comparison). - * @param obj1 The first object. - * @param obj2 The second object. - * @returns True if the objects are equal and false otherwise. - */ -export const areObjectsEqual = (obj1: T, obj2: T): boolean => { - if (Object.keys(obj1).length !== Object.keys(obj2).length) return false; - for (const key in obj1) { - if (obj1[key] !== obj2[key]) { - return false; - } - } - return true; -}; - -/** - * Maps an array of objects to a key-value pair object, where the key is the value of the given property. - * @param objectList - * @param property - * @returns An object with the values of the given property as keys and the objects as values. - */ -export const mapByProperty = ( - objectList: T[], - property: keyof T, -): KeyValuePairs => { - return Object.fromEntries(objectList.map((object) => [object[property], object])); -}; - -/** - * Flattens the values of an object. - * @param object The object to flatten. - * @returns An array of the values of the object. - */ -export const flattenObjectValues = (object: T): string[] => { - return Object.entries(object) - .map(([, value]) => { - return value; - }) - .flat(); -}; diff --git a/frontend/packages/ux-editor-v3/src/converters/formLayoutConverters/externalLayoutToInternal.ts b/frontend/packages/ux-editor-v3/src/converters/formLayoutConverters/externalLayoutToInternal.ts index dda918ca59f..663e95a47ae 100644 --- a/frontend/packages/ux-editor-v3/src/converters/formLayoutConverters/externalLayoutToInternal.ts +++ b/frontend/packages/ux-editor-v3/src/converters/formLayoutConverters/externalLayoutToInternal.ts @@ -15,7 +15,7 @@ import { externalSimpleComponentToInternal } from '../simpleComponentConverters' import type { FormComponent } from '../../types/FormComponent'; import type { FormContainer } from '../../types/FormContainer'; import { BASE_CONTAINER_ID } from 'app-shared/constants'; -import { mapByProperty } from 'app-shared/utils/objectUtils'; +import { ObjectUtils } from '@studio/pure-functions'; import type { ExternalContainerComponent } from '../../types/ExternalContainerComponent'; import type { ExternalSimpleComponent } from '../../types/ExternalSimpleComponent'; import { externalContainerComponentToInternal } from '../containerComponentConverters'; @@ -77,7 +77,7 @@ const getInternalComponents = ( const convert = (component: ExternalSimpleComponent) => convertSimpleComponent(externalComponents, component); const components: FormComponent[] = findSimpleComponents(externalComponents).map(convert); - return mapByProperty(components, 'id'); + return ObjectUtils.mapByProperty(components, 'id'); }; const getInternalContainers = ( @@ -92,7 +92,7 @@ const getInternalContainers = ( }; const convertedContainers = getConvertedContainers(externalComponents); const containers: FormContainer[] = [baseContainer, ...convertedContainers]; - return mapByProperty(containers, 'id'); + return ObjectUtils.mapByProperty(containers, 'id'); }; const getConvertedContainers = (externalComponents: ExternalComponentV3[]): FormContainer[] => { diff --git a/frontend/packages/ux-editor/src/converters/formLayoutConverters/externalLayoutToInternal.ts b/frontend/packages/ux-editor/src/converters/formLayoutConverters/externalLayoutToInternal.ts index 8918b4081cd..c4a7939bc08 100644 --- a/frontend/packages/ux-editor/src/converters/formLayoutConverters/externalLayoutToInternal.ts +++ b/frontend/packages/ux-editor/src/converters/formLayoutConverters/externalLayoutToInternal.ts @@ -11,7 +11,7 @@ import { externalSimpleComponentToInternal } from '../simpleComponentConverters' import type { FormComponent } from '../../types/FormComponent'; import type { FormContainer } from '../../types/FormContainer'; import { BASE_CONTAINER_ID } from 'app-shared/constants'; -import { mapByProperty } from 'app-shared/utils/objectUtils'; +import { ObjectUtils } from '@studio/pure-functions'; import type { ExternalContainerComponent } from '../../types/ExternalContainerComponent'; import type { ExternalSimpleComponent } from '../../types/ExternalSimpleComponent'; import { externalContainerComponentToInternal } from '../containerComponentConverters'; @@ -74,7 +74,7 @@ const getInternalComponents = ( const convert = (component: ExternalSimpleComponent) => convertSimpleComponent(externalComponents, component); const components: FormComponent[] = findSimpleComponents(externalComponents).map(convert); - return mapByProperty(components, 'id'); + return ObjectUtils.mapByProperty(components, 'id'); }; const getInternalContainers = ( @@ -89,7 +89,7 @@ const getInternalContainers = ( }; const convertedContainers = getConvertedContainers(externalComponents); const containers: FormContainer[] = [baseContainer, ...convertedContainers]; - return mapByProperty(containers, 'id'); + return ObjectUtils.mapByProperty(containers, 'id'); }; const getConvertedContainers = (externalComponents: ExternalComponent[]): FormContainer[] => { diff --git a/frontend/packages/ux-editor/src/utils/formLayoutUtils.ts b/frontend/packages/ux-editor/src/utils/formLayoutUtils.ts index ccdcbdc2bcc..c8388950137 100644 --- a/frontend/packages/ux-editor/src/utils/formLayoutUtils.ts +++ b/frontend/packages/ux-editor/src/utils/formLayoutUtils.ts @@ -15,7 +15,6 @@ import type { FormContainer } from '../types/FormContainer'; import type { FormItem } from '../types/FormItem'; import * as formItemUtils from './formItemUtils'; import type { ContainerComponentType } from '../types/ContainerComponent'; -import { flattenObjectValues } from 'app-shared/utils/objectUtils'; import type { FormLayoutPage } from '../types/FormLayoutPage'; import type { KeyValuePairs } from 'app-shared/types/KeyValuePairs'; @@ -433,7 +432,7 @@ export const idExistsInLayout = (id: string, layout: IInternalLayout): boolean = */ export const duplicatedIdsExistsInLayout = (layout: IInternalLayout): boolean => { if (!layout?.order) return false; - const idsInLayout = flattenObjectValues(layout.order); + const idsInLayout = ObjectUtils.flattenObjectValues(layout.order); return !ArrayUtils.areItemsUnique(idsInLayout); }; @@ -454,7 +453,7 @@ export const findLayoutsContainingDuplicateComponents = ( data: layouts[key], })); layoutPages.forEach(({ page, data }) => { - const components = flattenObjectValues(data.order); + const components = ObjectUtils.flattenObjectValues(data.order); components.forEach((component) => { if (componentMap.has(component)) { duplicateLayouts.add(page); @@ -477,7 +476,7 @@ export const findLayoutsContainingDuplicateComponents = ( * @returns An array of unique duplicated ids */ export const getDuplicatedIds = (layout: IInternalLayout): string[] => { - const idsInLayout = flattenObjectValues(layout.order); + const idsInLayout = ObjectUtils.flattenObjectValues(layout.order); const duplicatedIds = idsInLayout.filter((id, index) => idsInLayout.indexOf(id) !== index); const uniqueDuplicatedIds = Array.from(new Set(duplicatedIds)); return uniqueDuplicatedIds; @@ -489,7 +488,7 @@ export const getDuplicatedIds = (layout: IInternalLayout): string[] => { * @returns An array of all ids in the layout * */ export const getAllFormItemIds = (layout: IInternalLayout): string[] => - flattenObjectValues(layout.order); + ObjectUtils.flattenObjectValues(layout.order); /** * Gets all available componenent types to add for a given container