diff --git a/frontend/app-development/utils/metadataUtils.ts b/frontend/app-development/utils/metadataUtils.ts index b815f1fcee1..6e31c6ab731 100644 --- a/frontend/app-development/utils/metadataUtils.ts +++ b/frontend/app-development/utils/metadataUtils.ts @@ -3,8 +3,7 @@ import type { DataModelMetadataJson, DataModelMetadataXsd, } from 'app-shared/types/DataModelMetadata'; -import { replaceEnd } from 'app-shared/utils/stringUtils'; -import { ArrayUtils } from '@studio/pure-functions'; +import { ArrayUtils, StringUtils } from '@studio/pure-functions'; import type { MetadataOption } from '../types/MetadataOption'; import type { MetadataOptionsGroup } from '../types/MetadataOptionsGroup'; import { removeSchemaExtension } from 'app-shared/utils/filenameUtils'; @@ -23,7 +22,7 @@ export const filterOutXsdDataIfJsonDataExist = ( ({ fileName }) => !jsonData.find( ({ fileName: jsonFileName }) => - jsonFileName === replaceEnd(fileName, '.xsd', '.schema.json'), + jsonFileName === StringUtils.replaceEnd(fileName, '.xsd', '.schema.json'), ), ); diff --git a/frontend/libs/studio-pure-functions/src/StringUtils/StringUtils.test.ts b/frontend/libs/studio-pure-functions/src/StringUtils/StringUtils.test.ts index ec036675abd..73bf38ab0ec 100644 --- a/frontend/libs/studio-pure-functions/src/StringUtils/StringUtils.test.ts +++ b/frontend/libs/studio-pure-functions/src/StringUtils/StringUtils.test.ts @@ -52,4 +52,56 @@ describe('StringUtils', () => { expect(input).toBe('abc/def/ghi'); }); }); + + describe('replaceEnd', () => { + it('Replaces the given substring with the given replacement at the end of the string', () => { + expect(StringUtils.replaceEnd('abc/def/ghi', 'ghi', 'xyz')).toBe('abc/def/xyz'); + }); + + it('Does not replace the given substring other places than at the end', () => { + expect(StringUtils.replaceEnd('abcdefghi', 'abc', 'xyz')).toBe('abcdefghi'); + expect(StringUtils.replaceEnd('abcdefghi', 'def', 'xyz')).toBe('abcdefghi'); + expect(StringUtils.replaceEnd('abcdefghidef', 'def', 'xyz')).toBe('abcdefghixyz'); + }); + }); + + describe('replaceStart', () => { + it('Replaces the given substring with the given replacement at the start of the string', () => { + expect(StringUtils.replaceStart('abc/def/ghi', 'abc', 'xyz')).toBe('xyz/def/ghi'); + }); + + it('Does not replace the given substring other places than at the start', () => { + expect(StringUtils.replaceStart('abcdefghi', 'ghi', 'xyz')).toBe('abcdefghi'); + expect(StringUtils.replaceStart('abcdefghi', 'def', 'xyz')).toBe('abcdefghi'); + expect(StringUtils.replaceStart('defabcdefghi', 'def', 'xyz')).toBe('xyzabcdefghi'); + }); + }); + + describe('substringBeforeLast', () => { + it('Returns substring before last occurrence of separator', () => { + expect(StringUtils.substringBeforeLast('abc/def/ghi', '/')).toBe('abc/def'); + }); + + it('Returns whole string if separator is not found', () => { + expect(StringUtils.substringBeforeLast('abc', '/')).toBe('abc'); + }); + + it('Returns whole string if there are no characters before the last separator', () => { + expect(StringUtils.substringBeforeLast('/abc', '/')).toBe(''); + }); + }); + + describe('substringAfterLast', () => { + it('Returns substring after last occurrence of separator', () => { + expect(StringUtils.substringAfterLast('abc/def/ghi', '/')).toBe('ghi'); + }); + + it('Returns whole string if separator is not found', () => { + expect(StringUtils.substringAfterLast('abc', '/')).toBe('abc'); + }); + + it('Returns empty string if there are no characters after the last separator', () => { + expect(StringUtils.substringAfterLast('abc/def/', '/')).toBe(''); + }); + }); }); diff --git a/frontend/libs/studio-pure-functions/src/StringUtils/StringUtils.ts b/frontend/libs/studio-pure-functions/src/StringUtils/StringUtils.ts index f38304fb861..b5a3927feb4 100644 --- a/frontend/libs/studio-pure-functions/src/StringUtils/StringUtils.ts +++ b/frontend/libs/studio-pure-functions/src/StringUtils/StringUtils.ts @@ -1,3 +1,5 @@ +import { ArrayUtils } from '../ArrayUtils'; + export class StringUtils { /** * Removes any of the given substrings from the start of the string. @@ -32,4 +34,48 @@ export class StringUtils { } return str; }; + + /** + * Replaces the given substring with the given replacement at the end of the string. + * If the substring does not appear at the end of the string, the string is returned unchanged. + * @param str The string to search in. + * @param substring The substring to search for. + * @param replacement The replacement to replace the substring with. + * @returns The string with the substring replaced at the end. + */ + static replaceEnd = (str: string, substring: string, replacement: string): string => + str.replace(new RegExp(substring + '$'), replacement); + + /** + * Replaces the given substring with the given replacement at the start of the string. + * If the substring does not appear at the start of the string, the string is returned unchanged. + * @param str The string to search in. + * @param substring The substring to search for. + * @param replacement The replacement to replace the substring with. + * @returns The string with the substring replaced at the start. + */ + static replaceStart = (str: string, substring: string, replacement: string): string => { + if (str.startsWith(substring)) { + return replacement + str.slice(substring.length); + } + return str; + }; + + /** + * Returns substring before last occurrence of separator. + * @param str The string to search in. + * @param separator The separator to search for. + * @returns The substring before the last occurrence of the given separator. + */ + static substringBeforeLast = (str: string, separator: string): string => + str.includes(separator) ? str.substring(0, str.lastIndexOf(separator)) : str; + + /** + * Returns substring after last occurrence of separator. + * @param str The string to search in. + * @param separator The separator to search for. + * @returns The substring after the last occurrence of the given separator. + */ + static substringAfterLast = (str: string, separator: string): string => + ArrayUtils.last(str.split(separator)) || ''; } diff --git a/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModel.ts b/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModel.ts index 85263007fe8..7a0f7f7488d 100644 --- a/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModel.ts +++ b/frontend/packages/schema-model/src/lib/SchemaModel/SchemaModel.ts @@ -25,7 +25,6 @@ import { import { ROOT_POINTER, UNIQUE_POINTER_PREFIX } from '../constants'; import type { ReferenceNode } from '../../types/ReferenceNode'; import { ObjectUtils, ArrayUtils, StringUtils } from '@studio/pure-functions'; -import { replaceStart } from 'app-shared/utils/stringUtils'; import { createDefinitionPointer, createPropertyPointer, @@ -375,7 +374,7 @@ export class SchemaModel extends SchemaModelBase { const node = this.getNodeBySchemaPointer(newPointer); // Expects the node map to be updated if (isFieldOrCombination(node) && node.children) { const makeNewPointer = (schemaPointer: string) => - replaceStart(schemaPointer, oldPointer, newPointer); + StringUtils.replaceStart(schemaPointer, oldPointer, newPointer); node.children.forEach((childPointer) => { const newPointer = makeNewPointer(childPointer); this.changePointer(childPointer, newPointer); diff --git a/frontend/packages/schema-model/src/lib/mutations/ui-schema-reducers.test.ts b/frontend/packages/schema-model/src/lib/mutations/ui-schema-reducers.test.ts index d8b7eb144b1..d115e07bfa2 100644 --- a/frontend/packages/schema-model/src/lib/mutations/ui-schema-reducers.test.ts +++ b/frontend/packages/schema-model/src/lib/mutations/ui-schema-reducers.test.ts @@ -47,13 +47,13 @@ import { expect } from '@jest/globals'; import { CombinationKind, FieldType, Keyword, ObjectKind, StrRestrictionKey } from '../../types'; import { ROOT_POINTER } from '../constants'; import { getPointers } from '../mappers/getPointers'; -import { substringAfterLast, substringBeforeLast } from 'app-shared/utils/stringUtils'; import type { KeyValuePairs } from 'app-shared/types/KeyValuePairs'; import { validateTestUiSchema } from '../../../test/validateTestUiSchema'; import { SchemaModel } from '../SchemaModel'; import type { FieldNode } from '../../types/FieldNode'; import type { ReferenceNode } from '../../types/ReferenceNode'; import type { CombinationNode } from '../../types/CombinationNode'; +import { StringUtils } from '@studio/pure-functions'; describe('ui-schema-reducers', () => { let result: SchemaModel; @@ -73,7 +73,7 @@ describe('ui-schema-reducers', () => { it('Converts a property to a root level definition', () => { const { schemaPointer } = stringNodeMock; result = promoteProperty(createNewModelMock(), schemaPointer); - const expectedPointer = `${ROOT_POINTER}/$defs/${substringAfterLast(schemaPointer, '/')}`; + const expectedPointer = `${ROOT_POINTER}/$defs/${StringUtils.substringAfterLast(schemaPointer, '/')}`; expect(getPointers(result.asArray())).toContain(expectedPointer); expect(result.getNodeBySchemaPointer(expectedPointer)).toMatchObject({ fieldType: stringNodeMock.fieldType, @@ -223,7 +223,7 @@ describe('ui-schema-reducers', () => { const name = 'new name'; const callback = jest.fn(); const args: SetPropertyNameArgs = { path: schemaPointer, name, callback }; - const expectedPointer = substringBeforeLast(schemaPointer, '/') + '/' + name; + const expectedPointer = StringUtils.substringBeforeLast(schemaPointer, '/') + '/' + name; it('Sets the name of the given property', () => { result = setPropertyName(createNewModelMock(), args); diff --git a/frontend/packages/shared/src/utils/stringUtils.test.ts b/frontend/packages/shared/src/utils/stringUtils.test.ts deleted file mode 100644 index 65a3603972d..00000000000 --- a/frontend/packages/shared/src/utils/stringUtils.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { - replaceEnd, - replaceStart, - substringAfterLast, - substringBeforeLast, -} from 'app-shared/utils/stringUtils'; - -describe('stringUtils', () => { - describe('substringAfterLast', () => { - it('Returns substring after last occurrence of separator', () => { - expect(substringAfterLast('abc/def/ghi', '/')).toBe('ghi'); - }); - - it('Returns whole string if separator is not found', () => { - expect(substringAfterLast('abc', '/')).toBe('abc'); - }); - - it('Returns empty string if there are no characters after the last separator', () => { - expect(substringAfterLast('abc/def/', '/')).toBe(''); - }); - }); - - describe('substringBeforeLast', () => { - it('Returns substring before last occurrence of separator', () => { - expect(substringBeforeLast('abc/def/ghi', '/')).toBe('abc/def'); - }); - - it('Returns whole string if separator is not found', () => { - expect(substringBeforeLast('abc', '/')).toBe('abc'); - }); - - it('Returns whole string if there are no characters before the last separator', () => { - expect(substringBeforeLast('/abc', '/')).toBe(''); - }); - }); - - describe('replaceStart', () => { - it('Replaces the given substring with the given replacement at the start of the string', () => { - expect(replaceStart('abc/def/ghi', 'abc', 'xyz')).toBe('xyz/def/ghi'); - }); - - it('Does not replace the given substring other places than at the start', () => { - expect(replaceStart('abcdefghi', 'ghi', 'xyz')).toBe('abcdefghi'); - expect(replaceStart('abcdefghi', 'def', 'xyz')).toBe('abcdefghi'); - expect(replaceStart('defabcdefghi', 'def', 'xyz')).toBe('xyzabcdefghi'); - }); - }); - - describe('replaceEnd', () => { - it('Replaces the given substring with the given replacement at the end of the string', () => { - expect(replaceEnd('abc/def/ghi', 'ghi', 'xyz')).toBe('abc/def/xyz'); - }); - - it('Does not replace the given substring other places than at the end', () => { - expect(replaceEnd('abcdefghi', 'abc', 'xyz')).toBe('abcdefghi'); - expect(replaceEnd('abcdefghi', 'def', 'xyz')).toBe('abcdefghi'); - expect(replaceEnd('abcdefghidef', 'def', 'xyz')).toBe('abcdefghixyz'); - }); - }); -}); diff --git a/frontend/packages/shared/src/utils/stringUtils.ts b/frontend/packages/shared/src/utils/stringUtils.ts deleted file mode 100644 index 5cd182b7cb9..00000000000 --- a/frontend/packages/shared/src/utils/stringUtils.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { ArrayUtils } from '@studio/pure-functions'; - -/** - * Returns substring after last occurrence of separator. - * @param str The string to search in. - * @param separator The separator to search for. - * @returns The substring after the last occurrence of the given separator. - */ -export const substringAfterLast = (str: string, separator: string): string => - ArrayUtils.last(str.split(separator)) || ''; - -/** - * Returns substring before last occurrence of separator. - * @param str The string to search in. - * @param separator The separator to search for. - * @returns The substring before the last occurrence of the given separator. - */ -export const substringBeforeLast = (str: string, separator: string): string => - str.includes(separator) ? str.substring(0, str.lastIndexOf(separator)) : str; - -/** - * Replaces the given substring with the given replacement at the start of the string. - * If the substring does not appear at the start of the string, the string is returned unchanged. - * @param str The string to search in. - * @param substring The substring to search for. - * @param replacement The replacement to replace the substring with. - * @returns The string with the substring replaced at the start. - */ -export const replaceStart = (str: string, substring: string, replacement: string): string => { - if (str.startsWith(substring)) { - return replacement + str.slice(substring.length); - } - return str; -}; - -/** - * Replaces the given substring with the given replacement at the end of the string. - * If the substring does not appear at the end of the string, the string is returned unchanged. - * @param str The string to search in. - * @param substring The substring to search for. - * @param replacement The replacement to replace the substring with. - * @returns The string with the substring replaced at the end. - */ -export const replaceEnd = (str: string, substring: string, replacement: string): string => - str.replace(new RegExp(substring + '$'), replacement);