Skip to content

Commit

Permalink
chore: move objectUtils to @studio/pure-functions (#13938)
Browse files Browse the repository at this point in the history
  • Loading branch information
wrt95 authored Oct 30, 2024
1 parent e73a87a commit 7c0e49b
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 118 deletions.
Original file line number Diff line number Diff line change
@@ -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']);
});
});
});
44 changes: 44 additions & 0 deletions frontend/libs/studio-pure-functions/src/ObjectUtils/ObjectUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,47 @@
import { type KeyValuePairs } from '../types/KeyValuePairs';

export class ObjectUtils {
static deepCopy = <T>(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 = <T extends object>(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 = <T extends object>(
objectList: T[],
property: keyof T,
): KeyValuePairs<T> => {
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 = <T extends object>(object: T): string[] => {
return Object.entries(object)
.map(([, value]) => {
return value;
})
.flat();
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface KeyValuePairs<T = any> {
[key: string]: T;
}
4 changes: 2 additions & 2 deletions frontend/packages/shared/src/utils/dndUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -27,7 +27,7 @@ export const getDragCursorPosition = <T>(
): 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();
Expand Down
48 changes: 0 additions & 48 deletions frontend/packages/shared/src/utils/objectUtils.test.ts

This file was deleted.

43 changes: 0 additions & 43 deletions frontend/packages/shared/src/utils/objectUtils.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 = (
Expand All @@ -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[] => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 = (
Expand All @@ -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[] => {
Expand Down
9 changes: 4 additions & 5 deletions frontend/packages/ux-editor/src/utils/formLayoutUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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);
};

Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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
Expand Down

0 comments on commit 7c0e49b

Please sign in to comment.