Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: move objectUtils to @studio/pure-functions #13938

Merged
merged 4 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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