test: improve utils coverage (#4230)
* test: improve utils coverage * refactor: review - rename isDefined to isNonNullable, update tests and return statement
This commit is contained in:
@ -1,19 +0,0 @@
|
||||
import { assertNotNull } from '~/utils/assert';
|
||||
|
||||
describe('assert', () => {
|
||||
it('should return true for a NonNullable value', () => {
|
||||
expect(assertNotNull(1)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return true for a NonNullable value', () => {
|
||||
expect(assertNotNull('')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false for a null value', () => {
|
||||
expect(assertNotNull(null)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return false for an undefined value', () => {
|
||||
expect(assertNotNull(undefined)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@ -1,19 +0,0 @@
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
describe('isDefined', () => {
|
||||
it('should return true for a NonNullable value', () => {
|
||||
expect(isDefined(1)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for a NonNullable value', () => {
|
||||
expect(isDefined('')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for a null value', () => {
|
||||
expect(isDefined(null)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for an undefined value', () => {
|
||||
expect(isDefined(undefined)).toBe(false);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,15 @@
|
||||
import { isNonNullable } from '~/utils/isNonNullable';
|
||||
|
||||
describe('isNonNullable', () => {
|
||||
it('returns true if value is not undefined nor null', () => {
|
||||
expect(isNonNullable('')).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if value is null', () => {
|
||||
expect(isNonNullable(null)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if value is undefined', () => {
|
||||
expect(isNonNullable(undefined)).toBe(false);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,30 @@
|
||||
import { parseApolloStoreFieldName } from '../parseApolloStoreFieldName';
|
||||
|
||||
describe('parseApolloStoreFieldName', () => {
|
||||
it('returns an empty object if string is not a valid store field name', () => {
|
||||
const result = parseApolloStoreFieldName('////');
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('returns the field name and parsed variables if they exist', () => {
|
||||
const result = parseApolloStoreFieldName('fieldName({"key":"value"})');
|
||||
expect(result).toEqual({
|
||||
fieldName: 'fieldName',
|
||||
fieldVariables: { key: 'value' },
|
||||
});
|
||||
});
|
||||
|
||||
it('returns only the field name if the variables cannot be parsed', () => {
|
||||
const result = parseApolloStoreFieldName('fieldName(notJson)');
|
||||
expect(result).toEqual({
|
||||
fieldName: 'fieldName',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns only the field name if there are no variables', () => {
|
||||
const result = parseApolloStoreFieldName('fieldName');
|
||||
expect(result).toEqual({
|
||||
fieldName: 'fieldName',
|
||||
});
|
||||
});
|
||||
});
|
||||
35
packages/twenty-front/src/utils/__tests__/sort.test.ts
Normal file
35
packages/twenty-front/src/utils/__tests__/sort.test.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { sortAsc, sortDesc, sortNullsFirst, sortNullsLast } from '../sort';
|
||||
|
||||
describe('sort', () => {
|
||||
describe('sortNullsFirst', () => {
|
||||
it('should sort nulls first', () => {
|
||||
expect(sortNullsFirst(null, 'a')).toBe(-1);
|
||||
expect(sortNullsFirst('a', null)).toBe(1);
|
||||
expect(sortNullsFirst('a', 'a')).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortNullsLast', () => {
|
||||
it('should sort nulls last', () => {
|
||||
expect(sortNullsLast(null, 'a')).toBe(1);
|
||||
expect(sortNullsLast('a', null)).toBe(-1);
|
||||
expect(sortNullsLast('a', 'a')).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortAsc', () => {
|
||||
it('should sort in ascending order', () => {
|
||||
expect(sortAsc('a', 'b')).toBe(-1);
|
||||
expect(sortAsc('b', 'a')).toBe(1);
|
||||
expect(sortAsc('a', 'a')).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortDesc', () => {
|
||||
it('should sort in descending order', () => {
|
||||
expect(sortDesc('a', 'b')).toBe(1);
|
||||
expect(sortDesc('b', 'a')).toBe(-1);
|
||||
expect(sortDesc('a', 'a')).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,25 @@
|
||||
import { groupArrayItemsBy } from '../groupArrayItemsBy';
|
||||
|
||||
describe('groupArrayItemsBy', () => {
|
||||
it('groups an array of objects by a computed key', () => {
|
||||
// Given
|
||||
const array = [
|
||||
{ id: '1', type: 'fruit', value: 'apple' },
|
||||
{ id: '2', type: 'fruit', value: 'banana' },
|
||||
{ id: '3', type: 'vegetable', value: 'carrot' },
|
||||
];
|
||||
const computeGroupKey = ({ type }: (typeof array)[0]) => type;
|
||||
|
||||
// When
|
||||
const result = groupArrayItemsBy(array, computeGroupKey);
|
||||
|
||||
// Then
|
||||
expect(result).toEqual({
|
||||
fruit: [
|
||||
{ id: '1', type: 'fruit', value: 'apple' },
|
||||
{ id: '2', type: 'fruit', value: 'banana' },
|
||||
],
|
||||
vegetable: [{ id: '3', type: 'vegetable', value: 'carrot' }],
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,23 @@
|
||||
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
|
||||
|
||||
describe('mapArrayToObject', () => {
|
||||
it('maps an array of objects to an object with computed keys', () => {
|
||||
// Given
|
||||
const array = [
|
||||
{ id: '1', value: 'one' },
|
||||
{ id: '2', value: 'two' },
|
||||
{ id: '3', value: 'three' },
|
||||
];
|
||||
const computeItemKey = ({ id }: { id: string }) => id;
|
||||
|
||||
// When
|
||||
const result = mapArrayToObject(array, computeItemKey);
|
||||
|
||||
// Then
|
||||
expect(result).toEqual({
|
||||
'1': { id: '1', value: 'one' },
|
||||
'2': { id: '2', value: 'two' },
|
||||
'3': { id: '3', value: 'three' },
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,8 +1,30 @@
|
||||
export const groupArrayItemsBy = <Item, Key extends string>(
|
||||
array: Item[],
|
||||
computeGroupKey: (item: Item) => Key,
|
||||
/**
|
||||
* Groups an array of items by a key computed from each item.
|
||||
*
|
||||
* @param array - The array to group.
|
||||
* @param computeGroupKey - A function that computes the group key to which the item belongs.
|
||||
*
|
||||
* @returns An object with items grouped by a computed key.
|
||||
*
|
||||
* @example
|
||||
* groupArrayItemsBy(
|
||||
* [{ id: '1', type: 'fruit' }, { id: '2', type: 'vegetable' }, { id: '3', type: 'fruit' }],
|
||||
* ({ type }) => type,
|
||||
* )
|
||||
* ⬇️
|
||||
* {
|
||||
* fruit: [{ id: '1', type: 'fruit' }, { id: '3', type: 'fruit' }],
|
||||
* vegetable: [{ id: '2', type: 'vegetable' }],
|
||||
* }
|
||||
*/
|
||||
export const groupArrayItemsBy = <
|
||||
ArrayItem extends Record<string, unknown>,
|
||||
Key extends string,
|
||||
>(
|
||||
array: ArrayItem[],
|
||||
computeGroupKey: (item: ArrayItem) => Key,
|
||||
) =>
|
||||
array.reduce<Partial<Record<Key, Item[]>>>((result, item) => {
|
||||
array.reduce<Partial<Record<Key, ArrayItem[]>>>((result, item) => {
|
||||
const groupKey = computeGroupKey(item);
|
||||
const previousGroup = result[groupKey] || [];
|
||||
|
||||
|
||||
@ -1,4 +1,27 @@
|
||||
export const mapArrayToObject = <ArrayItem>(
|
||||
/**
|
||||
* Transforms an array of items into an object where the keys are computed from each item.
|
||||
*
|
||||
* @param array - The array to transform.
|
||||
* @param computeItemKey - A function that computes a key from an item.
|
||||
*
|
||||
* @returns An object where the keys are computed from the items in the array.
|
||||
*
|
||||
* @example
|
||||
* mapArrayToObject(
|
||||
* [{ id: '1', type: 'fruit' }, { id: '2', type: 'vegetable' }, { id: '3', type: 'fruit' }],
|
||||
* ({ id }) => id,
|
||||
* )
|
||||
* ⬇️
|
||||
* {
|
||||
* '1': { id: '1', type: 'fruit' },
|
||||
* '2': { id: '2', type: 'vegetable' },
|
||||
* '3': { id: '3', type: 'fruit' },
|
||||
* }
|
||||
*/
|
||||
export const mapArrayToObject = <ArrayItem, Key extends string>(
|
||||
array: ArrayItem[],
|
||||
computeItemKey: (item: ArrayItem) => string,
|
||||
) => Object.fromEntries(array.map((item) => [computeItemKey(item), item]));
|
||||
computeItemKey: (item: ArrayItem) => Key,
|
||||
) =>
|
||||
Object.fromEntries(
|
||||
array.map((item) => [computeItemKey(item), item]),
|
||||
) as Record<Key, ArrayItem>;
|
||||
|
||||
@ -1,5 +1,19 @@
|
||||
export const moveArrayItem = <Item>(
|
||||
array: Item[],
|
||||
/**
|
||||
* Moves an item in an array from one index to another.
|
||||
*
|
||||
* @param array - The array to move an item in.
|
||||
* @param indices - The indices to move the item from and to.
|
||||
* @param indices.fromIndex - The index to move the item from.
|
||||
* @param indices.toIndex - The index to move the item to.
|
||||
*
|
||||
* @returns A new array with the item moved to the new index.
|
||||
*
|
||||
* @example
|
||||
* moveArrayItem(['a', 'b', 'c'], { fromIndex: 0, toIndex: 2 })
|
||||
* => ['b', 'c', 'a']
|
||||
*/
|
||||
export const moveArrayItem = <ArrayItem>(
|
||||
array: ArrayItem[],
|
||||
{ fromIndex, toIndex }: { fromIndex: number; toIndex: number },
|
||||
) => {
|
||||
if (!(fromIndex in array) || !(toIndex in array) || fromIndex === toIndex) {
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
export const assertNotNull = <T>(item: T): item is NonNullable<T> =>
|
||||
item !== null && item !== undefined;
|
||||
@ -1,7 +1,7 @@
|
||||
import { isDefined } from './isDefined';
|
||||
import { isNonNullable } from './isNonNullable';
|
||||
|
||||
export const isDomain = (url: string | undefined | null) =>
|
||||
isDefined(url) &&
|
||||
isNonNullable(url) &&
|
||||
/^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$/.test(
|
||||
url,
|
||||
);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { isDefined } from './isDefined';
|
||||
import { isNonNullable } from './isNonNullable';
|
||||
|
||||
export const isURL = (url: string | undefined | null) =>
|
||||
isDefined(url) &&
|
||||
isNonNullable(url) &&
|
||||
url.match(
|
||||
/^(https?:\/\/)?(www.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-z]{2,63}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/i,
|
||||
);
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
export const isDefined = <T>(
|
||||
value: T | undefined | null,
|
||||
): value is NonNullable<T> => value !== undefined && value !== null;
|
||||
4
packages/twenty-front/src/utils/isNonNullable.ts
Normal file
4
packages/twenty-front/src/utils/isNonNullable.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { isNull, isUndefined } from '@sniptt/guards';
|
||||
|
||||
export const isNonNullable = <T>(value: T): value is NonNullable<T> =>
|
||||
!isUndefined(value) && !isNull(value);
|
||||
@ -8,19 +8,22 @@ export const parseApolloStoreFieldName = <
|
||||
>(
|
||||
storeFieldName: string,
|
||||
) => {
|
||||
const matches = storeFieldName.match(/([a-zA-Z][a-zA-Z0-9 ]*)\((.*)\)/);
|
||||
const matches = storeFieldName.match(/([a-zA-Z][a-zA-Z0-9 ]*)(\((.*)\))?/);
|
||||
|
||||
if (!matches?.[1]) return {};
|
||||
const fieldName = matches?.[1];
|
||||
|
||||
const [, , stringifiedVariables] = matches;
|
||||
const fieldName = matches[1] as string;
|
||||
if (!fieldName) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const stringifiedVariables = matches[3];
|
||||
|
||||
try {
|
||||
const fieldArguments = stringifiedVariables
|
||||
const fieldVariables = stringifiedVariables
|
||||
? (JSON.parse(stringifiedVariables) as Variables)
|
||||
: undefined;
|
||||
|
||||
return { fieldName, fieldArguments };
|
||||
return { fieldName, fieldVariables };
|
||||
} catch {
|
||||
return { fieldName };
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ export const sortNullsLast = (
|
||||
export const sortAsc = (
|
||||
fieldValueA: string | number,
|
||||
fieldValueB: string | number,
|
||||
) => (fieldValueA < fieldValueB ? -1 : 1);
|
||||
) => (fieldValueA === fieldValueB ? 0 : fieldValueA < fieldValueB ? -1 : 1);
|
||||
|
||||
export const sortDesc = (
|
||||
fieldValueA: string | number,
|
||||
|
||||
Reference in New Issue
Block a user