From 101657f1451279742c9773da48fa241a2c59c1f2 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 11 Nov 2024 09:46:08 +0100 Subject: [PATCH 01/37] Move validation logic into hook --- .../TopToolbar/CreateNewWrapper.tsx | 34 +-------------- .../TopToolbar/useValidateSchemaName.ts | 41 +++++++++++++++++++ 2 files changed, 43 insertions(+), 32 deletions(-) create mode 100644 frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx index ab21b0c3bea..65e75af5295 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx @@ -3,11 +3,9 @@ import classes from './CreateNewWrapper.module.css'; import { ErrorMessage, Textfield } from '@digdir/designsystemet-react'; import { useTranslation } from 'react-i18next'; import { PlusIcon } from '@studio/icons'; -import { extractModelNamesFromMetadataList } from '../../../../utils/metadataUtils'; import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; import { StudioButton, StudioPopover } from '@studio/components'; -import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; -import { useAppMetadataQuery } from 'app-shared/hooks/queries'; +import { useValidateSchemaName } from './useValidateSchemaName'; export interface CreateNewWrapperProps { disabled: boolean; @@ -28,11 +26,7 @@ export function CreateNewWrapper({ }: CreateNewWrapperProps) { const { t } = useTranslation(); const [newModelName, setNewModelName] = useState(''); - const [nameError, setNameError] = useState(''); - - const { org, app } = useStudioEnvironmentParams(); - const { data: appMetadata } = useAppMetadataQuery(org, app); - const modelNames = extractModelNamesFromMetadataList(dataModels); + const { validateName, nameError } = useValidateSchemaName(dataModels); const relativePath = createPathOption ? '' : undefined; @@ -42,36 +36,12 @@ export function CreateNewWrapper({ validateName(name); }; - const dataTypeWithNameExists = (id: string) => { - return appMetadata?.dataTypes?.find( - (dataType) => dataType.id.toLowerCase() === id.toLowerCase(), - ); - }; - - const nameValidationRegex = /^[a-zA-Z][a-zA-Z0-9_.\-æÆøØåÅ ]*$/; - const validateName = (name: string) => { - if (!name || !name.match(nameValidationRegex)) { - setNameError(t('schema_editor.invalid_datamodel_name')); - return; - } - if (modelNames.includes(name)) { - setNameError(t('schema_editor.error_model_name_exists', { newModelName: name })); - return; - } - if (dataTypeWithNameExists(name)) { - setNameError(t('schema_editor.error_data_type_name_exists')); - return; - } - setNameError(''); - }; - const onCreateConfirmClick = () => { handleCreateSchema({ name: newModelName, relativePath, }); setNewModelName(''); - setNameError(''); }; const onKeyUp = (e: React.KeyboardEvent) => { diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts new file mode 100644 index 00000000000..3f3d77a8328 --- /dev/null +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts @@ -0,0 +1,41 @@ +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { extractModelNamesFromMetadataList } from '../../../../utils/metadataUtils'; +import { useAppMetadataQuery } from 'app-shared/hooks/queries'; +import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; +import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; + +export const useValidateSchemaName = (dataModels: DataModelMetadata[]) => { + const [nameError, setNameError] = useState(''); + const { org, app } = useStudioEnvironmentParams(); + const { data: appMetadata } = useAppMetadataQuery(org, app); + const { t } = useTranslation(); + + const nameValidationRegex = /^[a-zA-Z][a-zA-Z0-9_.\-æÆøØåÅ ]*$/; + + const modelNames = extractModelNamesFromMetadataList(dataModels); + + const dataTypeWithNameExists = (id: string) => { + return appMetadata?.dataTypes?.find( + (dataType) => dataType.id.toLowerCase() === id.toLowerCase(), + ); + }; + + const validateName = (name: string): void => { + if (!name || !name.match(nameValidationRegex)) { + setNameError(t('schema_editor.invalid_datamodel_name')); + return; + } + if (modelNames.includes(name)) { + setNameError(t('schema_editor.error_model_name_exists', { newModelName: name })); + return; + } + if (dataTypeWithNameExists(name)) { + setNameError(t('schema_editor.error_data_type_name_exists')); + return; + } + setNameError(''); + }; + + return { validateName, nameError }; +}; From 71222a1705f94f03de9ed44b5a25c19a0d9149d6 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 11 Nov 2024 10:09:49 +0100 Subject: [PATCH 02/37] Remove period and space from regex --- .../SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts index 3f3d77a8328..a1c000ea813 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts @@ -11,7 +11,7 @@ export const useValidateSchemaName = (dataModels: DataModelMetadata[]) => { const { data: appMetadata } = useAppMetadataQuery(org, app); const { t } = useTranslation(); - const nameValidationRegex = /^[a-zA-Z][a-zA-Z0-9_.\-æÆøØåÅ ]*$/; + const nameValidationRegex = /^[a-zA-Z][a-zA-Z0-9_\-æÆøØåÅ]*$/; const modelNames = extractModelNamesFromMetadataList(dataModels); From 289aeb467f96e0dfd85f133315628efce84f1edf Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 11 Nov 2024 10:43:40 +0100 Subject: [PATCH 03/37] Add separate case for empty name --- .../TopToolbar/useValidateSchemaName.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts index a1c000ea813..19f35e2dab0 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts @@ -22,12 +22,16 @@ export const useValidateSchemaName = (dataModels: DataModelMetadata[]) => { }; const validateName = (name: string): void => { - if (!name || !name.match(nameValidationRegex)) { + if (!name) { + setNameError(t('validation_errors.required')); + return; + } + if (!name.match(nameValidationRegex)) { setNameError(t('schema_editor.invalid_datamodel_name')); return; } if (modelNames.includes(name)) { - setNameError(t('schema_editor.error_model_name_exists', { newModelName: name })); + setNameError(t('process_editor.configuration_panel_layout_set_id_not_unique')); return; } if (dataTypeWithNameExists(name)) { From e554177a2b0cc03aed810000dc0f9e388c4e5c5d Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 11 Nov 2024 11:02:30 +0100 Subject: [PATCH 04/37] Rename createNewOpen --> isCreateNewOpen --- .../SchemaEditorWithToolbar.tsx | 8 +++--- .../TopToolbar/CreateNewWrapper.test.tsx | 28 +++++++++---------- .../TopToolbar/CreateNewWrapper.tsx | 12 ++++---- .../TopToolbar/TopToolbar.test.tsx | 4 +-- .../TopToolbar/TopToolbar.tsx | 14 +++++----- .../TopToolbar/useValidateSchemaName.ts | 2 +- 6 files changed, 34 insertions(+), 34 deletions(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/SchemaEditorWithToolbar.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/SchemaEditorWithToolbar.tsx index 7c6cba15a82..959efdfb35c 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/SchemaEditorWithToolbar.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/SchemaEditorWithToolbar.tsx @@ -18,7 +18,7 @@ export const SchemaEditorWithToolbar = ({ createPathOption, dataModels, }: SchemaEditorWithToolbarProps) => { - const [createNewOpen, setCreateNewOpen] = useState(false); + const [isCreateNewOpen, setIsCreateNewOpen] = useState(false); const [selectedOption, setSelectedOption] = useState(undefined); const [schemaGenerationErrorMessages, setSchemaGenerationErrorMessages] = useState([]); const { mutate: addXsdFromRepo } = useAddXsdMutation(); @@ -41,11 +41,11 @@ export const SchemaEditorWithToolbar = ({ return (
setSchemaGenerationErrorMessages(errorMessages) @@ -58,7 +58,7 @@ export const SchemaEditorWithToolbar = ({ /> )}
- {!dataModels.length && setCreateNewOpen(true)} />} + {!dataModels.length && setIsCreateNewOpen(true)} />} {modelPath && }
diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx index b4edbb3df68..fc99158b7fb 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx @@ -16,13 +16,13 @@ import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; // Test data: const handleCreateSchema = jest.fn(); -const setCreateNewOpen = jest.fn(); +const setIsCreateNewOpen = jest.fn(); const defaultProps: CreateNewWrapperProps = { - createNewOpen: false, + isCreateNewOpen: false, dataModels: [], disabled: false, handleCreateSchema, - setCreateNewOpen, + setIsCreateNewOpen, }; describe('CreateNewWrapper', () => { @@ -44,13 +44,13 @@ describe('CreateNewWrapper', () => { }); await user.click(newButton); - expect(setCreateNewOpen).toHaveBeenCalledTimes(1); - expect(setCreateNewOpen).toHaveBeenCalledWith(true); + expect(setIsCreateNewOpen).toHaveBeenCalledTimes(1); + expect(setIsCreateNewOpen).toHaveBeenCalledWith(true); }); it('should close the popup when clicking "new" button', async () => { const user = userEvent.setup(); - render({ createNewOpen: true }); + render({ isCreateNewOpen: true }); expect(screen.getByRole('textbox')).toBeInTheDocument(); expect(okButton()).toBeInTheDocument(); @@ -59,14 +59,14 @@ describe('CreateNewWrapper', () => { }); await user.click(newButton); - expect(setCreateNewOpen).toHaveBeenCalledTimes(1); - expect(setCreateNewOpen).toHaveBeenCalledWith(false); + expect(setIsCreateNewOpen).toHaveBeenCalledTimes(1); + expect(setIsCreateNewOpen).toHaveBeenCalledWith(false); }); describe('createAction', () => { it('should call handleCreateSchema callback when ok button is clicked', async () => { const user = userEvent.setup(); - render({ createNewOpen: true }); + render({ isCreateNewOpen: true }); const textInput = screen.getByRole('textbox'); await user.type(textInput, 'new-model'); @@ -79,7 +79,7 @@ describe('CreateNewWrapper', () => { it('should call handleCreateSchema callback when input is focused and Enter key is pressed', async () => { const user = userEvent.setup(); - render({ createNewOpen: true }); + render({ isCreateNewOpen: true }); const textInput = screen.getByRole('textbox'); @@ -93,7 +93,7 @@ describe('CreateNewWrapper', () => { it('should call handleCreateSchema callback with relativePath when createPathOption is set and ok button is clicked', async () => { const user = userEvent.setup(); - render({ createNewOpen: true, createPathOption: true }); + render({ isCreateNewOpen: true, createPathOption: true }); const textInput = screen.getByRole('textbox'); await user.type(textInput, 'new-model'); @@ -108,7 +108,7 @@ describe('CreateNewWrapper', () => { const user = userEvent.setup(); const newModelName = dataModel1NameMock; const errMessage = textMock('schema_editor.error_model_name_exists', { newModelName }); - render({ createNewOpen: true, dataModels: [jsonMetadata1Mock] }); + render({ isCreateNewOpen: true, dataModels: [jsonMetadata1Mock] }); const textInput = screen.getByRole('textbox'); @@ -124,7 +124,7 @@ describe('CreateNewWrapper', () => { const userWithNoPointerEventCheck = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never, }); - render({ createNewOpen: true, dataModels: [jsonMetadata1Mock] }); + render({ isCreateNewOpen: true, dataModels: [jsonMetadata1Mock] }); await userWithNoPointerEventCheck.click(okButton()); @@ -139,7 +139,7 @@ describe('CreateNewWrapper', () => { queryClient.setQueryData([QueryKey.AppMetadata, org, app], { dataTypes: [{ id: dataTypeName }], }); - render({ createNewOpen: true, dataModels: [jsonMetadata1Mock] }, queryClient); + render({ isCreateNewOpen: true, dataModels: [jsonMetadata1Mock] }, queryClient); await user.type(screen.getByRole('textbox'), dataTypeName); expect( diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx index 65e75af5295..751746a4db5 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx @@ -9,19 +9,19 @@ import { useValidateSchemaName } from './useValidateSchemaName'; export interface CreateNewWrapperProps { disabled: boolean; - createNewOpen: boolean; + isCreateNewOpen: boolean; createPathOption?: boolean; dataModels: DataModelMetadata[]; - setCreateNewOpen: (open: boolean) => void; + setIsCreateNewOpen: (open: boolean) => void; handleCreateSchema: (props: { name: string; relativePath: string | undefined }) => void; } export function CreateNewWrapper({ disabled, createPathOption = false, - createNewOpen, + isCreateNewOpen: createNewOpen, dataModels, - setCreateNewOpen, + setIsCreateNewOpen, handleCreateSchema, }: CreateNewWrapperProps) { const { t } = useTranslation(); @@ -51,12 +51,12 @@ export function CreateNewWrapper({ }; return ( - + setCreateNewOpen(!createNewOpen)} + onClick={() => setIsCreateNewOpen(!createNewOpen)} size='small' > {} diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx index 998e1f395e3..a1cf364d909 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx @@ -31,10 +31,10 @@ const setSelectedOption = jest.fn(); const onSetSchemaGenerationErrorMessages = jest.fn(); const selectedOption: MetadataOption = convertMetadataToOption(jsonMetadata1Mock); const defaultProps: TopToolbarProps = { - createNewOpen: false, + isCreateNewOpen: false, dataModels: [jsonMetadata1Mock], selectedOption, - setCreateNewOpen, + setIsCreateNewOpen: setCreateNewOpen, setSelectedOption, onSetSchemaGenerationErrorMessages, }; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx index 9139f7af038..703ae5af637 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx @@ -14,21 +14,21 @@ import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; import { useTranslation } from 'react-i18next'; export interface TopToolbarProps { - createNewOpen: boolean; + isCreateNewOpen: boolean; createPathOption?: boolean; dataModels: DataModelMetadata[]; selectedOption?: MetadataOption; - setCreateNewOpen: (open: boolean) => void; + setIsCreateNewOpen: (open: boolean) => void; setSelectedOption: (option?: MetadataOption) => void; onSetSchemaGenerationErrorMessages: (errorMessages: string[]) => void; } export function TopToolbar({ - createNewOpen, + isCreateNewOpen, createPathOption, dataModels, selectedOption, - setCreateNewOpen, + setIsCreateNewOpen, setSelectedOption, onSetSchemaGenerationErrorMessages, }: TopToolbarProps) { @@ -44,7 +44,7 @@ export function TopToolbar({ const handleCreateSchema = (model: CreateDataModelMutationArgs) => { createDataModel(model); - setCreateNewOpen(false); + setIsCreateNewOpen(false); }; return ( @@ -52,8 +52,8 @@ export function TopToolbar({ diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts index 19f35e2dab0..d86e2063641 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts @@ -31,7 +31,7 @@ export const useValidateSchemaName = (dataModels: DataModelMetadata[]) => { return; } if (modelNames.includes(name)) { - setNameError(t('process_editor.configuration_panel_layout_set_id_not_unique')); + setNameError(t('schema_editor.error_model_name_exists', { newModelName: name })); return; } if (dataTypeWithNameExists(name)) { From 60dd1d5714fea1f0960f15b08080b82957ce449f Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 11 Nov 2024 15:03:47 +0100 Subject: [PATCH 05/37] Clear error on reopened popover, and autofocus input field --- .../TopToolbar/CreateNewWrapper.module.css | 4 --- .../TopToolbar/CreateNewWrapper.test.tsx | 14 +++++----- .../TopToolbar/CreateNewWrapper.tsx | 28 +++++++++++-------- .../TopToolbar/DeleteWrapper.tsx | 2 ++ .../TopToolbar/TopToolbar.module.css | 22 +++++---------- .../TopToolbar/useValidateSchemaName.ts | 8 ++++-- 6 files changed, 39 insertions(+), 39 deletions(-) delete mode 100644 frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.module.css diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.module.css b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.module.css deleted file mode 100644 index d799729b9f7..00000000000 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.popoverContent { - /* This is so that it goes above the header which has an z-index of 2000 */ - z-index: 2001; -} diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx index fc99158b7fb..56abc32e93e 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx @@ -52,7 +52,7 @@ describe('CreateNewWrapper', () => { const user = userEvent.setup(); render({ isCreateNewOpen: true }); expect(screen.getByRole('textbox')).toBeInTheDocument(); - expect(okButton()).toBeInTheDocument(); + expect(confirmButton()).toBeInTheDocument(); const newButton = screen.getByRole('button', { name: textMock('general.create_new'), @@ -70,7 +70,7 @@ describe('CreateNewWrapper', () => { const textInput = screen.getByRole('textbox'); await user.type(textInput, 'new-model'); - await user.click(okButton()); + await user.click(confirmButton()); expect(handleCreateSchema).toHaveBeenCalledWith({ name: 'new-model', relativePath: undefined, @@ -97,7 +97,7 @@ describe('CreateNewWrapper', () => { const textInput = screen.getByRole('textbox'); await user.type(textInput, 'new-model'); - await user.click(okButton()); + await user.click(confirmButton()); expect(handleCreateSchema).toHaveBeenCalledWith({ name: 'new-model', relativePath: '', @@ -115,7 +115,7 @@ describe('CreateNewWrapper', () => { expect(screen.queryByText(errMessage)).not.toBeInTheDocument(); await user.type(textInput, newModelName); - expect(okButton()).toBeDisabled(); + expect(confirmButton()).toBeDisabled(); expect(handleCreateSchema).not.toHaveBeenCalled(); expect(screen.getByText(errMessage)).toBeInTheDocument(); }); @@ -126,7 +126,7 @@ describe('CreateNewWrapper', () => { }); render({ isCreateNewOpen: true, dataModels: [jsonMetadata1Mock] }); - await userWithNoPointerEventCheck.click(okButton()); + await userWithNoPointerEventCheck.click(confirmButton()); expect(handleCreateSchema).not.toHaveBeenCalled(); }); @@ -146,12 +146,12 @@ describe('CreateNewWrapper', () => { screen.getByText(textMock('schema_editor.error_data_type_name_exists')), ).toBeInTheDocument(); - expect(okButton()).toBeDisabled(); + expect(confirmButton()).toBeDisabled(); }); }); }); -const okButton = () => { +const confirmButton = () => { return screen.getByRole('button', { name: textMock('schema_editor.create_model_confirm_button'), }); diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx index 751746a4db5..f724666874b 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx @@ -1,11 +1,11 @@ import React, { useState } from 'react'; -import classes from './CreateNewWrapper.module.css'; -import { ErrorMessage, Textfield } from '@digdir/designsystemet-react'; +import classes from './TopToolbar.module.css'; import { useTranslation } from 'react-i18next'; import { PlusIcon } from '@studio/icons'; import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; -import { StudioButton, StudioPopover } from '@studio/components'; +import { StudioButton, StudioPopover, StudioTextfield } from '@studio/components'; import { useValidateSchemaName } from './useValidateSchemaName'; +import cn from 'classnames'; export interface CreateNewWrapperProps { disabled: boolean; @@ -19,14 +19,14 @@ export interface CreateNewWrapperProps { export function CreateNewWrapper({ disabled, createPathOption = false, - isCreateNewOpen: createNewOpen, + isCreateNewOpen, dataModels, setIsCreateNewOpen, handleCreateSchema, }: CreateNewWrapperProps) { const { t } = useTranslation(); const [newModelName, setNewModelName] = useState(''); - const { validateName, nameError } = useValidateSchemaName(dataModels); + const { validateName, nameError, clearError } = useValidateSchemaName(dataModels); const relativePath = createPathOption ? '' : undefined; @@ -50,31 +50,37 @@ export function CreateNewWrapper({ } }; + const handleOpenChange = () => { + setIsCreateNewOpen(!isCreateNewOpen); + setNewModelName(''); + clearError(); + }; + return ( - + setIsCreateNewOpen(!createNewOpen)} + onClick={handleOpenChange} size='small' > {} {t('general.create_new')} - - + {nameError}} + error={nameError} + autoFocus /> diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper.tsx index 0fdc14c7229..17abee432e1 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper.tsx @@ -8,6 +8,7 @@ import { AltinnConfirmDialog } from 'app-shared/components'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; import { useUpdateBpmn } from 'app-shared/hooks/useUpdateBpmn'; import { removeDataTypeIdsToSign } from 'app-shared/utils/bpmnUtils'; +import classes from './TopToolbar.module.css'; export interface DeleteWrapperProps { selectedOption: MetadataOption | null; } @@ -36,6 +37,7 @@ export function DeleteWrapper({ selectedOption }: DeleteWrapperProps) { return ( { ); }; + const clearError = () => { + setNameError(''); + }; + const validateName = (name: string): void => { if (!name) { setNameError(t('validation_errors.required')); @@ -38,8 +42,8 @@ export const useValidateSchemaName = (dataModels: DataModelMetadata[]) => { setNameError(t('schema_editor.error_data_type_name_exists')); return; } - setNameError(''); + clearError(); }; - return { validateName, nameError }; + return { validateName, nameError, clearError }; }; From 718d1224db0de53abe4758a71622814637dffa88 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 11 Nov 2024 15:44:49 +0100 Subject: [PATCH 06/37] Add check for max length --- .../TopToolbar/useValidateSchemaName.ts | 13 ++++++++++--- frontend/language/src/nb.json | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts index b3b5a437c4b..39dfb4a145a 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts @@ -11,8 +11,6 @@ export const useValidateSchemaName = (dataModels: DataModelMetadata[]) => { const { data: appMetadata } = useAppMetadataQuery(org, app); const { t } = useTranslation(); - const nameValidationRegex = /^[a-zA-Z][a-zA-Z0-9_\-æÆøØåÅ]*$/; - const modelNames = extractModelNamesFromMetadataList(dataModels); const dataTypeWithNameExists = (id: string) => { @@ -30,10 +28,16 @@ export const useValidateSchemaName = (dataModels: DataModelMetadata[]) => { setNameError(t('validation_errors.required')); return; } - if (!name.match(nameValidationRegex)) { + if (!name.match(SCHEMA_NAME_REGEX)) { setNameError(t('schema_editor.invalid_datamodel_name')); return; } + if (name.length > SCHEMA_NAME_MAX_LENGTH) { + setNameError( + t('schema_editor.error_model_name_max_length', { maxLength: SCHEMA_NAME_MAX_LENGTH }), + ); + return; + } if (modelNames.includes(name)) { setNameError(t('schema_editor.error_model_name_exists', { newModelName: name })); return; @@ -47,3 +51,6 @@ export const useValidateSchemaName = (dataModels: DataModelMetadata[]) => { return { validateName, nameError, clearError }; }; + +const SCHEMA_NAME_MAX_LENGTH: number = 100; +const SCHEMA_NAME_REGEX: RegExp = /^[a-zA-Z][a-zA-Z0-9_\-æÆøØåÅ]*$/; diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 47ffd8d588c..f115199128a 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -863,6 +863,7 @@ "schema_editor.error_could_not_detect_taskType_description": "Dette gjør at vi ikke kan vise riktig liste over tilgjengelige komponenter. Velg en annen sidegruppe eller prøv å last siden på nytt.", "schema_editor.error_data_type_name_exists": "Modellen kan ikke ha samme navn som datatyper i løsningen.", "schema_editor.error_model_name_exists": "Modellnavnet {{newModelName}} er allerede i bruk.", + "schema_editor.error_model_name_max_length": "Modellnavnet kan ikke være lengre enn {{maxLength}} tegn.", "schema_editor.error_upload_data_model_id_exists_override_option": "Det eksisterer allerede en modell med dette navnet. Ønsker du å overskrive den?", "schema_editor.field": "Objekt", "schema_editor.field_name": "Navn på felt", From 8b0fa8e5d572ae44085ed72945a304c6cb9d3b78 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 12 Nov 2024 10:15:50 +0100 Subject: [PATCH 07/37] Add check for C# reserved keyword --- .../TopToolbar/CreateNewWrapper.tsx | 4 +- .../TopToolbar/SchemaSelect.module.css | 4 - .../TopToolbar/SchemaSelect.tsx | 2 +- .../TopToolbar/TopToolbar.module.css | 5 + .../TopToolbar/useValidateSchemaName.ts | 122 +++++++++++++++--- frontend/language/src/nb.json | 1 + 6 files changed, 114 insertions(+), 24 deletions(-) delete mode 100644 frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.module.css diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx index f724666874b..95f757cad40 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx @@ -26,7 +26,7 @@ export function CreateNewWrapper({ }: CreateNewWrapperProps) { const { t } = useTranslation(); const [newModelName, setNewModelName] = useState(''); - const { validateName, nameError, clearError } = useValidateSchemaName(dataModels); + const { validateName, nameError, setNameError } = useValidateSchemaName(dataModels); const relativePath = createPathOption ? '' : undefined; @@ -53,7 +53,7 @@ export function CreateNewWrapper({ const handleOpenChange = () => { setIsCreateNewOpen(!isCreateNewOpen); setNewModelName(''); - clearError(); + setNameError(''); }; return ( diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.module.css b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.module.css deleted file mode 100644 index 7914b8c1c12..00000000000 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.select { - cursor: pointer; - min-width: 20vw; -} diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.tsx index d1ff68acf42..75714cacb8f 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.tsx @@ -6,7 +6,7 @@ import { } from '../../../../utils/metadataUtils'; import type { MetadataOption } from '../../../../types/MetadataOption'; import { NativeSelect } from '@digdir/designsystemet-react'; -import classes from './SchemaSelect.module.css'; +import classes from './TopToolbar.module.css'; import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; import { useTranslation } from 'react-i18next'; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.module.css b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.module.css index 9ab0a84a25d..a13cc06789a 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.module.css +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.module.css @@ -31,6 +31,11 @@ gap: var(--fds-spacing-3); } +.select { + cursor: pointer; + min-width: 20vw; +} + .generateButtonWrapper { flex: 1; } diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts index 39dfb4a145a..c00665ff19e 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts @@ -1,9 +1,10 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { extractModelNamesFromMetadataList } from '../../../../utils/metadataUtils'; import { useAppMetadataQuery } from 'app-shared/hooks/queries'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; +import type { ApplicationMetadata, DataTypeElement } from 'app-shared/types/ApplicationMetadata'; +import { extractModelNamesFromMetadataList } from '../../../../utils/metadataUtils'; export const useValidateSchemaName = (dataModels: DataModelMetadata[]) => { const [nameError, setNameError] = useState(''); @@ -11,18 +12,6 @@ export const useValidateSchemaName = (dataModels: DataModelMetadata[]) => { const { data: appMetadata } = useAppMetadataQuery(org, app); const { t } = useTranslation(); - const modelNames = extractModelNamesFromMetadataList(dataModels); - - const dataTypeWithNameExists = (id: string) => { - return appMetadata?.dataTypes?.find( - (dataType) => dataType.id.toLowerCase() === id.toLowerCase(), - ); - }; - - const clearError = () => { - setNameError(''); - }; - const validateName = (name: string): void => { if (!name) { setNameError(t('validation_errors.required')); @@ -38,19 +27,118 @@ export const useValidateSchemaName = (dataModels: DataModelMetadata[]) => { ); return; } - if (modelNames.includes(name)) { + if (isExistingModelName(name, dataModels)) { setNameError(t('schema_editor.error_model_name_exists', { newModelName: name })); return; } - if (dataTypeWithNameExists(name)) { + if (isExistingDataTypeName(name, appMetadata)) { setNameError(t('schema_editor.error_data_type_name_exists')); return; } - clearError(); + if (isCSharpReservedKeyword(name)) { + setNameError(t('schema_editor.error_reserved_keyword')); + return; + } + setNameError(''); }; - return { validateName, nameError, clearError }; + return { validateName, nameError, setNameError }; }; const SCHEMA_NAME_MAX_LENGTH: number = 100; const SCHEMA_NAME_REGEX: RegExp = /^[a-zA-Z][a-zA-Z0-9_\-æÆøØåÅ]*$/; + +const isExistingDataTypeName = (id: string, appMetaData: ApplicationMetadata): DataTypeElement => { + return appMetaData?.dataTypes?.find( + (dataType: DataTypeElement) => dataType.id.toLowerCase() === id.toLowerCase(), + ); +}; + +const isExistingModelName = (name: string, dataModels: DataModelMetadata[]): boolean => { + const modelNames = extractModelNamesFromMetadataList(dataModels); + return modelNames.includes(name); +}; + +const isCSharpReservedKeyword = (word: string): boolean => { + const cSharpKeywords = new Set([ + 'abstract', + 'as', + 'base', + 'bool', + 'break', + 'byte', + 'case', + 'catch', + 'char', + 'checked', + 'class', + 'const', + 'continue', + 'decimal', + 'default', + 'delegate', + 'do', + 'double', + 'else', + 'enum', + 'event', + 'explicit', + 'extern', + 'false', + 'finally', + 'fixed', + 'float', + 'for', + 'foreach', + 'goto', + 'if', + 'implicit', + 'in', + 'int', + 'interface', + 'internal', + 'is', + 'lock', + 'long', + 'namespace', + 'new', + 'null', + 'object', + 'operator', + 'out', + 'override', + 'params', + 'private', + 'protected', + 'public', + 'readonly', + 'ref', + 'return', + 'sbyte', + 'sealed', + 'short', + 'sizeof', + 'stackalloc', + 'static', + 'string', + 'struct', + 'switch', + 'this', + 'throw', + 'true', + 'try', + 'typeof', + 'uint', + 'ulong', + 'unchecked', + 'unsafe', + 'ushort', + 'using', + 'virtual', + 'void', + 'volatile', + 'while', + ]); + + return cSharpKeywords.has(word); +}; diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index f115199128a..425621d1171 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -864,6 +864,7 @@ "schema_editor.error_data_type_name_exists": "Modellen kan ikke ha samme navn som datatyper i løsningen.", "schema_editor.error_model_name_exists": "Modellnavnet {{newModelName}} er allerede i bruk.", "schema_editor.error_model_name_max_length": "Modellnavnet kan ikke være lengre enn {{maxLength}} tegn.", + "schema_editor.error_reserved_keyword": "Navnet er reservert og kan ikke brukes.", "schema_editor.error_upload_data_model_id_exists_override_option": "Det eksisterer allerede en modell med dette navnet. Ønsker du å overskrive den?", "schema_editor.field": "Objekt", "schema_editor.field_name": "Navn på felt", From 4706506374fdb0b0752491ff234d88db541388e0 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 13 Nov 2024 12:52:10 +0100 Subject: [PATCH 08/37] Write tests for useValidateSchemaName --- .../TopToolbar/CreateNewWrapper.test.tsx | 124 ++++++++---------- .../TopToolbar/useValidateSchemaName.test.ts | 120 +++++++++++++++++ .../TopToolbar/useValidateSchemaName.ts | 2 +- .../mocks/applicationMetadataMock.ts | 8 ++ 4 files changed, 185 insertions(+), 69 deletions(-) create mode 100644 frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.test.ts diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx index 56abc32e93e..5fb6a0594eb 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx @@ -32,17 +32,10 @@ describe('CreateNewWrapper', () => { const user = userEvent.setup(); render(); - expect(screen.queryByRole('textbox')).not.toBeInTheDocument(); - expect( - screen.queryByRole('button', { - name: textMock('schema_editor.create_model_confirm_button'), - }), - ).not.toBeInTheDocument(); - - const newButton = screen.getByRole('button', { - name: textMock('general.create_new'), - }); - await user.click(newButton); + expect(queryInputField()).not.toBeInTheDocument(); + expect(queryConfirmButton()).not.toBeInTheDocument(); + + await user.click(getNewButton()); expect(setIsCreateNewOpen).toHaveBeenCalledTimes(1); expect(setIsCreateNewOpen).toHaveBeenCalledWith(true); @@ -51,26 +44,54 @@ describe('CreateNewWrapper', () => { it('should close the popup when clicking "new" button', async () => { const user = userEvent.setup(); render({ isCreateNewOpen: true }); - expect(screen.getByRole('textbox')).toBeInTheDocument(); - expect(confirmButton()).toBeInTheDocument(); - const newButton = screen.getByRole('button', { - name: textMock('general.create_new'), - }); + expect(queryInputField()).toBeInTheDocument(); + expect(queryConfirmButton()).toBeInTheDocument(); + + await user.click(getNewButton()); - await user.click(newButton); expect(setIsCreateNewOpen).toHaveBeenCalledTimes(1); expect(setIsCreateNewOpen).toHaveBeenCalledWith(false); }); - describe('createAction', () => { + it('should show error when trying to create model with existing name', async () => { + const user = userEvent.setup(); + const newModelName = dataModel1NameMock; + const errorMessage = textMock('schema_editor.error_model_name_exists', { newModelName }); + render({ isCreateNewOpen: true, dataModels: [jsonMetadata1Mock] }); + + expect(screen.queryByText(errorMessage)).not.toBeInTheDocument(); + + await user.type(queryInputField(), newModelName); + + expect(screen.getByText(errorMessage)).toBeInTheDocument(); + expect(queryConfirmButton()).toBeDisabled(); + }); + + it('should show error when name already is used in applicationmetadata json file', async () => { + const user = userEvent.setup(); + const errorMessage = textMock('schema_editor.error_data_type_name_exists'); + const dataTypeName = 'testmodel'; + const queryClient = createQueryClientMock(); + queryClient.setQueryData([QueryKey.AppMetadata, org, app], { + dataTypes: [{ id: dataTypeName }], + }); + render({ isCreateNewOpen: true, dataModels: [jsonMetadata1Mock] }, queryClient); + + await user.type(queryInputField(), dataTypeName); + + expect(screen.getByText(errorMessage)).toBeInTheDocument(); + expect(queryConfirmButton()).toBeDisabled(); + }); + + describe('handleCreateSchema', () => { it('should call handleCreateSchema callback when ok button is clicked', async () => { const user = userEvent.setup(); render({ isCreateNewOpen: true }); - const textInput = screen.getByRole('textbox'); - await user.type(textInput, 'new-model'); - await user.click(confirmButton()); + await user.type(queryInputField(), 'new-model'); + await user.click(queryConfirmButton()); + expect(handleCreateSchema).toHaveBeenCalledWith({ name: 'new-model', relativePath: undefined, @@ -81,10 +102,9 @@ describe('CreateNewWrapper', () => { const user = userEvent.setup(); render({ isCreateNewOpen: true }); - const textInput = screen.getByRole('textbox'); - - await user.type(textInput, 'new-model'); + await user.type(queryInputField(), 'new-model'); await user.keyboard('{Enter}'); + expect(handleCreateSchema).toHaveBeenCalledWith({ name: 'new-model', relativePath: undefined, @@ -95,67 +115,35 @@ describe('CreateNewWrapper', () => { const user = userEvent.setup(); render({ isCreateNewOpen: true, createPathOption: true }); - const textInput = screen.getByRole('textbox'); - await user.type(textInput, 'new-model'); - await user.click(confirmButton()); + await user.type(queryInputField(), 'new-model'); + await user.click(queryConfirmButton()); + expect(handleCreateSchema).toHaveBeenCalledWith({ name: 'new-model', relativePath: '', }); }); - it('should not call handleCreateSchema callback and show error message when trying to create a new model with the same name as an existing one when ok button is clicked', async () => { - const user = userEvent.setup(); - const newModelName = dataModel1NameMock; - const errMessage = textMock('schema_editor.error_model_name_exists', { newModelName }); - render({ isCreateNewOpen: true, dataModels: [jsonMetadata1Mock] }); - - const textInput = screen.getByRole('textbox'); - - expect(screen.queryByText(errMessage)).not.toBeInTheDocument(); - await user.type(textInput, newModelName); - - expect(confirmButton()).toBeDisabled(); - expect(handleCreateSchema).not.toHaveBeenCalled(); - expect(screen.getByText(errMessage)).toBeInTheDocument(); - }); - - it('should not call handleCreateSchema callback when trying to create a new model with no name when ok button is clicked', async () => { + it('should not call handleCreateSchema callback when name field is empty and ok button is clicked', async () => { const userWithNoPointerEventCheck = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never, }); render({ isCreateNewOpen: true, dataModels: [jsonMetadata1Mock] }); - await userWithNoPointerEventCheck.click(confirmButton()); + await userWithNoPointerEventCheck.click(queryConfirmButton()); expect(handleCreateSchema).not.toHaveBeenCalled(); }); - - it('should not allow a name already in use in applicationmetadata json file', async () => { - const user = userEvent.setup(); - - const dataTypeName = 'testmodel'; - const queryClient = createQueryClientMock(); - queryClient.setQueryData([QueryKey.AppMetadata, org, app], { - dataTypes: [{ id: dataTypeName }], - }); - render({ isCreateNewOpen: true, dataModels: [jsonMetadata1Mock] }, queryClient); - - await user.type(screen.getByRole('textbox'), dataTypeName); - expect( - screen.getByText(textMock('schema_editor.error_data_type_name_exists')), - ).toBeInTheDocument(); - - expect(confirmButton()).toBeDisabled(); - }); }); }); -const confirmButton = () => { - return screen.getByRole('button', { - name: textMock('schema_editor.create_model_confirm_button'), - }); -}; +const getNewButton = () => screen.getByRole('button', { name: textMock('general.create_new') }); + +const queryInputField = () => + screen.queryByRole('textbox', { name: textMock('schema_editor.create_model_description') }); + +const queryConfirmButton = () => + screen.queryByRole('button', { name: textMock('schema_editor.create_model_confirm_button') }); const render = ( props: Partial = {}, diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.test.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.test.ts new file mode 100644 index 00000000000..f6ad45f3e61 --- /dev/null +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.test.ts @@ -0,0 +1,120 @@ +import { SCHEMA_NAME_MAX_LENGTH, useValidateSchemaName } from './useValidateSchemaName'; +import { useAppMetadataQuery } from 'app-shared/hooks/queries'; +import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; +import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; +import { textMock } from '@studio/testing/mocks/i18nMock'; +import { renderHook, act } from '@testing-library/react'; +import { + dataModel1NameMock, + jsonMetadata1Mock, +} from '../../../../../packages/schema-editor/test/mocks/metadataMocks'; +import type { ApplicationMetadata } from 'app-shared/types/ApplicationMetadata'; +import { mockAppMetadata } from '../../../../layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/mocks/applicationMetadataMock'; + +//Test data +const org = 'test-org'; +const app = 'test-app'; +const dataModels: DataModelMetadata[] = [jsonMetadata1Mock]; +const appMetaData: ApplicationMetadata = mockAppMetadata; + +jest.mock('app-shared/hooks/queries', () => ({ + useAppMetadataQuery: jest.fn(), +})); + +jest.mock('app-shared/hooks/useStudioEnvironmentParams', () => ({ + useStudioEnvironmentParams: jest.fn(), +})); + +describe('useValidateSchemaName', () => { + beforeEach(() => { + (useStudioEnvironmentParams as jest.Mock).mockReturnValue({ org, app }); + (useAppMetadataQuery as jest.Mock).mockReturnValue({ data: appMetaData }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should set nameError to empty string when name is valid', () => { + const { result } = renderHook(() => useValidateSchemaName(dataModels)); + + result.current.validateName('ValidName'); + + expect(result.current.nameError).toBe(''); + }); + + it('should set error when name is empty', () => { + const { result } = renderHook(() => useValidateSchemaName(dataModels)); + + act(() => { + result.current.validateName(''); + }); + + expect(result.current.nameError).toBe(textMock('validation_errors.required')); + }); + + it('should set error when name does not match regex', () => { + const { result } = renderHook(() => useValidateSchemaName(dataModels)); + + act(() => { + result.current.validateName('123InvalidName'); + }); + + expect(result.current.nameError).toBe(textMock('schema_editor.invalid_datamodel_name')); + }); + + it('should set error when name exceeds max length', () => { + const longName = 'a'.repeat(SCHEMA_NAME_MAX_LENGTH + 1); + const { result } = renderHook(() => useValidateSchemaName(dataModels)); + + act(() => { + result.current.validateName(longName); + }); + + expect(result.current.nameError).toBe( + textMock('schema_editor.error_model_name_max_length', { maxLength: SCHEMA_NAME_MAX_LENGTH }), + ); + }); + + it('should set error when name exists in dataModels', () => { + const { result } = renderHook(() => useValidateSchemaName(dataModels)); + + act(() => { + result.current.validateName(dataModel1NameMock); + }); + + expect(result.current.nameError).toBe( + textMock('schema_editor.error_model_name_exists', { + newModelName: dataModel1NameMock, + }), + ); + }); + + it('should set error when name exists in dataTypes', () => { + const { result } = renderHook(() => useValidateSchemaName(dataModels)); + + act(() => { + result.current.validateName('dataTypeId'); + }); + + expect(result.current.nameError).toBe(textMock('schema_editor.error_data_type_name_exists')); + }); + + it('should set error when name is a C# reserved keyword', () => { + const { result } = renderHook(() => useValidateSchemaName(dataModels)); + + act(() => { + result.current.validateName('class'); + }); + + expect(result.current.nameError).toBe(textMock('schema_editor.error_reserved_keyword')); + }); + + it('should handle norwegian characters in name', () => { + const { result } = renderHook(() => useValidateSchemaName(dataModels)); + + result.current.validateName('Valid_Name-æøåÆØÅ'); + + expect(result.current.nameError).toBe(''); + }); +}); diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts index c00665ff19e..1dca847bb0d 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts @@ -45,7 +45,7 @@ export const useValidateSchemaName = (dataModels: DataModelMetadata[]) => { return { validateName, nameError, setNameError }; }; -const SCHEMA_NAME_MAX_LENGTH: number = 100; +export const SCHEMA_NAME_MAX_LENGTH: number = 100; const SCHEMA_NAME_REGEX: RegExp = /^[a-zA-Z][a-zA-Z0-9_\-æÆøØåÅ]*$/; const isExistingDataTypeName = (id: string, appMetaData: ApplicationMetadata): DataTypeElement => { diff --git a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/mocks/applicationMetadataMock.ts b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/mocks/applicationMetadataMock.ts index a50499bd96b..03ca95e6050 100644 --- a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/mocks/applicationMetadataMock.ts +++ b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/mocks/applicationMetadataMock.ts @@ -1,6 +1,7 @@ import type { ApplicationMetadata, CopyInstanceSettings, + DataTypeElement, HideSettings, MessageBoxConfig, OnEntry, @@ -34,9 +35,16 @@ const mockOnEntry: OnEntry = { show: 'select-instance', }; +const mockDataTypes: DataTypeElement[] = [ + { + id: 'dataTypeId', + }, +]; + export const mockAppMetadata: ApplicationMetadata = { id: 'mockId', org, + dataTypes: mockDataTypes, partyTypesAllowed: mockPartyTypesAllowed, validFrom: mockValidFrom, validTo: mockValidTo, From f9c3df6b4d14f19ddd66c1ff65b1b4e0b71d46f0 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 13 Nov 2024 14:04:23 +0100 Subject: [PATCH 09/37] Update text resources --- .../TopToolbar/CreateNewWrapper.tsx | 1 + .../TopToolbar/useValidateSchemaName.test.ts | 4 +-- .../TopToolbar/useValidateSchemaName.ts | 6 ++--- frontend/language/src/en.json | 2 +- frontend/language/src/nb.json | 25 +++++++++---------- .../src/containers/FormDesigner.tsx | 4 +-- .../ux-editor/src/containers/FormDesigner.tsx | 4 +-- 7 files changed, 22 insertions(+), 24 deletions(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx index 95f757cad40..dff7762b189 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx @@ -42,6 +42,7 @@ export function CreateNewWrapper({ relativePath, }); setNewModelName(''); + setNameError(''); }; const onKeyUp = (e: React.KeyboardEvent) => { diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.test.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.test.ts index f6ad45f3e61..ead15143202 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.test.ts +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.test.ts @@ -60,7 +60,7 @@ describe('useValidateSchemaName', () => { result.current.validateName('123InvalidName'); }); - expect(result.current.nameError).toBe(textMock('schema_editor.invalid_datamodel_name')); + expect(result.current.nameError).toBe(textMock('schema_editor.error_invalid_datamodel_name')); }); it('should set error when name exceeds max length', () => { @@ -72,7 +72,7 @@ describe('useValidateSchemaName', () => { }); expect(result.current.nameError).toBe( - textMock('schema_editor.error_model_name_max_length', { maxLength: SCHEMA_NAME_MAX_LENGTH }), + textMock('validation_errors.maxLength', { 0: SCHEMA_NAME_MAX_LENGTH }), ); }); diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts index 1dca847bb0d..11449955097 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts @@ -18,13 +18,11 @@ export const useValidateSchemaName = (dataModels: DataModelMetadata[]) => { return; } if (!name.match(SCHEMA_NAME_REGEX)) { - setNameError(t('schema_editor.invalid_datamodel_name')); + setNameError(t('schema_editor.error_invalid_datamodel_name')); return; } if (name.length > SCHEMA_NAME_MAX_LENGTH) { - setNameError( - t('schema_editor.error_model_name_max_length', { maxLength: SCHEMA_NAME_MAX_LENGTH }), - ); + setNameError(t('validation_errors.maxLength', { 0: SCHEMA_NAME_MAX_LENGTH })); return; } if (isExistingModelName(name, dataModels)) { diff --git a/frontend/language/src/en.json b/frontend/language/src/en.json index a65067eb030..2c6b7da12aa 100644 --- a/frontend/language/src/en.json +++ b/frontend/language/src/en.json @@ -638,13 +638,13 @@ "schema_editor.delete_data_model": "Delete data model", "schema_editor.delete_field": "Delete field", "schema_editor.delete_model_confirm": "Are you sure you want to delete {{schemaName}}?", - "schema_editor.depth_error": "It is not possible to put the group here because it will make the form exceed the maximum allowed depth.", "schema_editor.description": "Description", "schema_editor.descriptive_fields": "Descriptive fields", "schema_editor.enum": "Valid values", "schema_editor.enum_empty": "The list is empty—all values will be approved.", "schema_editor.enum_error_duplicate": "The values must be unique.", "schema_editor.enum_legend": "List of valid values", + "schema_editor.error_depth": "It is not possible to put the group here because it will make the form exceed the maximum allowed depth.", "schema_editor.error_model_name_exists": "A model with the name {{newModelName}} already exists.", "schema_editor.field": "Field", "schema_editor.field_name": "Field name", diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 425621d1171..2ec022a65d4 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -849,7 +849,6 @@ "schema_editor.delete_data_model": "Slett datamodell", "schema_editor.delete_field": "Slett felt", "schema_editor.delete_model_confirm": "Er du sikker på at du vil slette datamodellen {{schemaName}}?", - "schema_editor.depth_error": "Du kan ikke plassere gruppen her, fordi skjemaet får for mange nivåer.", "schema_editor.description": "Tekst", "schema_editor.descriptive_fields": "Beskrivende felter", "schema_editor.disable_deletion_info_for_used_definition": "Du kan ikke slette en definisjon som er i bruk.", @@ -862,8 +861,10 @@ "schema_editor.error_could_not_detect_taskType": "Kunne ikke hente oppgavetype for {{layout}}", "schema_editor.error_could_not_detect_taskType_description": "Dette gjør at vi ikke kan vise riktig liste over tilgjengelige komponenter. Velg en annen sidegruppe eller prøv å last siden på nytt.", "schema_editor.error_data_type_name_exists": "Modellen kan ikke ha samme navn som datatyper i løsningen.", + "schema_editor.error_depth": "Du kan ikke plassere gruppen her, fordi skjemaet får for mange nivåer.", + "schema_editor.error_invalid_child": "Du kan ikke plassere den komponenttypen i denne gruppen.", + "schema_editor.error_invalid_datamodel_name": "Navnet er ugyldig. Det første tegnet må være en bokstav (a-z). Deretter kan du bruke bokstaver (a-å), tall, understrek og bindestrek.", "schema_editor.error_model_name_exists": "Modellnavnet {{newModelName}} er allerede i bruk.", - "schema_editor.error_model_name_max_length": "Modellnavnet kan ikke være lengre enn {{maxLength}} tegn.", "schema_editor.error_reserved_keyword": "Navnet er reservert og kan ikke brukes.", "schema_editor.error_upload_data_model_id_exists_override_option": "Det eksisterer allerede en modell med dette navnet. Ønsker du å overskrive den?", "schema_editor.field": "Objekt", @@ -898,8 +899,6 @@ "schema_editor.generate_model_files": "Generer modeller", "schema_editor.go_to_type": "Gå til type", "schema_editor.integer": "Heltall", - "schema_editor.invalid_child_error": "Du kan ikke plassere den komponenttypen i denne gruppen.", - "schema_editor.invalid_datamodel_name": "Navnet er ugyldig. Du kan bruke tall og store og små bokstaver fra det norske alfabetet, og understrek, punktum og bindestrek.", "schema_editor.language": "Språk", "schema_editor.language_add_language": "Legg til språk:", "schema_editor.language_confirm_deletion": "Ja, slett språket", @@ -1780,13 +1779,13 @@ "ux_editor.upload_file_error_too_large": "Kunne ikke laste opp filen. Den er for stor.", "ux_editor.url_label": "Lenke", "ux_editor.warning": "Advarsel", - "validation_errors.length": "Antall tillatte tegn er {{0}}", - "validation_errors.max": "Største gyldig verdi er {{0}}", - "validation_errors.maxLength": "Bruk {{0}} eller færre tegn", - "validation_errors.min": "Minste gyldig verdi er {{0}}", - "validation_errors.minLength": "Bruk {{0}} eller flere tegn", - "validation_errors.numbers_only": "Kun sifre er gyldige tegn", - "validation_errors.pattern": "Feil format eller verdi", - "validation_errors.required": "Feltet må fylles ut", - "validation_errors.value_as_url": "Ugyldig lenke" + "validation_errors.length": "Antall tillatte tegn er {{0}}.", + "validation_errors.max": "Største gyldig verdi er {{0}}.", + "validation_errors.maxLength": "Bruk {{0}} eller færre tegn.", + "validation_errors.min": "Minste gyldig verdi er {{0}}.", + "validation_errors.minLength": "Bruk {{0}} eller flere tegn.", + "validation_errors.numbers_only": "Kun sifre er gyldige tegn.", + "validation_errors.pattern": "Feil format eller verdi.", + "validation_errors.required": "Feltet må fylles ut.", + "validation_errors.value_as_url": "Ugyldig lenke." } diff --git a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx index 583bd37f418..ef42f5db94e 100644 --- a/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx +++ b/frontend/packages/ux-editor-v3/src/containers/FormDesigner.tsx @@ -110,8 +110,8 @@ export const FormDesigner = ({ } if (formLayoutIsReady) { - const triggerDepthAlert = () => alert(t('schema_editor.depth_error')); - const triggerInvalidChildAlert = () => alert(t('schema_editor.invalid_child_error')); + const triggerDepthAlert = () => alert(t('schema_editor.error_depth')); + const triggerInvalidChildAlert = () => alert(t('schema_editor.error_invalid_child')); const layout = formLayouts[selectedLayout]; const addItem: HandleAdd = (type, { parentId, index }) => { diff --git a/frontend/packages/ux-editor/src/containers/FormDesigner.tsx b/frontend/packages/ux-editor/src/containers/FormDesigner.tsx index 28d7cd8e269..64f213b6196 100644 --- a/frontend/packages/ux-editor/src/containers/FormDesigner.tsx +++ b/frontend/packages/ux-editor/src/containers/FormDesigner.tsx @@ -105,8 +105,8 @@ export const FormDesigner = (): JSX.Element => { } if (formLayoutIsReady) { - const triggerDepthAlert = () => alert(t('schema_editor.depth_error')); - const triggerInvalidChildAlert = () => alert(t('schema_editor.invalid_child_error')); + const triggerDepthAlert = () => alert(t('schema_editor.error_depth')); + const triggerInvalidChildAlert = () => alert(t('schema_editor.error_invalid_child')); const layout = formLayouts[selectedFormLayoutName]; const addItem: HandleAdd = (type, { parentId, index }) => { From 099eff36dcf01a1363d449d52e9091528a7e4b63 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 14 Nov 2024 08:59:09 +0100 Subject: [PATCH 10/37] Fix bug where invalid model could be created with enter key --- .../TopToolbar/CreateNewWrapper.test.tsx | 72 ++++++++++--------- .../TopToolbar/CreateNewWrapper.tsx | 30 ++++---- .../TopToolbar/TopToolbar.tsx | 9 --- 3 files changed, 53 insertions(+), 58 deletions(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx index 5fb6a0594eb..3fce5ccf5f2 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx @@ -11,21 +11,28 @@ import { import { renderWithProviders } from '../../../../test/testUtils'; import { app, org } from '@studio/testing/testids'; import { APP_DEVELOPMENT_BASENAME } from 'app-shared/constants'; -import { QueryKey } from 'app-shared/types/QueryKey'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; +import { useCreateDataModelMutation } from '../../../../hooks/mutations'; // Test data: -const handleCreateSchema = jest.fn(); -const setIsCreateNewOpen = jest.fn(); +const mockSetIsCreateNewOpen = jest.fn(); const defaultProps: CreateNewWrapperProps = { isCreateNewOpen: false, dataModels: [], disabled: false, - handleCreateSchema, - setIsCreateNewOpen, + setIsCreateNewOpen: mockSetIsCreateNewOpen, }; +const mockCreateDataModel = jest.fn(); + +jest.mock('../../../../hooks/mutations', () => ({ + useCreateDataModelMutation: jest.fn(), +})); describe('CreateNewWrapper', () => { + beforeEach(() => { + (useCreateDataModelMutation as jest.Mock).mockReturnValue({ mutate: mockCreateDataModel }); + }); + afterEach(jest.clearAllMocks); it('should open the popup when clicking "new" button', async () => { @@ -37,8 +44,8 @@ describe('CreateNewWrapper', () => { await user.click(getNewButton()); - expect(setIsCreateNewOpen).toHaveBeenCalledTimes(1); - expect(setIsCreateNewOpen).toHaveBeenCalledWith(true); + expect(mockSetIsCreateNewOpen).toHaveBeenCalledTimes(1); + expect(mockSetIsCreateNewOpen).toHaveBeenCalledWith(true); }); it('should close the popup when clicking "new" button', async () => { @@ -50,11 +57,11 @@ describe('CreateNewWrapper', () => { await user.click(getNewButton()); - expect(setIsCreateNewOpen).toHaveBeenCalledTimes(1); - expect(setIsCreateNewOpen).toHaveBeenCalledWith(false); + expect(mockSetIsCreateNewOpen).toHaveBeenCalledTimes(1); + expect(mockSetIsCreateNewOpen).toHaveBeenCalledWith(false); }); - it('should show error when trying to create model with existing name', async () => { + it('should show error when validation fails', async () => { const user = userEvent.setup(); const newModelName = dataModel1NameMock; const errorMessage = textMock('schema_editor.error_model_name_exists', { newModelName }); @@ -68,63 +75,47 @@ describe('CreateNewWrapper', () => { expect(queryConfirmButton()).toBeDisabled(); }); - it('should show error when name already is used in applicationmetadata json file', async () => { - const user = userEvent.setup(); - const errorMessage = textMock('schema_editor.error_data_type_name_exists'); - const dataTypeName = 'testmodel'; - const queryClient = createQueryClientMock(); - queryClient.setQueryData([QueryKey.AppMetadata, org, app], { - dataTypes: [{ id: dataTypeName }], - }); - render({ isCreateNewOpen: true, dataModels: [jsonMetadata1Mock] }, queryClient); - - await user.type(queryInputField(), dataTypeName); - - expect(screen.getByText(errorMessage)).toBeInTheDocument(); - expect(queryConfirmButton()).toBeDisabled(); - }); - - describe('handleCreateSchema', () => { - it('should call handleCreateSchema callback when ok button is clicked', async () => { + describe('createDataModel', () => { + it('should call createDataModel when confirm button is clicked', async () => { const user = userEvent.setup(); render({ isCreateNewOpen: true }); await user.type(queryInputField(), 'new-model'); await user.click(queryConfirmButton()); - expect(handleCreateSchema).toHaveBeenCalledWith({ + expect(mockCreateDataModel).toHaveBeenCalledWith({ name: 'new-model', relativePath: undefined, }); }); - it('should call handleCreateSchema callback when input is focused and Enter key is pressed', async () => { + it('should call createDataModel when input is focused and Enter key is pressed', async () => { const user = userEvent.setup(); render({ isCreateNewOpen: true }); await user.type(queryInputField(), 'new-model'); await user.keyboard('{Enter}'); - expect(handleCreateSchema).toHaveBeenCalledWith({ + expect(mockCreateDataModel).toHaveBeenCalledWith({ name: 'new-model', relativePath: undefined, }); }); - it('should call handleCreateSchema callback with relativePath when createPathOption is set and ok button is clicked', async () => { + it('should call createDataModel with relativePath when createPathOption is set and ok button is clicked', async () => { const user = userEvent.setup(); render({ isCreateNewOpen: true, createPathOption: true }); await user.type(queryInputField(), 'new-model'); await user.click(queryConfirmButton()); - expect(handleCreateSchema).toHaveBeenCalledWith({ + expect(mockCreateDataModel).toHaveBeenCalledWith({ name: 'new-model', relativePath: '', }); }); - it('should not call handleCreateSchema callback when name field is empty and ok button is clicked', async () => { + it('should not call createDataModel when name field is empty and ok button is clicked', async () => { const userWithNoPointerEventCheck = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never, }); @@ -132,7 +123,18 @@ describe('CreateNewWrapper', () => { await userWithNoPointerEventCheck.click(queryConfirmButton()); - expect(handleCreateSchema).not.toHaveBeenCalled(); + expect(mockCreateDataModel).not.toHaveBeenCalled(); + }); + + it('should not call createDataModel when name field is empty and enter button is pressed', async () => { + const userWithNoPointerEventCheck = userEvent.setup({ + pointerEventsCheck: PointerEventsCheckLevel.Never, + }); + render({ isCreateNewOpen: true, dataModels: [jsonMetadata1Mock] }); + + await userWithNoPointerEventCheck.keyboard('{Enter}'); + + expect(mockCreateDataModel).not.toHaveBeenCalled(); }); }); }); diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx index dff7762b189..1274c01faa2 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx @@ -6,6 +6,7 @@ import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; import { StudioButton, StudioPopover, StudioTextfield } from '@studio/components'; import { useValidateSchemaName } from './useValidateSchemaName'; import cn from 'classnames'; +import { useCreateDataModelMutation } from '../../../../hooks/mutations'; export interface CreateNewWrapperProps { disabled: boolean; @@ -13,7 +14,6 @@ export interface CreateNewWrapperProps { createPathOption?: boolean; dataModels: DataModelMetadata[]; setIsCreateNewOpen: (open: boolean) => void; - handleCreateSchema: (props: { name: string; relativePath: string | undefined }) => void; } export function CreateNewWrapper({ @@ -22,32 +22,34 @@ export function CreateNewWrapper({ isCreateNewOpen, dataModels, setIsCreateNewOpen, - handleCreateSchema, }: CreateNewWrapperProps) { const { t } = useTranslation(); const [newModelName, setNewModelName] = useState(''); const { validateName, nameError, setNameError } = useValidateSchemaName(dataModels); + const { mutate: createDataModel } = useCreateDataModelMutation(); + const isConfirmButtonActivated = newModelName && !nameError; const relativePath = createPathOption ? '' : undefined; - const onNameChange = (e: any) => { + const handleNameChange = (e: any) => { const name = e.target.value || ''; setNewModelName(name); validateName(name); }; - const onCreateConfirmClick = () => { - handleCreateSchema({ + const handleConfirm = () => { + createDataModel({ name: newModelName, relativePath, }); - setNewModelName(''); - setNameError(''); + + handleOpenChange(); }; - const onKeyUp = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - onCreateConfirmClick(); + const handleKeyUp = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && isConfirmButtonActivated) { + console.log(!disabled); + handleConfirm(); } }; @@ -73,15 +75,15 @@ export function CreateNewWrapper({ diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx index 703ae5af637..cf3ff87ccb6 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx @@ -5,8 +5,6 @@ import { XSDUpload } from './XSDUpload'; import { SchemaSelect } from './SchemaSelect'; import { DeleteWrapper } from './DeleteWrapper'; import { computeSelectedOption } from '../../../../utils/metadataUtils'; -import type { CreateDataModelMutationArgs } from '../../../../hooks/mutations/useCreateDataModelMutation'; -import { useCreateDataModelMutation } from '../../../../hooks/mutations'; import type { MetadataOption } from '../../../../types/MetadataOption'; import { GenerateModelsButton } from './GenerateModelsButton'; import { usePrevious } from '@studio/components'; @@ -35,18 +33,12 @@ export function TopToolbar({ const modelPath = selectedOption?.value.repositoryRelativeUrl; const { t } = useTranslation(); - const { mutate: createDataModel } = useCreateDataModelMutation(); const prevDataModels = usePrevious(dataModels); useEffect(() => { setSelectedOption(computeSelectedOption(selectedOption, dataModels, prevDataModels)); }, [selectedOption, dataModels, prevDataModels, setSelectedOption]); - const handleCreateSchema = (model: CreateDataModelMutationArgs) => { - createDataModel(model); - setIsCreateNewOpen(false); - }; - return (
Date: Thu, 14 Nov 2024 09:29:49 +0100 Subject: [PATCH 11/37] Move hooks into 'hooks' folder --- .../SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx | 3 +-- .../TopToolbar => hooks}/useValidateSchemaName.test.ts | 4 ++-- .../TopToolbar => hooks}/useValidateSchemaName.ts | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) rename frontend/app-development/features/dataModelling/{SchemaEditorWithToolbar/TopToolbar => hooks}/useValidateSchemaName.test.ts (94%) rename frontend/app-development/features/dataModelling/{SchemaEditorWithToolbar/TopToolbar => hooks}/useValidateSchemaName.ts (97%) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx index 1274c01faa2..3333b7b8129 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import { PlusIcon } from '@studio/icons'; import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; import { StudioButton, StudioPopover, StudioTextfield } from '@studio/components'; -import { useValidateSchemaName } from './useValidateSchemaName'; +import { useValidateSchemaName } from '../../hooks/useValidateSchemaName'; import cn from 'classnames'; import { useCreateDataModelMutation } from '../../../../hooks/mutations'; @@ -48,7 +48,6 @@ export function CreateNewWrapper({ const handleKeyUp = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && isConfirmButtonActivated) { - console.log(!disabled); handleConfirm(); } }; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.test.ts b/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.test.ts similarity index 94% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.test.ts rename to frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.test.ts index ead15143202..afc5252003b 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.test.ts +++ b/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.test.ts @@ -7,9 +7,9 @@ import { renderHook, act } from '@testing-library/react'; import { dataModel1NameMock, jsonMetadata1Mock, -} from '../../../../../packages/schema-editor/test/mocks/metadataMocks'; +} from '../../../../packages/schema-editor/test/mocks/metadataMocks'; import type { ApplicationMetadata } from 'app-shared/types/ApplicationMetadata'; -import { mockAppMetadata } from '../../../../layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/mocks/applicationMetadataMock'; +import { mockAppMetadata } from '../../../layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/mocks/applicationMetadataMock'; //Test data const org = 'test-org'; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts b/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.ts similarity index 97% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts rename to frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.ts index 11449955097..17db566906f 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/useValidateSchemaName.ts +++ b/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.ts @@ -4,7 +4,7 @@ import { useAppMetadataQuery } from 'app-shared/hooks/queries'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; import type { ApplicationMetadata, DataTypeElement } from 'app-shared/types/ApplicationMetadata'; -import { extractModelNamesFromMetadataList } from '../../../../utils/metadataUtils'; +import { extractModelNamesFromMetadataList } from '../../../utils/metadataUtils'; export const useValidateSchemaName = (dataModels: DataModelMetadata[]) => { const [nameError, setNameError] = useState(''); From 539a955e72c2bdf663fe59559d05cf6afea4937f Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 18 Nov 2024 14:32:46 +0100 Subject: [PATCH 12/37] Fix PR comments --- frontend/.eslintrc.json | 2 +- .../TopToolbar/CreateNewWrapper.module.css | 8 + .../TopToolbar/CreateNewWrapper.test.tsx | 41 +++-- .../TopToolbar/CreateNewWrapper.tsx | 10 +- .../TopToolbar/DeleteWrapper.module.css | 4 + .../TopToolbar/DeleteWrapper.tsx | 3 +- .../TopToolbar/TopToolbar.module.css | 11 -- .../hooks/useValidateSchemaName.test.ts | 154 ++++++++++++------ .../hooks/useValidateSchemaName.ts | 2 +- .../AccessControlTab.test.tsx | 2 +- .../SelectAllowedPartyTypes.test.tsx | 2 +- .../Tabs/SetupTab/SetupTab.test.tsx | 2 +- .../SetupTabContent/SetupTabContent.test.tsx | 2 +- .../mocks => test}/applicationMetadataMock.ts | 2 +- frontend/language/src/nb.json | 22 +-- 15 files changed, 161 insertions(+), 106 deletions(-) create mode 100644 frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.module.css create mode 100644 frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper.module.css rename frontend/app-development/{layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/mocks => test}/applicationMetadataMock.ts (95%) diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index e5e903981a1..1a19ab58389 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -8,7 +8,7 @@ "plugins": ["jsx-a11y", "react", "react-hooks", "@typescript-eslint", "import"], "parser": "@typescript-eslint/parser", "parserOptions": { - "project": "./frontend/tsconfig.json" + "project": "./**/tsconfig.json" }, "rules": { "react/destructuring-assignment": "off", diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.module.css b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.module.css new file mode 100644 index 00000000000..af5356f29e0 --- /dev/null +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.module.css @@ -0,0 +1,8 @@ +.popover { + display: flex; + flex-direction: column; + gap: var(--fds-spacing-3); + + /* This is so that popovers goes above the header which has an z-index of 2000 */ + z-index: 2001; +} diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx index 3fce5ccf5f2..340fb63c27e 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx @@ -12,9 +12,10 @@ import { renderWithProviders } from '../../../../test/testUtils'; import { app, org } from '@studio/testing/testids'; import { APP_DEVELOPMENT_BASENAME } from 'app-shared/constants'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; -import { useCreateDataModelMutation } from '../../../../hooks/mutations'; // Test data: +const [testOrg, testApp] = ['testOrg', 'testApp']; +const mockCreateDataModel = jest.fn(); const mockSetIsCreateNewOpen = jest.fn(); const defaultProps: CreateNewWrapperProps = { isCreateNewOpen: false, @@ -22,17 +23,8 @@ const defaultProps: CreateNewWrapperProps = { disabled: false, setIsCreateNewOpen: mockSetIsCreateNewOpen, }; -const mockCreateDataModel = jest.fn(); - -jest.mock('../../../../hooks/mutations', () => ({ - useCreateDataModelMutation: jest.fn(), -})); describe('CreateNewWrapper', () => { - beforeEach(() => { - (useCreateDataModelMutation as jest.Mock).mockReturnValue({ mutate: mockCreateDataModel }); - }); - afterEach(jest.clearAllMocks); it('should open the popup when clicking "new" button', async () => { @@ -61,17 +53,17 @@ describe('CreateNewWrapper', () => { expect(mockSetIsCreateNewOpen).toHaveBeenCalledWith(false); }); - it('should show error when validation fails', async () => { + it('should show an error text when validation fails', async () => { const user = userEvent.setup(); const newModelName = dataModel1NameMock; const errorMessage = textMock('schema_editor.error_model_name_exists', { newModelName }); render({ isCreateNewOpen: true, dataModels: [jsonMetadata1Mock] }); - expect(screen.queryByText(errorMessage)).not.toBeInTheDocument(); + expect(queryErrorMessage(errorMessage)).not.toBeInTheDocument(); await user.type(queryInputField(), newModelName); - expect(screen.getByText(errorMessage)).toBeInTheDocument(); + expect(queryErrorMessage(errorMessage)).toBeInTheDocument(); expect(queryConfirmButton()).toBeDisabled(); }); @@ -83,9 +75,9 @@ describe('CreateNewWrapper', () => { await user.type(queryInputField(), 'new-model'); await user.click(queryConfirmButton()); - expect(mockCreateDataModel).toHaveBeenCalledWith({ - name: 'new-model', - relativePath: undefined, + expect(mockCreateDataModel).toHaveBeenCalledWith(testOrg, testApp, { + modelName: 'new-model', + relativeDirectory: undefined, }); }); @@ -96,9 +88,9 @@ describe('CreateNewWrapper', () => { await user.type(queryInputField(), 'new-model'); await user.keyboard('{Enter}'); - expect(mockCreateDataModel).toHaveBeenCalledWith({ - name: 'new-model', - relativePath: undefined, + expect(mockCreateDataModel).toHaveBeenCalledWith(testOrg, testApp, { + modelName: 'new-model', + relativeDirectory: undefined, }); }); @@ -109,9 +101,9 @@ describe('CreateNewWrapper', () => { await user.type(queryInputField(), 'new-model'); await user.click(queryConfirmButton()); - expect(mockCreateDataModel).toHaveBeenCalledWith({ - name: 'new-model', - relativePath: '', + expect(mockCreateDataModel).toHaveBeenCalledWith(testOrg, testApp, { + modelName: 'new-model', + relativeDirectory: '', }); }); @@ -144,6 +136,10 @@ const getNewButton = () => screen.getByRole('button', { name: textMock('general. const queryInputField = () => screen.queryByRole('textbox', { name: textMock('schema_editor.create_model_description') }); +const queryErrorMessage = (errorMessage: string) => { + return screen.queryByText(errorMessage); +}; + const queryConfirmButton = () => screen.queryByRole('button', { name: textMock('schema_editor.create_model_confirm_button') }); @@ -152,6 +148,7 @@ const render = ( queryClient = createQueryClientMock(), ) => { renderWithProviders(, { + queries: { createDataModel: mockCreateDataModel }, startUrl: `${APP_DEVELOPMENT_BASENAME}/${org}/${app}/ui-editor`, queryClient, }); diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx index 3333b7b8129..5ba40bd20a9 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx @@ -1,11 +1,11 @@ +import type { ChangeEvent } from 'react'; import React, { useState } from 'react'; -import classes from './TopToolbar.module.css'; +import classes from './CreateNewWrapper.module.css'; import { useTranslation } from 'react-i18next'; import { PlusIcon } from '@studio/icons'; import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; import { StudioButton, StudioPopover, StudioTextfield } from '@studio/components'; import { useValidateSchemaName } from '../../hooks/useValidateSchemaName'; -import cn from 'classnames'; import { useCreateDataModelMutation } from '../../../../hooks/mutations'; export interface CreateNewWrapperProps { @@ -31,7 +31,7 @@ export function CreateNewWrapper({ const isConfirmButtonActivated = newModelName && !nameError; const relativePath = createPathOption ? '' : undefined; - const handleNameChange = (e: any) => { + const handleNameChange = (e: ChangeEvent) => { const name = e.target.value || ''; setNewModelName(name); validateName(name); @@ -46,7 +46,7 @@ export function CreateNewWrapper({ handleOpenChange(); }; - const handleKeyUp = (e: React.KeyboardEvent) => { + const handleKeyUp = (e: KeyboardEvent) => { if (e.key === 'Enter' && isConfirmButtonActivated) { handleConfirm(); } @@ -70,7 +70,7 @@ export function CreateNewWrapper({ {} {t('general.create_new')} - + ({ - useAppMetadataQuery: jest.fn(), -})); - -jest.mock('app-shared/hooks/useStudioEnvironmentParams', () => ({ - useStudioEnvironmentParams: jest.fn(), -})); +import { mockAppMetadata, mockDataTypes } from '../../../test/applicationMetadataMock'; +import { renderHookWithProviders } from '../../../test/mocks'; +import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; +import { QueryKey } from 'app-shared/types/QueryKey'; +import { app, org } from '@studio/testing/testids'; describe('useValidateSchemaName', () => { - beforeEach(() => { - (useStudioEnvironmentParams as jest.Mock).mockReturnValue({ org, app }); - (useAppMetadataQuery as jest.Mock).mockReturnValue({ data: appMetaData }); - }); - afterEach(() => { jest.clearAllMocks(); }); it('should set nameError to empty string when name is valid', () => { - const { result } = renderHook(() => useValidateSchemaName(dataModels)); + const { result } = renderUseValidateSchemaName(); - result.current.validateName('ValidName'); + act(() => { + result.current.validateName('ValidName'); + }); expect(result.current.nameError).toBe(''); }); it('should set error when name is empty', () => { - const { result } = renderHook(() => useValidateSchemaName(dataModels)); + const { result } = renderUseValidateSchemaName(); act(() => { result.current.validateName(''); @@ -53,31 +37,21 @@ describe('useValidateSchemaName', () => { expect(result.current.nameError).toBe(textMock('validation_errors.required')); }); - it('should set error when name does not match regex', () => { - const { result } = renderHook(() => useValidateSchemaName(dataModels)); - - act(() => { - result.current.validateName('123InvalidName'); - }); - - expect(result.current.nameError).toBe(textMock('schema_editor.error_invalid_datamodel_name')); - }); - it('should set error when name exceeds max length', () => { const longName = 'a'.repeat(SCHEMA_NAME_MAX_LENGTH + 1); - const { result } = renderHook(() => useValidateSchemaName(dataModels)); + const { result } = renderUseValidateSchemaName(); act(() => { result.current.validateName(longName); }); expect(result.current.nameError).toBe( - textMock('validation_errors.maxLength', { 0: SCHEMA_NAME_MAX_LENGTH }), + textMock('validation_errors.maxLength', { number: SCHEMA_NAME_MAX_LENGTH }), ); }); - it('should set error when name exists in dataModels', () => { - const { result } = renderHook(() => useValidateSchemaName(dataModels)); + it('should set error when data model with same name exists', () => { + const { result } = renderUseValidateSchemaName(); act(() => { result.current.validateName(dataModel1NameMock); @@ -90,18 +64,18 @@ describe('useValidateSchemaName', () => { ); }); - it('should set error when name exists in dataTypes', () => { - const { result } = renderHook(() => useValidateSchemaName(dataModels)); + it('should set error when data type in appMetadata with same name exists', () => { + const { result } = renderUseValidateSchemaName(); act(() => { - result.current.validateName('dataTypeId'); + result.current.validateName(mockDataTypes[0].id); }); expect(result.current.nameError).toBe(textMock('schema_editor.error_data_type_name_exists')); }); it('should set error when name is a C# reserved keyword', () => { - const { result } = renderHook(() => useValidateSchemaName(dataModels)); + const { result } = renderUseValidateSchemaName(); act(() => { result.current.validateName('class'); @@ -110,11 +84,93 @@ describe('useValidateSchemaName', () => { expect(result.current.nameError).toBe(textMock('schema_editor.error_reserved_keyword')); }); - it('should handle norwegian characters in name', () => { - const { result } = renderHook(() => useValidateSchemaName(dataModels)); + describe('regular expressions', () => { + it('should disallow Norwegian characters at start of name', () => { + const { result } = renderUseValidateSchemaName(); + const invalidFirstCharacters = ['æ', 'ø', 'å', 'Æ', 'Ø', 'Å']; - result.current.validateName('Valid_Name-æøåÆØÅ'); + invalidFirstCharacters.forEach((char) => { + act(() => { + result.current.validateName(char); + }); - expect(result.current.nameError).toBe(''); + expect(result.current.nameError).toBe( + textMock('schema_editor.error_invalid_datamodel_name'), + ); + }); + }); + + it('should allow Norwegian characters in rest of name', () => { + const { result } = renderUseValidateSchemaName(); + + act(() => { + result.current.validateName('aÆØÅæøå'); + }); + + expect(result.current.nameError).toBe(''); + }); + + it('should disallow numbers at start of name', () => { + const { result } = renderUseValidateSchemaName(); + const invalidFirstCharacters = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']; + + invalidFirstCharacters.forEach((char) => { + act(() => { + result.current.validateName(char); + }); + + expect(result.current.nameError).toBe( + textMock('schema_editor.error_invalid_datamodel_name'), + ); + }); + }); + + it('should allow numbers in rest of name', () => { + const { result } = renderUseValidateSchemaName(); + + act(() => { + result.current.validateName('a1234567890'); + }); + + expect(result.current.nameError).toBe(''); + }); + + it('should disallow "-" and "_" at start of name', () => { + const { result } = renderUseValidateSchemaName(); + const invalidFirstCharacters = ['-', '_', 'æ', 'ø', 'å', 'Æ', 'Ø', 'Å']; + + invalidFirstCharacters.forEach((char) => { + act(() => { + result.current.validateName(char); + }); + + expect(result.current.nameError).toBe( + textMock('schema_editor.error_invalid_datamodel_name'), + ); + }); + }); + + it('should allow "-" and "_" in rest of name', () => { + const { result } = renderUseValidateSchemaName(); + + act(() => { + result.current.validateName('a-_'); + }); + + expect(result.current.nameError).toBe(''); + }); }); }); + +const renderUseValidateSchemaName = () => { + const dataModels: DataModelMetadata[] = [jsonMetadata1Mock]; + + const queryClient = createQueryClientMock(); + queryClient.setQueryData([QueryKey.AppMetadata, org, app], mockAppMetadata); + const { renderHookResult: result } = renderHookWithProviders( + {}, + queryClient, + )(() => useValidateSchemaName(dataModels)); + + return result; +}; diff --git a/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.ts b/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.ts index 17db566906f..c7ede8bfdff 100644 --- a/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.ts +++ b/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.ts @@ -22,7 +22,7 @@ export const useValidateSchemaName = (dataModels: DataModelMetadata[]) => { return; } if (name.length > SCHEMA_NAME_MAX_LENGTH) { - setNameError(t('validation_errors.maxLength', { 0: SCHEMA_NAME_MAX_LENGTH })); + setNameError(t('validation_errors.maxLength', { number: SCHEMA_NAME_MAX_LENGTH })); return; } if (isExistingModelName(name, dataModels)) { diff --git a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AccessControlTab/AccessControlTab.test.tsx b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AccessControlTab/AccessControlTab.test.tsx index f2fdd656671..f7116c1df68 100644 --- a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AccessControlTab/AccessControlTab.test.tsx +++ b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AccessControlTab/AccessControlTab.test.tsx @@ -6,7 +6,7 @@ import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; import { ServicesContextProvider } from 'app-shared/contexts/ServicesContext'; import type { QueryClient } from '@tanstack/react-query'; -import { mockAppMetadata } from '../../../mocks/applicationMetadataMock'; +import { mockAppMetadata } from '../../../../../../../../test/applicationMetadataMock'; import userEvent from '@testing-library/user-event'; import { app, org } from '@studio/testing/testids'; import { MemoryRouter } from 'react-router-dom'; diff --git a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AccessControlTab/SelectAllowedPartyTypes/SelectAllowedPartyTypes.test.tsx b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AccessControlTab/SelectAllowedPartyTypes/SelectAllowedPartyTypes.test.tsx index 7b95536f7c1..5321f69b76a 100644 --- a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AccessControlTab/SelectAllowedPartyTypes/SelectAllowedPartyTypes.test.tsx +++ b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/AccessControlTab/SelectAllowedPartyTypes/SelectAllowedPartyTypes.test.tsx @@ -3,7 +3,7 @@ import { SelectAllowedPartyTypes, type SelectAllowedPartyTypesProps, } from './SelectAllowedPartyTypes'; -import { mockAppMetadata } from '../../../../mocks/applicationMetadataMock'; +import { mockAppMetadata } from '../../../../../../../../../test/applicationMetadataMock'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { render as rtlRender, screen, waitFor } from '@testing-library/react'; import type { QueryClient } from '@tanstack/react-query'; diff --git a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/SetupTab/SetupTab.test.tsx b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/SetupTab/SetupTab.test.tsx index f82bfec3ec0..7d5e7842657 100644 --- a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/SetupTab/SetupTab.test.tsx +++ b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/SetupTab/SetupTab.test.tsx @@ -8,7 +8,7 @@ import type { QueryClient } from '@tanstack/react-query'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import { MemoryRouter } from 'react-router-dom'; import { queriesMock } from 'app-shared/mocks/queriesMock'; -import { mockAppMetadata } from '../../../mocks/applicationMetadataMock'; +import { mockAppMetadata } from '../../../../../../../../test/applicationMetadataMock'; import { app, org } from '@studio/testing/testids'; const getAppMetadata = jest.fn().mockImplementation(() => Promise.resolve({})); diff --git a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/SetupTab/SetupTabContent/SetupTabContent.test.tsx b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/SetupTab/SetupTabContent/SetupTabContent.test.tsx index 8a3ced8835e..91f6c9765d5 100644 --- a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/SetupTab/SetupTabContent/SetupTabContent.test.tsx +++ b/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/components/Tabs/SetupTab/SetupTabContent/SetupTabContent.test.tsx @@ -10,7 +10,7 @@ import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import { MemoryRouter } from 'react-router-dom'; import { queriesMock } from 'app-shared/mocks/queriesMock'; import type { ApplicationMetadata } from 'app-shared/types/ApplicationMetadata'; -import { mockAppMetadata } from '../../../../mocks/applicationMetadataMock'; +import { mockAppMetadata } from '../../../../../../../../../test/applicationMetadataMock'; import userEvent from '@testing-library/user-event'; import { app, org } from '@studio/testing/testids'; diff --git a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/mocks/applicationMetadataMock.ts b/frontend/app-development/test/applicationMetadataMock.ts similarity index 95% rename from frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/mocks/applicationMetadataMock.ts rename to frontend/app-development/test/applicationMetadataMock.ts index 03ca95e6050..600be4fc2c3 100644 --- a/frontend/app-development/layout/PageHeader/SubHeader/SettingsModalButton/SettingsModal/mocks/applicationMetadataMock.ts +++ b/frontend/app-development/test/applicationMetadataMock.ts @@ -35,7 +35,7 @@ const mockOnEntry: OnEntry = { show: 'select-instance', }; -const mockDataTypes: DataTypeElement[] = [ +export const mockDataTypes: DataTypeElement[] = [ { id: 'dataTypeId', }, diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 9c51e9879c6..8d2acce8fc7 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -869,12 +869,12 @@ "schema_editor.error_could_not_detect_taskType": "Kunne ikke hente oppgavetype for {{layout}}", "schema_editor.error_could_not_detect_taskType_description": "Dette gjør at vi ikke kan vise riktig liste over tilgjengelige komponenter. Velg en annen sidegruppe eller prøv å last siden på nytt.", "schema_editor.error_data_type_name_exists": "Modellen kan ikke ha samme navn som datatyper i løsningen.", - "schema_editor.error_depth": "Du kan ikke plassere gruppen her, fordi skjemaet får for mange nivåer.", - "schema_editor.error_invalid_child": "Du kan ikke plassere den komponenttypen i denne gruppen.", - "schema_editor.error_invalid_datamodel_name": "Navnet er ugyldig. Det første tegnet må være en bokstav (a-z). Deretter kan du bruke bokstaver (a-å), tall, understrek og bindestrek.", + "schema_editor.error_depth": "Du kan ikke plassere denne gruppen her, fordi skjemaet får for mange nivåer.", + "schema_editor.error_invalid_child": "Du kan ikke plassere denne komponenttypen i denne gruppen.", + "schema_editor.error_invalid_datamodel_name": "Navnet er ugyldig. Det første tegnet må være en stor eller liten bokstav fra a til z. Deretter kan du bruke små eller store bokstaver fra a til å, tall, understrek og bindestrek.", "schema_editor.error_model_name_exists": "Modellnavnet {{newModelName}} er allerede i bruk.", - "schema_editor.error_reserved_keyword": "Navnet er reservert og kan ikke brukes.", - "schema_editor.error_upload_data_model_id_exists_override_option": "Det eksisterer allerede en modell med dette navnet. Ønsker du å overskrive den?", + "schema_editor.error_reserved_keyword": "Du kan ikke bruke dette navnet, det er allerede reservert.", + "schema_editor.error_upload_data_model_id_exists_override_option": "Det finnes allerede en datamodell med dette navnet. Vil du overskrive den?", "schema_editor.field": "Objekt", "schema_editor.field_name": "Navn på felt", "schema_editor.fields": "Felter", @@ -1792,12 +1792,12 @@ "ux_editor.upload_file_error_too_large": "Kunne ikke laste opp filen. Den er for stor.", "ux_editor.url_label": "Lenke", "ux_editor.warning": "Advarsel", - "validation_errors.length": "Antall tillatte tegn er {{0}}.", - "validation_errors.max": "Største gyldig verdi er {{0}}.", - "validation_errors.maxLength": "Bruk {{0}} eller færre tegn.", - "validation_errors.min": "Minste gyldig verdi er {{0}}.", - "validation_errors.minLength": "Bruk {{0}} eller flere tegn.", - "validation_errors.numbers_only": "Kun sifre er gyldige tegn.", + "validation_errors.length": "Antall tillatte tegn er {{number}}.", + "validation_errors.max": "Største gyldige verdi er {{number}}.", + "validation_errors.maxLength": "Bruk {{number}} eller færre tegn.", + "validation_errors.min": "Minste gyldige verdi er {{number}}.", + "validation_errors.minLength": "Bruk {{number}} eller flere tegn.", + "validation_errors.numbers_only": "Du kan bare bruke sifre.", "validation_errors.pattern": "Feil format eller verdi.", "validation_errors.required": "Feltet må fylles ut.", "validation_errors.value_as_url": "Ugyldig lenke." From 8a9b1ab44c37d4df87b6e0bd0b4169715f9288a1 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 18 Nov 2024 14:48:23 +0100 Subject: [PATCH 13/37] Fix type import --- .../SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx index 5ba40bd20a9..6068c5d302d 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx @@ -1,4 +1,4 @@ -import type { ChangeEvent } from 'react'; +import type { ChangeEvent, KeyboardEvent } from 'react'; import React, { useState } from 'react'; import classes from './CreateNewWrapper.module.css'; import { useTranslation } from 'react-i18next'; From 3f8917b90216374055e7830c6eb34a9c42a001ea Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 18 Nov 2024 14:54:17 +0100 Subject: [PATCH 14/37] Add test case for " " and "." --- .../hooks/useValidateSchemaName.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.test.ts b/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.test.ts index 3f1b7b91bef..140164e98f0 100644 --- a/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.test.ts +++ b/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.test.ts @@ -159,6 +159,21 @@ describe('useValidateSchemaName', () => { expect(result.current.nameError).toBe(''); }); + + it('should disallow " " and "." in name', () => { + const { result } = renderUseValidateSchemaName(); + const invalidCharacters = [' ', '.']; + + invalidCharacters.forEach((char) => { + act(() => { + result.current.validateName('a' + char); + }); + + expect(result.current.nameError).toBe( + textMock('schema_editor.error_invalid_datamodel_name'), + ); + }); + }); }); }); From d3cda35fdf5bfcda70b25ba1f2843d652e195b98 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Mon, 18 Nov 2024 15:00:48 +0100 Subject: [PATCH 15/37] Remove Norwegian characters from test for '-', '_' --- .../features/dataModelling/hooks/useValidateSchemaName.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.test.ts b/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.test.ts index 140164e98f0..4a674d0f1c5 100644 --- a/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.test.ts +++ b/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.test.ts @@ -137,7 +137,7 @@ describe('useValidateSchemaName', () => { it('should disallow "-" and "_" at start of name', () => { const { result } = renderUseValidateSchemaName(); - const invalidFirstCharacters = ['-', '_', 'æ', 'ø', 'å', 'Æ', 'Ø', 'Å']; + const invalidFirstCharacters = ['-', '_']; invalidFirstCharacters.forEach((char) => { act(() => { From e6c7bc9c45be6995df5edfc861600d0d3b1a0f35 Mon Sep 17 00:00:00 2001 From: Erling Hauan <148075168+ErlingHauan@users.noreply.github.com> Date: Tue, 19 Nov 2024 09:42:13 +0100 Subject: [PATCH 16/37] Update reserved keyword text --- frontend/language/src/nb.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 349b8899932..bfc93d151a8 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -873,7 +873,7 @@ "schema_editor.error_invalid_child": "Du kan ikke plassere denne komponenttypen i denne gruppen.", "schema_editor.error_invalid_datamodel_name": "Navnet er ugyldig. Det første tegnet må være en stor eller liten bokstav fra a til z. Deretter kan du bruke små eller store bokstaver fra a til å, tall, understrek og bindestrek.", "schema_editor.error_model_name_exists": "Modellnavnet {{newModelName}} er allerede i bruk.", - "schema_editor.error_reserved_keyword": "Du kan ikke bruke dette navnet, det er allerede reservert.", + "schema_editor.error_reserved_keyword": "Systemet bruker allerede dette navnet. Velg et annet navn.", "schema_editor.error_upload_data_model_id_exists_override_option": "Det finnes allerede en datamodell med dette navnet. Vil du overskrive den?", "schema_editor.field": "Objekt", "schema_editor.field_name": "Navn på felt", From 59613fc018063a9e92f003ae928a8392bf93c880 Mon Sep 17 00:00:00 2001 From: Erling Hauan <148075168+ErlingHauan@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:14:14 +0100 Subject: [PATCH 17/37] Update text for schema_editor.enum_empty --- frontend/language/src/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/language/src/en.json b/frontend/language/src/en.json index 01d142b9dca..70c71336d24 100644 --- a/frontend/language/src/en.json +++ b/frontend/language/src/en.json @@ -641,7 +641,7 @@ "schema_editor.description": "Description", "schema_editor.descriptive_fields": "Descriptive fields", "schema_editor.enum": "Valid values", - "schema_editor.enum_empty": "The list is empty—all values will be approved.", + "schema_editor.enum_empty": "The list is empty. All values will be approved.", "schema_editor.enum_error_duplicate": "The values must be unique.", "schema_editor.enum_legend": "List of valid values", "schema_editor.error_depth": "It is not possible to put the group here because it will make the form exceed the maximum allowed depth.", From 60d56d13e11ff686cf0d933c18ca50ea10377419 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 19 Nov 2024 14:35:20 +0100 Subject: [PATCH 18/37] Fix most PR comments --- .../TopToolbar/CreateNewWrapper.test.tsx | 3 +-- .../TopToolbar/SchemaSelect.module.css | 4 ++++ .../SchemaEditorWithToolbar/TopToolbar/SchemaSelect.tsx | 2 +- .../TopToolbar/TopToolbar.module.css | 5 ----- .../TopToolbar/TopToolbar.test.tsx | 4 ++-- .../dataModelling/hooks/useValidateSchemaName.test.ts | 4 ++-- frontend/app-development/test/applicationMetadataMock.ts | 5 +++-- frontend/language/src/nb.json | 8 ++++---- 8 files changed, 17 insertions(+), 18 deletions(-) create mode 100644 frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.module.css diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx index 340fb63c27e..4f4b24d15e5 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx @@ -14,7 +14,6 @@ import { APP_DEVELOPMENT_BASENAME } from 'app-shared/constants'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; // Test data: -const [testOrg, testApp] = ['testOrg', 'testApp']; const mockCreateDataModel = jest.fn(); const mockSetIsCreateNewOpen = jest.fn(); const defaultProps: CreateNewWrapperProps = { @@ -53,7 +52,7 @@ describe('CreateNewWrapper', () => { expect(mockSetIsCreateNewOpen).toHaveBeenCalledWith(false); }); - it('should show an error text when validation fails', async () => { + it('should disable confirm button and show an error text when validation fails', async () => { const user = userEvent.setup(); const newModelName = dataModel1NameMock; const errorMessage = textMock('schema_editor.error_model_name_exists', { newModelName }); diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.module.css b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.module.css new file mode 100644 index 00000000000..7914b8c1c12 --- /dev/null +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.module.css @@ -0,0 +1,4 @@ +.select { + cursor: pointer; + min-width: 20vw; +} diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.tsx index 75714cacb8f..d1ff68acf42 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.tsx @@ -6,7 +6,7 @@ import { } from '../../../../utils/metadataUtils'; import type { MetadataOption } from '../../../../types/MetadataOption'; import { NativeSelect } from '@digdir/designsystemet-react'; -import classes from './TopToolbar.module.css'; +import classes from './SchemaSelect.module.css'; import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; import { useTranslation } from 'react-i18next'; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.module.css b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.module.css index 0030bd7e41f..d5974be7f7a 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.module.css +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.module.css @@ -20,11 +20,6 @@ padding-top: 4px; } -.select { - cursor: pointer; - min-width: 20vw; -} - .generateButtonWrapper { flex: 1; } diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx index a1cf364d909..4f5f2efdd9b 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx @@ -26,7 +26,7 @@ const dataModelGenerationSuccessMessage = textMock( ); const savingText = textMock('general.saving'); -const setCreateNewOpen = jest.fn(); +const setIsCreateNewOpen = jest.fn(); const setSelectedOption = jest.fn(); const onSetSchemaGenerationErrorMessages = jest.fn(); const selectedOption: MetadataOption = convertMetadataToOption(jsonMetadata1Mock); @@ -34,7 +34,7 @@ const defaultProps: TopToolbarProps = { isCreateNewOpen: false, dataModels: [jsonMetadata1Mock], selectedOption, - setIsCreateNewOpen: setCreateNewOpen, + setIsCreateNewOpen, setSelectedOption, onSetSchemaGenerationErrorMessages, }; diff --git a/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.test.ts b/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.test.ts index 4a674d0f1c5..c1c243700af 100644 --- a/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.test.ts +++ b/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.test.ts @@ -6,7 +6,7 @@ import { dataModel1NameMock, jsonMetadata1Mock, } from '../../../../packages/schema-editor/test/mocks/metadataMocks'; -import { mockAppMetadata, mockDataTypes } from '../../../test/applicationMetadataMock'; +import { mockAppMetadata, mockDataTypeId } from '../../../test/applicationMetadataMock'; import { renderHookWithProviders } from '../../../test/mocks'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import { QueryKey } from 'app-shared/types/QueryKey'; @@ -68,7 +68,7 @@ describe('useValidateSchemaName', () => { const { result } = renderUseValidateSchemaName(); act(() => { - result.current.validateName(mockDataTypes[0].id); + result.current.validateName(mockDataTypeId); }); expect(result.current.nameError).toBe(textMock('schema_editor.error_data_type_name_exists')); diff --git a/frontend/app-development/test/applicationMetadataMock.ts b/frontend/app-development/test/applicationMetadataMock.ts index 600be4fc2c3..5d407df4d09 100644 --- a/frontend/app-development/test/applicationMetadataMock.ts +++ b/frontend/app-development/test/applicationMetadataMock.ts @@ -35,9 +35,10 @@ const mockOnEntry: OnEntry = { show: 'select-instance', }; -export const mockDataTypes: DataTypeElement[] = [ +export const mockDataTypeId: string = 'mockDataTypeId'; +const mockDataTypes: DataTypeElement[] = [ { - id: 'dataTypeId', + id: mockDataTypeId, }, ]; diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 349b8899932..4b0c85aac48 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -1798,11 +1798,11 @@ "ux_editor.upload_file_error_too_large": "Kunne ikke laste opp filen. Den er for stor.", "ux_editor.url_label": "Lenke", "ux_editor.warning": "Advarsel", - "validation_errors.length": "Antall tillatte tegn er {{number}}.", - "validation_errors.max": "Største gyldige verdi er {{number}}.", + "validation_errors.length": "Antall tillatte tegn er {{0}}.", + "validation_errors.max": "Største gyldige verdi er {{0}}.", "validation_errors.maxLength": "Bruk {{number}} eller færre tegn.", - "validation_errors.min": "Minste gyldige verdi er {{number}}.", - "validation_errors.minLength": "Bruk {{number}} eller flere tegn.", + "validation_errors.min": "Minste gyldige verdi er {{0}}.", + "validation_errors.minLength": "Bruk {{0}} eller flere tegn.", "validation_errors.numbers_only": "Du kan bare bruke sifre.", "validation_errors.pattern": "Feil format eller verdi.", "validation_errors.required": "Feltet må fylles ut.", From adfad0d2201a9056dcbd619e10fbb3a190f4d642 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 19 Nov 2024 14:37:39 +0100 Subject: [PATCH 19/37] Fix datamodel landing page spacing --- .../SchemaEditorWithToolbar/LandingPagePanel.module.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/LandingPagePanel.module.css b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/LandingPagePanel.module.css index 5a67bd9d759..6fbebba2061 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/LandingPagePanel.module.css +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/LandingPagePanel.module.css @@ -1,9 +1,9 @@ .buttonWrapper { display: flex; flex-direction: row; - gap: var(--ds-spacing-3); - padding-top: var(--ds-spacing-3); - padding-bottom: var(--ds-spacing-3); + gap: var(--fds-spacing-3); + padding-top: var(--fds-spacing-3); + padding-bottom: var(--fds-spacing-3); align-items: center; } @@ -13,7 +13,7 @@ background-color: var(--fds-semantic-surface-info-subtle); box-shadow: 1px 1px 3px 2px rgb(0 0 0 / 25%); padding: var(--fds-spacing-10); - gap: var(--ds-spacing-8); + gap: var(--fds-spacing-8); width: 40%; height: fit-content; margin-top: var(--fds-spacing-10); From 2317835bf249491190f0aa5f938ac7eb8ffbea16 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 20 Nov 2024 08:39:42 +0100 Subject: [PATCH 20/37] Remove dataModels parameter from useValidateSchemaName Uses tanstack cache instead --- .../TopToolbar/CreateNewWrapper.tsx | 7 ++--- .../TopToolbar/TopToolbar.tsx | 1 - .../mutations/useGenerateModelsMutation.ts | 2 ++ .../src}/hooks/useValidateSchemaName.test.ts | 29 ++++++++++--------- .../src}/hooks/useValidateSchemaName.ts | 23 +++++++++++---- 5 files changed, 37 insertions(+), 25 deletions(-) rename frontend/{app-development/features/dataModelling => packages/shared/src}/hooks/useValidateSchemaName.test.ts (87%) rename frontend/{app-development/features/dataModelling => packages/shared/src}/hooks/useValidateSchemaName.ts (80%) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx index 6068c5d302d..f3b0244452d 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx @@ -3,16 +3,14 @@ import React, { useState } from 'react'; import classes from './CreateNewWrapper.module.css'; import { useTranslation } from 'react-i18next'; import { PlusIcon } from '@studio/icons'; -import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; import { StudioButton, StudioPopover, StudioTextfield } from '@studio/components'; -import { useValidateSchemaName } from '../../hooks/useValidateSchemaName'; +import { useValidateSchemaName } from 'app-shared/hooks/useValidateSchemaName'; import { useCreateDataModelMutation } from '../../../../hooks/mutations'; export interface CreateNewWrapperProps { disabled: boolean; isCreateNewOpen: boolean; createPathOption?: boolean; - dataModels: DataModelMetadata[]; setIsCreateNewOpen: (open: boolean) => void; } @@ -20,12 +18,11 @@ export function CreateNewWrapper({ disabled, createPathOption = false, isCreateNewOpen, - dataModels, setIsCreateNewOpen, }: CreateNewWrapperProps) { const { t } = useTranslation(); const [newModelName, setNewModelName] = useState(''); - const { validateName, nameError, setNameError } = useValidateSchemaName(dataModels); + const { validateName, nameError, setNameError } = useValidateSchemaName(); const { mutate: createDataModel } = useCreateDataModelMutation(); const isConfirmButtonActivated = newModelName && !nameError; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx index cf3ff87ccb6..5f9cce4f02c 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx @@ -42,7 +42,6 @@ export function TopToolbar({ return (
{ afterEach(() => { @@ -54,12 +57,12 @@ describe('useValidateSchemaName', () => { const { result } = renderUseValidateSchemaName(); act(() => { - result.current.validateName(dataModel1NameMock); + result.current.validateName(dataModelNameMock); }); expect(result.current.nameError).toBe( textMock('schema_editor.error_model_name_exists', { - newModelName: dataModel1NameMock, + newModelName: dataModelNameMock, }), ); }); @@ -178,14 +181,14 @@ describe('useValidateSchemaName', () => { }); const renderUseValidateSchemaName = () => { - const dataModels: DataModelMetadata[] = [jsonMetadata1Mock]; - const queryClient = createQueryClientMock(); queryClient.setQueryData([QueryKey.AppMetadata, org, app], mockAppMetadata); + queryClient.setQueryData([QueryKey.DataModelsJson, org, app], [jsonMetadataMock]); + queryClient.setQueryData([QueryKey.DataModelsXsd, org, app], [xsdMetadataMock]); const { renderHookResult: result } = renderHookWithProviders( {}, queryClient, - )(() => useValidateSchemaName(dataModels)); + )(() => useValidateSchemaName()); return result; }; diff --git a/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.ts b/frontend/packages/shared/src/hooks/useValidateSchemaName.ts similarity index 80% rename from frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.ts rename to frontend/packages/shared/src/hooks/useValidateSchemaName.ts index c7ede8bfdff..8c0de9b6dab 100644 --- a/frontend/app-development/features/dataModelling/hooks/useValidateSchemaName.ts +++ b/frontend/packages/shared/src/hooks/useValidateSchemaName.ts @@ -1,17 +1,28 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useAppMetadataQuery } from 'app-shared/hooks/queries'; -import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; -import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; -import type { ApplicationMetadata, DataTypeElement } from 'app-shared/types/ApplicationMetadata'; -import { extractModelNamesFromMetadataList } from '../../../utils/metadataUtils'; +import { + useAppMetadataQuery, + useDataModelsJsonQuery, + useDataModelsXsdQuery, +} from '../hooks/queries'; +import { useStudioEnvironmentParams } from '../hooks/useStudioEnvironmentParams'; +import type { DataModelMetadata } from '../types/DataModelMetadata'; +import type { ApplicationMetadata, DataTypeElement } from '../types/ApplicationMetadata'; +import { + extractModelNamesFromMetadataList, + mergeJsonAndXsdData, +} from '../../../../app-development/utils/metadataUtils'; -export const useValidateSchemaName = (dataModels: DataModelMetadata[]) => { +export const useValidateSchemaName = () => { const [nameError, setNameError] = useState(''); const { org, app } = useStudioEnvironmentParams(); const { data: appMetadata } = useAppMetadataQuery(org, app); + const { data: jsonData } = useDataModelsJsonQuery(org, app); + const { data: xsdData } = useDataModelsXsdQuery(org, app); const { t } = useTranslation(); + const dataModels = mergeJsonAndXsdData(jsonData, xsdData); + const validateName = (name: string): void => { if (!name) { setNameError(t('validation_errors.required')); From 454c681fca56c728a3d6f87ad2499d409da80dd5 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 20 Nov 2024 09:24:56 +0100 Subject: [PATCH 21/37] Memoize result of mergeJsonAndXsdData --- frontend/packages/shared/src/hooks/useValidateSchemaName.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/packages/shared/src/hooks/useValidateSchemaName.ts b/frontend/packages/shared/src/hooks/useValidateSchemaName.ts index 8c0de9b6dab..488e92b9a80 100644 --- a/frontend/packages/shared/src/hooks/useValidateSchemaName.ts +++ b/frontend/packages/shared/src/hooks/useValidateSchemaName.ts @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useAppMetadataQuery, @@ -21,7 +21,7 @@ export const useValidateSchemaName = () => { const { data: xsdData } = useDataModelsXsdQuery(org, app); const { t } = useTranslation(); - const dataModels = mergeJsonAndXsdData(jsonData, xsdData); + const dataModels = useMemo(() => mergeJsonAndXsdData(jsonData, xsdData), [jsonData, xsdData]); const validateName = (name: string): void => { if (!name) { From 8b7b717a2452fa682ac247b53ab5044b7158cccf Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 20 Nov 2024 09:43:58 +0100 Subject: [PATCH 22/37] Fix test --- .../TopToolbar/CreateNewWrapper.test.tsx | 37 +++++++++---------- .../shared/src/hooks/useValidateSchemaName.ts | 2 +- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx index 4f4b24d15e5..e5de3dc66fe 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx @@ -4,21 +4,18 @@ import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event' import type { CreateNewWrapperProps } from './CreateNewWrapper'; import { CreateNewWrapper } from './CreateNewWrapper'; import { textMock } from '@studio/testing/mocks/i18nMock'; -import { - dataModel1NameMock, - jsonMetadata1Mock, -} from '../../../../../packages/schema-editor/test/mocks/metadataMocks'; import { renderWithProviders } from '../../../../test/testUtils'; import { app, org } from '@studio/testing/testids'; import { APP_DEVELOPMENT_BASENAME } from 'app-shared/constants'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; +import { QueryKey } from 'app-shared/types/QueryKey'; +import { jsonMetadataMock, xsdMetadataMock } from 'app-shared/mocks/dataModelMetadataMocks'; // Test data: const mockCreateDataModel = jest.fn(); const mockSetIsCreateNewOpen = jest.fn(); const defaultProps: CreateNewWrapperProps = { isCreateNewOpen: false, - dataModels: [], disabled: false, setIsCreateNewOpen: mockSetIsCreateNewOpen, }; @@ -28,7 +25,7 @@ describe('CreateNewWrapper', () => { it('should open the popup when clicking "new" button', async () => { const user = userEvent.setup(); - render(); + renderCreateNewWrapper(); expect(queryInputField()).not.toBeInTheDocument(); expect(queryConfirmButton()).not.toBeInTheDocument(); @@ -41,7 +38,7 @@ describe('CreateNewWrapper', () => { it('should close the popup when clicking "new" button', async () => { const user = userEvent.setup(); - render({ isCreateNewOpen: true }); + renderCreateNewWrapper({ isCreateNewOpen: true }); expect(queryInputField()).toBeInTheDocument(); expect(queryConfirmButton()).toBeInTheDocument(); @@ -54,9 +51,9 @@ describe('CreateNewWrapper', () => { it('should disable confirm button and show an error text when validation fails', async () => { const user = userEvent.setup(); - const newModelName = dataModel1NameMock; - const errorMessage = textMock('schema_editor.error_model_name_exists', { newModelName }); - render({ isCreateNewOpen: true, dataModels: [jsonMetadata1Mock] }); + const newModelName = '_InvalidName'; + const errorMessage = textMock('schema_editor.error_invalid_datamodel_name'); + renderCreateNewWrapper({ isCreateNewOpen: true }); expect(queryErrorMessage(errorMessage)).not.toBeInTheDocument(); @@ -69,12 +66,12 @@ describe('CreateNewWrapper', () => { describe('createDataModel', () => { it('should call createDataModel when confirm button is clicked', async () => { const user = userEvent.setup(); - render({ isCreateNewOpen: true }); + renderCreateNewWrapper({ isCreateNewOpen: true }); await user.type(queryInputField(), 'new-model'); await user.click(queryConfirmButton()); - expect(mockCreateDataModel).toHaveBeenCalledWith(testOrg, testApp, { + expect(mockCreateDataModel).toHaveBeenCalledWith(org, app, { modelName: 'new-model', relativeDirectory: undefined, }); @@ -82,12 +79,12 @@ describe('CreateNewWrapper', () => { it('should call createDataModel when input is focused and Enter key is pressed', async () => { const user = userEvent.setup(); - render({ isCreateNewOpen: true }); + renderCreateNewWrapper({ isCreateNewOpen: true }); await user.type(queryInputField(), 'new-model'); await user.keyboard('{Enter}'); - expect(mockCreateDataModel).toHaveBeenCalledWith(testOrg, testApp, { + expect(mockCreateDataModel).toHaveBeenCalledWith(org, app, { modelName: 'new-model', relativeDirectory: undefined, }); @@ -95,12 +92,12 @@ describe('CreateNewWrapper', () => { it('should call createDataModel with relativePath when createPathOption is set and ok button is clicked', async () => { const user = userEvent.setup(); - render({ isCreateNewOpen: true, createPathOption: true }); + renderCreateNewWrapper({ isCreateNewOpen: true, createPathOption: true }); await user.type(queryInputField(), 'new-model'); await user.click(queryConfirmButton()); - expect(mockCreateDataModel).toHaveBeenCalledWith(testOrg, testApp, { + expect(mockCreateDataModel).toHaveBeenCalledWith(org, app, { modelName: 'new-model', relativeDirectory: '', }); @@ -110,7 +107,7 @@ describe('CreateNewWrapper', () => { const userWithNoPointerEventCheck = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never, }); - render({ isCreateNewOpen: true, dataModels: [jsonMetadata1Mock] }); + renderCreateNewWrapper({ isCreateNewOpen: true }); await userWithNoPointerEventCheck.click(queryConfirmButton()); @@ -121,7 +118,7 @@ describe('CreateNewWrapper', () => { const userWithNoPointerEventCheck = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never, }); - render({ isCreateNewOpen: true, dataModels: [jsonMetadata1Mock] }); + renderCreateNewWrapper({ isCreateNewOpen: true }); await userWithNoPointerEventCheck.keyboard('{Enter}'); @@ -142,10 +139,12 @@ const queryErrorMessage = (errorMessage: string) => { const queryConfirmButton = () => screen.queryByRole('button', { name: textMock('schema_editor.create_model_confirm_button') }); -const render = ( +const renderCreateNewWrapper = ( props: Partial = {}, queryClient = createQueryClientMock(), ) => { + queryClient.setQueryData([QueryKey.DataModelsJson, org, app], [jsonMetadataMock]); + queryClient.setQueryData([QueryKey.DataModelsXsd, org, app], [xsdMetadataMock]); renderWithProviders(, { queries: { createDataModel: mockCreateDataModel }, startUrl: `${APP_DEVELOPMENT_BASENAME}/${org}/${app}/ui-editor`, diff --git a/frontend/packages/shared/src/hooks/useValidateSchemaName.ts b/frontend/packages/shared/src/hooks/useValidateSchemaName.ts index 488e92b9a80..6bdba86ff31 100644 --- a/frontend/packages/shared/src/hooks/useValidateSchemaName.ts +++ b/frontend/packages/shared/src/hooks/useValidateSchemaName.ts @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useAppMetadataQuery, From cf8db9d3007f1c2cb164b126b43dd0a7f3624bf6 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Wed, 20 Nov 2024 10:14:41 +0100 Subject: [PATCH 23/37] Fix TopToolbar test by adding query data for JSON and XSD models --- .../TopToolbar/TopToolbar.test.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx index 4f5f2efdd9b..ce5fa3eeda5 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx @@ -4,7 +4,10 @@ import { TopToolbar } from './TopToolbar'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; -import { jsonMetadata1Mock } from '../../../../../packages/schema-editor/test/mocks/metadataMocks'; +import { + jsonMetadata1Mock, + xsdMetadata1Mock, +} from '../../../../../packages/schema-editor/test/mocks/metadataMocks'; import { QueryKey } from 'app-shared/types/QueryKey'; import { uiSchemaNodesMock } from '../../../../../packages/schema-editor/test/mocks/uiSchemaMock'; import type { MetadataOption } from '../../../../types/MetadataOption'; @@ -46,10 +49,13 @@ const renderToolbar = ( ) => { const TopToolbarWithInitData = () => { const queryClient = useQueryClient(); + queryClient.setQueryData([QueryKey.DataModelsJson, org, app], [jsonMetadata1Mock]); + queryClient.setQueryData([QueryKey.DataModelsXsd, org, app], [xsdMetadata1Mock]); queryClient.setQueryData( [QueryKey.JsonSchema, org, app, modelPath], buildJsonSchema(uiSchemaNodesMock), ); + return ; }; From a89ae52521f655e04bdde114226d5f7982749e94 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 21 Nov 2024 12:35:12 +0100 Subject: [PATCH 24/37] Remove fetching of data model names and data type names from useValidateSchemaName and improve test for existing data type name --- .../TopToolbar/CreateNewWrapper.test.tsx | 5 +- .../TopToolbar/CreateNewWrapper.tsx | 28 +++++++++-- .../TopToolbar/TopToolbar.tsx | 1 + .../src/hooks/useValidateSchemaName.test.ts | 48 ++++++++----------- .../shared/src/hooks/useValidateSchemaName.ts | 40 +++------------- 5 files changed, 54 insertions(+), 68 deletions(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx index e5de3dc66fe..a5515d335b7 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx @@ -8,8 +8,6 @@ import { renderWithProviders } from '../../../../test/testUtils'; import { app, org } from '@studio/testing/testids'; import { APP_DEVELOPMENT_BASENAME } from 'app-shared/constants'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; -import { QueryKey } from 'app-shared/types/QueryKey'; -import { jsonMetadataMock, xsdMetadataMock } from 'app-shared/mocks/dataModelMetadataMocks'; // Test data: const mockCreateDataModel = jest.fn(); @@ -18,6 +16,7 @@ const defaultProps: CreateNewWrapperProps = { isCreateNewOpen: false, disabled: false, setIsCreateNewOpen: mockSetIsCreateNewOpen, + dataModels: [], }; describe('CreateNewWrapper', () => { @@ -143,8 +142,6 @@ const renderCreateNewWrapper = ( props: Partial = {}, queryClient = createQueryClientMock(), ) => { - queryClient.setQueryData([QueryKey.DataModelsJson, org, app], [jsonMetadataMock]); - queryClient.setQueryData([QueryKey.DataModelsXsd, org, app], [xsdMetadataMock]); renderWithProviders(, { queries: { createDataModel: mockCreateDataModel }, startUrl: `${APP_DEVELOPMENT_BASENAME}/${org}/${app}/ui-editor`, diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx index f3b0244452d..77e485fc67f 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx @@ -6,11 +6,17 @@ import { PlusIcon } from '@studio/icons'; import { StudioButton, StudioPopover, StudioTextfield } from '@studio/components'; import { useValidateSchemaName } from 'app-shared/hooks/useValidateSchemaName'; import { useCreateDataModelMutation } from '../../../../hooks/mutations'; +import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; +import { extractModelNamesFromMetadataList } from '../../../../utils/metadataUtils'; +import type { ApplicationMetadata } from 'app-shared/types/ApplicationMetadata'; +import { useAppMetadataQuery } from 'app-shared/hooks/queries'; +import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; export interface CreateNewWrapperProps { disabled: boolean; isCreateNewOpen: boolean; createPathOption?: boolean; + dataModels: DataModelMetadata[]; setIsCreateNewOpen: (open: boolean) => void; } @@ -18,12 +24,20 @@ export function CreateNewWrapper({ disabled, createPathOption = false, isCreateNewOpen, + dataModels, setIsCreateNewOpen, }: CreateNewWrapperProps) { - const { t } = useTranslation(); - const [newModelName, setNewModelName] = useState(''); - const { validateName, nameError, setNameError } = useValidateSchemaName(); + const { org, app } = useStudioEnvironmentParams(); + const { data: appMetadata } = useAppMetadataQuery(org, app); const { mutate: createDataModel } = useCreateDataModelMutation(); + const dataModelNames = extractModelNamesFromMetadataList(dataModels); + const dataTypeNames = extractDataTypeNamesFromAppMetadata(appMetadata); + const { validateName, nameError, setNameError } = useValidateSchemaName( + dataModelNames, + dataTypeNames, + ); + const [newModelName, setNewModelName] = useState(''); + const { t } = useTranslation(); const isConfirmButtonActivated = newModelName && !nameError; const relativePath = createPathOption ? '' : undefined; @@ -89,3 +103,11 @@ export function CreateNewWrapper({ ); } + +const extractDataTypeNamesFromAppMetadata = (appMetadata?: ApplicationMetadata): string[] => { + if (appMetadata?.dataTypes) { + return appMetadata.dataTypes.map((dataType) => dataType.id); + } else { + return []; + } +}; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx index 5f9cce4f02c..cf3ff87ccb6 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.tsx @@ -42,6 +42,7 @@ export function TopToolbar({ return (
{ afterEach(() => { @@ -57,21 +50,29 @@ describe('useValidateSchemaName', () => { const { result } = renderUseValidateSchemaName(); act(() => { - result.current.validateName(dataModelNameMock); + result.current.validateName(existingModelName); }); expect(result.current.nameError).toBe( textMock('schema_editor.error_model_name_exists', { - newModelName: dataModelNameMock, + newModelName: existingModelName, }), ); }); - it('should set error when data type in appMetadata with same name exists', () => { + it('should set error when data type in appMetadata with same name exists, when the data type is not also a data model', () => { const { result } = renderUseValidateSchemaName(); act(() => { - result.current.validateName(mockDataTypeId); + result.current.validateName(existingModelName); + }); + + expect(result.current.nameError).not.toBe( + textMock('schema_editor.error_data_type_name_exists'), + ); + + act(() => { + result.current.validateName(existingDataTypeName); }); expect(result.current.nameError).toBe(textMock('schema_editor.error_data_type_name_exists')); @@ -181,14 +182,5 @@ describe('useValidateSchemaName', () => { }); const renderUseValidateSchemaName = () => { - const queryClient = createQueryClientMock(); - queryClient.setQueryData([QueryKey.AppMetadata, org, app], mockAppMetadata); - queryClient.setQueryData([QueryKey.DataModelsJson, org, app], [jsonMetadataMock]); - queryClient.setQueryData([QueryKey.DataModelsXsd, org, app], [xsdMetadataMock]); - const { renderHookResult: result } = renderHookWithProviders( - {}, - queryClient, - )(() => useValidateSchemaName()); - - return result; + return renderHook(() => useValidateSchemaName(dataModelNames, dataTypeNames)); }; diff --git a/frontend/packages/shared/src/hooks/useValidateSchemaName.ts b/frontend/packages/shared/src/hooks/useValidateSchemaName.ts index 6bdba86ff31..6ed3ed96f09 100644 --- a/frontend/packages/shared/src/hooks/useValidateSchemaName.ts +++ b/frontend/packages/shared/src/hooks/useValidateSchemaName.ts @@ -1,28 +1,13 @@ -import { useMemo, useState } from 'react'; +import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { - useAppMetadataQuery, - useDataModelsJsonQuery, - useDataModelsXsdQuery, -} from '../hooks/queries'; -import { useStudioEnvironmentParams } from '../hooks/useStudioEnvironmentParams'; -import type { DataModelMetadata } from '../types/DataModelMetadata'; -import type { ApplicationMetadata, DataTypeElement } from '../types/ApplicationMetadata'; -import { - extractModelNamesFromMetadataList, - mergeJsonAndXsdData, -} from '../../../../app-development/utils/metadataUtils'; -export const useValidateSchemaName = () => { +export const useValidateSchemaName = ( + existingDataModelNames: string[], + existingDataTypeNames: string[], +) => { const [nameError, setNameError] = useState(''); - const { org, app } = useStudioEnvironmentParams(); - const { data: appMetadata } = useAppMetadataQuery(org, app); - const { data: jsonData } = useDataModelsJsonQuery(org, app); - const { data: xsdData } = useDataModelsXsdQuery(org, app); const { t } = useTranslation(); - const dataModels = useMemo(() => mergeJsonAndXsdData(jsonData, xsdData), [jsonData, xsdData]); - const validateName = (name: string): void => { if (!name) { setNameError(t('validation_errors.required')); @@ -36,11 +21,11 @@ export const useValidateSchemaName = () => { setNameError(t('validation_errors.maxLength', { number: SCHEMA_NAME_MAX_LENGTH })); return; } - if (isExistingModelName(name, dataModels)) { + if (existingDataModelNames.includes(name)) { setNameError(t('schema_editor.error_model_name_exists', { newModelName: name })); return; } - if (isExistingDataTypeName(name, appMetadata)) { + if (existingDataTypeNames.includes(name)) { setNameError(t('schema_editor.error_data_type_name_exists')); return; } @@ -57,17 +42,6 @@ export const useValidateSchemaName = () => { export const SCHEMA_NAME_MAX_LENGTH: number = 100; const SCHEMA_NAME_REGEX: RegExp = /^[a-zA-Z][a-zA-Z0-9_\-æÆøØåÅ]*$/; -const isExistingDataTypeName = (id: string, appMetaData: ApplicationMetadata): DataTypeElement => { - return appMetaData?.dataTypes?.find( - (dataType: DataTypeElement) => dataType.id.toLowerCase() === id.toLowerCase(), - ); -}; - -const isExistingModelName = (name: string, dataModels: DataModelMetadata[]): boolean => { - const modelNames = extractModelNamesFromMetadataList(dataModels); - return modelNames.includes(name); -}; - const isCSharpReservedKeyword = (word: string): boolean => { const cSharpKeywords = new Set([ 'abstract', From 66b9b8909c33e6435f9617bfbbc21009073ab8cc Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 21 Nov 2024 13:25:16 +0100 Subject: [PATCH 25/37] Add tests for extractDataTypeNamesFromAppMetadata --- .../TopToolbar/CreateNewWrapper.test.tsx | 24 ++++++++++++++++++- .../TopToolbar/CreateNewWrapper.tsx | 4 +++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx index a5515d335b7..7d4da639d4c 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx @@ -1,13 +1,14 @@ import React from 'react'; import { screen } from '@testing-library/react'; import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event'; -import type { CreateNewWrapperProps } from './CreateNewWrapper'; +import { CreateNewWrapperProps, extractDataTypeNamesFromAppMetadata } from './CreateNewWrapper'; import { CreateNewWrapper } from './CreateNewWrapper'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { renderWithProviders } from '../../../../test/testUtils'; import { app, org } from '@studio/testing/testids'; import { APP_DEVELOPMENT_BASENAME } from 'app-shared/constants'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; +import { mockAppMetadata, mockDataTypeId } from '../../../../test/applicationMetadataMock'; // Test data: const mockCreateDataModel = jest.fn(); @@ -126,6 +127,27 @@ describe('CreateNewWrapper', () => { }); }); +describe('extractDataTypeNamesFromAppMetadata', () => { + it('should extract data type names when application metadata is provided', () => { + const dataTypeNames = extractDataTypeNamesFromAppMetadata(mockAppMetadata); + expect(dataTypeNames).toEqual([mockDataTypeId]); + }); + + it('should return an empty array when dataTypes is undefined', () => { + const mockAppMetadataCopy = { ...mockAppMetadata }; + delete mockAppMetadataCopy.dataTypes; + + const dataTypeNames = extractDataTypeNamesFromAppMetadata(mockAppMetadataCopy); + + expect(dataTypeNames).toEqual([]); + }); + + it('should return an empty array when application metadata is undefined', () => { + const dataTypeNames = extractDataTypeNamesFromAppMetadata(undefined); + expect(dataTypeNames).toEqual([]); + }); +}); + const getNewButton = () => screen.getByRole('button', { name: textMock('general.create_new') }); const queryInputField = () => diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx index 77e485fc67f..b3193b16334 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx @@ -104,7 +104,9 @@ export function CreateNewWrapper({ ); } -const extractDataTypeNamesFromAppMetadata = (appMetadata?: ApplicationMetadata): string[] => { +export const extractDataTypeNamesFromAppMetadata = ( + appMetadata?: ApplicationMetadata, +): string[] => { if (appMetadata?.dataTypes) { return appMetadata.dataTypes.map((dataType) => dataType.id); } else { From 392939ba79bb516768e3f926f766ee867079538a Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 21 Nov 2024 13:29:40 +0100 Subject: [PATCH 26/37] Remove data model query mocks from TopToolbar tests --- .../SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx index ce5fa3eeda5..064eeb6594f 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx @@ -49,8 +49,6 @@ const renderToolbar = ( ) => { const TopToolbarWithInitData = () => { const queryClient = useQueryClient(); - queryClient.setQueryData([QueryKey.DataModelsJson, org, app], [jsonMetadata1Mock]); - queryClient.setQueryData([QueryKey.DataModelsXsd, org, app], [xsdMetadata1Mock]); queryClient.setQueryData( [QueryKey.JsonSchema, org, app, modelPath], buildJsonSchema(uiSchemaNodesMock), From 92940190e313a2dc02e3c67bb45ac0b8a38f1bce Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 21 Nov 2024 13:38:14 +0100 Subject: [PATCH 27/37] Remove unused import --- .../SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx index 064eeb6594f..54239eabbfc 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/TopToolbar.test.tsx @@ -4,10 +4,7 @@ import { TopToolbar } from './TopToolbar'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; -import { - jsonMetadata1Mock, - xsdMetadata1Mock, -} from '../../../../../packages/schema-editor/test/mocks/metadataMocks'; +import { jsonMetadata1Mock } from '../../../../../packages/schema-editor/test/mocks/metadataMocks'; import { QueryKey } from 'app-shared/types/QueryKey'; import { uiSchemaNodesMock } from '../../../../../packages/schema-editor/test/mocks/uiSchemaMock'; import type { MetadataOption } from '../../../../types/MetadataOption'; From cc349d6b675393f45c8d66f33bea7effe55dd7c8 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Thu, 21 Nov 2024 13:42:06 +0100 Subject: [PATCH 28/37] Fix linting error --- .../TopToolbar/CreateNewWrapper.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx index 7d4da639d4c..6745ee939c3 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx @@ -1,8 +1,8 @@ import React from 'react'; import { screen } from '@testing-library/react'; import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event'; -import { CreateNewWrapperProps, extractDataTypeNamesFromAppMetadata } from './CreateNewWrapper'; -import { CreateNewWrapper } from './CreateNewWrapper'; +import type { CreateNewWrapperProps } from './CreateNewWrapper'; +import { extractDataTypeNamesFromAppMetadata, CreateNewWrapper } from './CreateNewWrapper'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { renderWithProviders } from '../../../../test/testUtils'; import { app, org } from '@studio/testing/testids'; From a38fdf005cff55d0e051025570b87704fc632617 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 22 Nov 2024 08:50:19 +0100 Subject: [PATCH 29/37] Refactoring - move extractDataTypeNamesFromAppMetadata into validationUtils.ts - move tests for extractDataTypeNamesFromAppMetadata into new file validationUtils.test.ts - create 'utils' and 'types' folder in 'features/dataModelling' - remove XSDUpload folder and move its contents into 'utils', 'types' and 'hooks' in 'features/datamodelling' - move data model name regex and data model length constants into 'shared/src/constants' - replace outdated data model name regex in existing validationUtils with the regex from 'constants' --- .../TopToolbar/CreateNewWrapper.test.tsx | 24 +------------------ .../TopToolbar/CreateNewWrapper.tsx | 12 +--------- .../{XSDUpload => }/XSDUpload.test.tsx | 2 +- .../TopToolbar/{XSDUpload => }/XSDUpload.tsx | 10 ++++---- .../TopToolbar/XSDUpload/index.ts | 1 - .../XSDUpload => hooks}/useValidationAlert.ts | 2 +- .../XSDUpload => types}/FileNameError.ts | 0 .../utils/validationUtils.test.ts | 23 ++++++++++++++++++ .../XSDUpload => utils}/validationUtils.ts | 16 ++++++++++--- frontend/packages/shared/src/constants.js | 3 ++- .../src/hooks/useValidateSchemaName.test.ts | 7 +++--- .../shared/src/hooks/useValidateSchemaName.ts | 10 ++++---- 12 files changed, 55 insertions(+), 55 deletions(-) rename frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/{XSDUpload => }/XSDUpload.test.tsx (99%) rename frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/{XSDUpload => }/XSDUpload.tsx (90%) delete mode 100644 frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/index.ts rename frontend/app-development/features/dataModelling/{SchemaEditorWithToolbar/TopToolbar/XSDUpload => hooks}/useValidationAlert.ts (85%) rename frontend/app-development/features/dataModelling/{SchemaEditorWithToolbar/TopToolbar/XSDUpload => types}/FileNameError.ts (100%) create mode 100644 frontend/app-development/features/dataModelling/utils/validationUtils.test.ts rename frontend/app-development/features/dataModelling/{SchemaEditorWithToolbar/TopToolbar/XSDUpload => utils}/validationUtils.ts (78%) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx index 6745ee939c3..a5515d335b7 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx @@ -2,13 +2,12 @@ import React from 'react'; import { screen } from '@testing-library/react'; import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event'; import type { CreateNewWrapperProps } from './CreateNewWrapper'; -import { extractDataTypeNamesFromAppMetadata, CreateNewWrapper } from './CreateNewWrapper'; +import { CreateNewWrapper } from './CreateNewWrapper'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { renderWithProviders } from '../../../../test/testUtils'; import { app, org } from '@studio/testing/testids'; import { APP_DEVELOPMENT_BASENAME } from 'app-shared/constants'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; -import { mockAppMetadata, mockDataTypeId } from '../../../../test/applicationMetadataMock'; // Test data: const mockCreateDataModel = jest.fn(); @@ -127,27 +126,6 @@ describe('CreateNewWrapper', () => { }); }); -describe('extractDataTypeNamesFromAppMetadata', () => { - it('should extract data type names when application metadata is provided', () => { - const dataTypeNames = extractDataTypeNamesFromAppMetadata(mockAppMetadata); - expect(dataTypeNames).toEqual([mockDataTypeId]); - }); - - it('should return an empty array when dataTypes is undefined', () => { - const mockAppMetadataCopy = { ...mockAppMetadata }; - delete mockAppMetadataCopy.dataTypes; - - const dataTypeNames = extractDataTypeNamesFromAppMetadata(mockAppMetadataCopy); - - expect(dataTypeNames).toEqual([]); - }); - - it('should return an empty array when application metadata is undefined', () => { - const dataTypeNames = extractDataTypeNamesFromAppMetadata(undefined); - expect(dataTypeNames).toEqual([]); - }); -}); - const getNewButton = () => screen.getByRole('button', { name: textMock('general.create_new') }); const queryInputField = () => diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx index b3193b16334..e602e7ec55a 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx @@ -8,9 +8,9 @@ import { useValidateSchemaName } from 'app-shared/hooks/useValidateSchemaName'; import { useCreateDataModelMutation } from '../../../../hooks/mutations'; import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; import { extractModelNamesFromMetadataList } from '../../../../utils/metadataUtils'; -import type { ApplicationMetadata } from 'app-shared/types/ApplicationMetadata'; import { useAppMetadataQuery } from 'app-shared/hooks/queries'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; +import { extractDataTypeNamesFromAppMetadata } from '../../utils/validationUtils'; export interface CreateNewWrapperProps { disabled: boolean; @@ -103,13 +103,3 @@ export function CreateNewWrapper({ ); } - -export const extractDataTypeNamesFromAppMetadata = ( - appMetadata?: ApplicationMetadata, -): string[] => { - if (appMetadata?.dataTypes) { - return appMetadata.dataTypes.map((dataType) => dataType.id); - } else { - return []; - } -}; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/XSDUpload.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload.test.tsx similarity index 99% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/XSDUpload.test.tsx rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload.test.tsx index af38ca89f6e..4606e86537c 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/XSDUpload.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload.test.tsx @@ -6,7 +6,7 @@ import { textMock } from '@studio/testing/mocks/i18nMock'; import type { QueryClient } from '@tanstack/react-query'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import { app, org } from '@studio/testing/testids'; -import { renderWithProviders } from '../../../../../test/mocks'; +import { renderWithProviders } from '../../../../test/mocks'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; import { createApiErrorMock } from 'app-shared/mocks/apiErrorMock'; import { QueryKey } from 'app-shared/types/QueryKey'; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/XSDUpload.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload.tsx similarity index 90% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/XSDUpload.tsx rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload.tsx index 5919dfbd758..252631b5e6a 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/XSDUpload.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload.tsx @@ -2,21 +2,21 @@ import React from 'react'; import type { StudioButtonProps } from '@studio/components'; import { StudioFileUploader, StudioSpinner } from '@studio/components'; import { useTranslation } from 'react-i18next'; -import { useUploadDataModelMutation } from '../../../../../hooks/mutations/useUploadDataModelMutation'; +import { useUploadDataModelMutation } from '../../../../hooks/mutations/useUploadDataModelMutation'; import type { AxiosError } from 'axios'; import type { ApiError } from 'app-shared/types/api/ApiError'; import { toast } from 'react-toastify'; -import type { MetadataOption } from '../../../../../types/MetadataOption'; +import type { MetadataOption } from '../../../../types/MetadataOption'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; import { useAppMetadataQuery } from 'app-shared/hooks/queries'; -import { useValidationAlert } from './useValidationAlert'; +import { useValidationAlert } from '../../hooks/useValidationAlert'; import { removeExtension } from 'app-shared/utils/filenameUtils'; import { doesFileExistInMetadataWithClassRef, doesFileExistInMetadataWithoutClassRef, findFileNameError, -} from './validationUtils'; -import type { FileNameError } from './FileNameError'; +} from '../../utils/validationUtils'; +import type { FileNameError } from '../../types/FileNameError'; export interface XSDUploadProps { selectedOption?: MetadataOption; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/index.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/index.ts deleted file mode 100644 index a4bde7e8040..00000000000 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './XSDUpload'; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/useValidationAlert.ts b/frontend/app-development/features/dataModelling/hooks/useValidationAlert.ts similarity index 85% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/useValidationAlert.ts rename to frontend/app-development/features/dataModelling/hooks/useValidationAlert.ts index 8deda17be23..396237a142d 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/useValidationAlert.ts +++ b/frontend/app-development/features/dataModelling/hooks/useValidationAlert.ts @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next'; import { useCallback } from 'react'; -import type { FileNameError } from './FileNameError'; +import type { FileNameError } from '../types/FileNameError'; export const useValidationAlert = () => { const { t } = useTranslation(); diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/FileNameError.ts b/frontend/app-development/features/dataModelling/types/FileNameError.ts similarity index 100% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/FileNameError.ts rename to frontend/app-development/features/dataModelling/types/FileNameError.ts diff --git a/frontend/app-development/features/dataModelling/utils/validationUtils.test.ts b/frontend/app-development/features/dataModelling/utils/validationUtils.test.ts new file mode 100644 index 00000000000..707f4d698ba --- /dev/null +++ b/frontend/app-development/features/dataModelling/utils/validationUtils.test.ts @@ -0,0 +1,23 @@ +import { mockAppMetadata, mockDataTypeId } from '../../../test/applicationMetadataMock'; +import { extractDataTypeNamesFromAppMetadata } from './validationUtils'; + +describe('extractDataTypeNamesFromAppMetadata', () => { + it('should extract data type names when application metadata is provided', () => { + const dataTypeNames = extractDataTypeNamesFromAppMetadata(mockAppMetadata); + expect(dataTypeNames).toEqual([mockDataTypeId]); + }); + + it('should return an empty array when dataTypes is undefined', () => { + const mockAppMetadataCopy = { ...mockAppMetadata }; + delete mockAppMetadataCopy.dataTypes; + + const dataTypeNames = extractDataTypeNamesFromAppMetadata(mockAppMetadataCopy); + + expect(dataTypeNames).toEqual([]); + }); + + it('should return an empty array when application metadata is undefined', () => { + const dataTypeNames = extractDataTypeNamesFromAppMetadata(undefined); + expect(dataTypeNames).toEqual([]); + }); +}); diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/validationUtils.ts b/frontend/app-development/features/dataModelling/utils/validationUtils.ts similarity index 78% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/validationUtils.ts rename to frontend/app-development/features/dataModelling/utils/validationUtils.ts index 768ef1ee4d6..4c7ed6e8e70 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/validationUtils.ts +++ b/frontend/app-development/features/dataModelling/utils/validationUtils.ts @@ -1,6 +1,7 @@ import type { ApplicationMetadata } from 'app-shared/types/ApplicationMetadata'; import { removeExtension } from 'app-shared/utils/filenameUtils'; -import type { FileNameError } from './FileNameError'; +import type { FileNameError } from '../types/FileNameError'; +import { DATA_MODEL_NAME_REGEX } from 'app-shared/constants'; export const doesFileExistInMetadataWithClassRef = ( appMetadata: ApplicationMetadata, @@ -39,11 +40,20 @@ export const findFileNameError = ( }; const isNameFormatValid = (fileNameWithoutExtension: string): boolean => { - const fileNameRegex: RegExp = /^[a-zA-Z][a-zA-Z0-9_.\-æÆøØåÅ ]*$/; - return Boolean(fileNameWithoutExtension.match(fileNameRegex)); + return Boolean(fileNameWithoutExtension.match(DATA_MODEL_NAME_REGEX)); }; const doesFileExistInMetadata = ( appMetadata: ApplicationMetadata, fileNameWithoutExtension: string, ): boolean => appMetadata.dataTypes?.some((dataType) => dataType.id === fileNameWithoutExtension); + +export const extractDataTypeNamesFromAppMetadata = ( + appMetadata?: ApplicationMetadata, +): string[] => { + if (appMetadata?.dataTypes) { + return appMetadata.dataTypes.map((dataType) => dataType.id); + } else { + return []; + } +}; diff --git a/frontend/packages/shared/src/constants.js b/frontend/packages/shared/src/constants.js index 193e01a5748..67b503f50be 100644 --- a/frontend/packages/shared/src/constants.js +++ b/frontend/packages/shared/src/constants.js @@ -19,5 +19,6 @@ export const PROD_ENV_TYPE = 'production'; export const PROTECTED_TASK_NAME_CUSTOM_RECEIPT = 'CustomReceipt'; export const PREVIEW_MOCK_PARTY_ID = '51001'; export const PREVIEW_MOCK_INSTANCE_GUID = 'f1e23d45-6789-1bcd-8c34-56789abcdef0'; - export const MEDIA_QUERY_MAX_WIDTH = '(max-width: 1024px)'; +export const DATA_MODEL_NAME_MAX_LENGTH = 100; +export const DATA_MODEL_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_\-æÆøØåÅ]*$/; diff --git a/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts b/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts index 726c2e2e5c6..968b82a1111 100644 --- a/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts +++ b/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts @@ -1,6 +1,7 @@ -import { SCHEMA_NAME_MAX_LENGTH, useValidateSchemaName } from './useValidateSchemaName'; +import { useValidateSchemaName } from './useValidateSchemaName'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { act, renderHook } from '@testing-library/react'; +import { DATA_MODEL_NAME_MAX_LENGTH } from 'app-shared/constants'; // Test data const existingModelName = 'existingModelName'; @@ -34,7 +35,7 @@ describe('useValidateSchemaName', () => { }); it('should set error when name exceeds max length', () => { - const longName = 'a'.repeat(SCHEMA_NAME_MAX_LENGTH + 1); + const longName = 'a'.repeat(DATA_MODEL_NAME_MAX_LENGTH + 1); const { result } = renderUseValidateSchemaName(); act(() => { @@ -42,7 +43,7 @@ describe('useValidateSchemaName', () => { }); expect(result.current.nameError).toBe( - textMock('validation_errors.maxLength', { number: SCHEMA_NAME_MAX_LENGTH }), + textMock('validation_errors.maxLength', { number: DATA_MODEL_NAME_MAX_LENGTH }), ); }); diff --git a/frontend/packages/shared/src/hooks/useValidateSchemaName.ts b/frontend/packages/shared/src/hooks/useValidateSchemaName.ts index 6ed3ed96f09..4232eb9f09b 100644 --- a/frontend/packages/shared/src/hooks/useValidateSchemaName.ts +++ b/frontend/packages/shared/src/hooks/useValidateSchemaName.ts @@ -1,5 +1,6 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { DATA_MODEL_NAME_MAX_LENGTH, DATA_MODEL_NAME_REGEX } from 'app-shared/constants'; export const useValidateSchemaName = ( existingDataModelNames: string[], @@ -13,12 +14,12 @@ export const useValidateSchemaName = ( setNameError(t('validation_errors.required')); return; } - if (!name.match(SCHEMA_NAME_REGEX)) { + if (!name.match(DATA_MODEL_NAME_REGEX)) { setNameError(t('schema_editor.error_invalid_datamodel_name')); return; } - if (name.length > SCHEMA_NAME_MAX_LENGTH) { - setNameError(t('validation_errors.maxLength', { number: SCHEMA_NAME_MAX_LENGTH })); + if (name.length > DATA_MODEL_NAME_MAX_LENGTH) { + setNameError(t('validation_errors.maxLength', { number: DATA_MODEL_NAME_MAX_LENGTH })); return; } if (existingDataModelNames.includes(name)) { @@ -39,9 +40,6 @@ export const useValidateSchemaName = ( return { validateName, nameError, setNameError }; }; -export const SCHEMA_NAME_MAX_LENGTH: number = 100; -const SCHEMA_NAME_REGEX: RegExp = /^[a-zA-Z][a-zA-Z0-9_\-æÆøØåÅ]*$/; - const isCSharpReservedKeyword = (word: string): boolean => { const cSharpKeywords = new Set([ 'abstract', From b5363a544f0cc8eec09a744f305752b2d5ace54c Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 22 Nov 2024 09:41:38 +0100 Subject: [PATCH 30/37] Fix import paths in XSDUpload test --- .../SchemaEditorWithToolbar/TopToolbar/XSDUpload.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload.test.tsx index 4606e86537c..f210f2e926b 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload.test.tsx @@ -14,13 +14,13 @@ import { queriesMock } from 'app-shared/mocks/queriesMock'; const user = userEvent.setup(); -jest.mock('../../../../../hooks/mutations/useUploadDataModelMutation', () => ({ +jest.mock('../../../../hooks/mutations/useUploadDataModelMutation', () => ({ __esModule: true, - ...jest.requireActual('../../../../../hooks/mutations/useUploadDataModelMutation'), + ...jest.requireActual('../../../../hooks/mutations/useUploadDataModelMutation'), })); const useUploadDataModelMutationSpy = jest.spyOn( - require('../../../../../hooks/mutations/useUploadDataModelMutation'), + require('../../../../hooks/mutations/useUploadDataModelMutation'), 'useUploadDataModelMutation', ); From fcc0e6a7fd20c7164fc589f51e1039d8f075578a Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 22 Nov 2024 13:05:04 +0100 Subject: [PATCH 31/37] Change folder structure --- .../{ => CreateNewWrapper}/CreateNewWrapper.module.css | 0 .../{ => CreateNewWrapper}/CreateNewWrapper.test.tsx | 2 +- .../{ => CreateNewWrapper}/CreateNewWrapper.tsx | 6 +++--- .../TopToolbar/CreateNewWrapper/index.ts | 1 + .../{ => DeleteWrapper}/DeleteWrapper.module.css | 0 .../{ => DeleteWrapper}/DeleteWrapper.test.tsx | 6 +++--- .../TopToolbar/{ => DeleteWrapper}/DeleteWrapper.tsx | 4 ++-- .../TopToolbar/DeleteWrapper/index.ts | 1 + .../GenerateModelsButton.tsx | 4 ++-- .../TopToolbar/GenerateModelsButton/index.ts | 1 + .../{ => SchemaSelect}/SchemaSelect.module.css | 0 .../{ => SchemaSelect}/SchemaSelect.test.tsx | 4 ++-- .../TopToolbar/{ => SchemaSelect}/SchemaSelect.tsx | 4 ++-- .../TopToolbar/SchemaSelect/index.ts | 1 + .../TopToolbar/{ => XSDUpload}/XSDUpload.test.tsx | 8 ++++---- .../TopToolbar/{ => XSDUpload}/XSDUpload.tsx | 10 +++++----- .../TopToolbar/XSDUpload/index.ts | 1 + .../TopToolbar/XSDUpload}/useValidationAlert.ts | 0 .../TopToolbar}/types/FileNameError.ts | 0 .../TopToolbar}/utils/validationUtils.test.ts | 2 +- .../TopToolbar}/utils/validationUtils.ts | 0 21 files changed, 30 insertions(+), 25 deletions(-) rename frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/{ => CreateNewWrapper}/CreateNewWrapper.module.css (100%) rename frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/{ => CreateNewWrapper}/CreateNewWrapper.test.tsx (98%) rename frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/{ => CreateNewWrapper}/CreateNewWrapper.tsx (92%) create mode 100644 frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper/index.ts rename frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/{ => DeleteWrapper}/DeleteWrapper.module.css (100%) rename frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/{ => DeleteWrapper}/DeleteWrapper.test.tsx (94%) rename frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/{ => DeleteWrapper}/DeleteWrapper.tsx (93%) create mode 100644 frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper/index.ts rename frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/{ => GenerateModelsButton}/GenerateModelsButton.tsx (91%) create mode 100644 frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/GenerateModelsButton/index.ts rename frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/{ => SchemaSelect}/SchemaSelect.module.css (100%) rename frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/{ => SchemaSelect}/SchemaSelect.test.tsx (92%) rename frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/{ => SchemaSelect}/SchemaSelect.tsx (93%) create mode 100644 frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect/index.ts rename frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/{ => XSDUpload}/XSDUpload.test.tsx (95%) rename frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/{ => XSDUpload}/XSDUpload.tsx (90%) create mode 100644 frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/index.ts rename frontend/app-development/features/dataModelling/{hooks => SchemaEditorWithToolbar/TopToolbar/XSDUpload}/useValidationAlert.ts (100%) rename frontend/app-development/features/dataModelling/{ => SchemaEditorWithToolbar/TopToolbar}/types/FileNameError.ts (100%) rename frontend/app-development/features/dataModelling/{ => SchemaEditorWithToolbar/TopToolbar}/utils/validationUtils.test.ts (90%) rename frontend/app-development/features/dataModelling/{ => SchemaEditorWithToolbar/TopToolbar}/utils/validationUtils.ts (100%) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.module.css b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper/CreateNewWrapper.module.css similarity index 100% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.module.css rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper/CreateNewWrapper.module.css diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper/CreateNewWrapper.test.tsx similarity index 98% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper/CreateNewWrapper.test.tsx index a5515d335b7..018b114c4f2 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper/CreateNewWrapper.test.tsx @@ -4,7 +4,7 @@ import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event' import type { CreateNewWrapperProps } from './CreateNewWrapper'; import { CreateNewWrapper } from './CreateNewWrapper'; import { textMock } from '@studio/testing/mocks/i18nMock'; -import { renderWithProviders } from '../../../../test/testUtils'; +import { renderWithProviders } from '../../../../../test/testUtils'; import { app, org } from '@studio/testing/testids'; import { APP_DEVELOPMENT_BASENAME } from 'app-shared/constants'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper/CreateNewWrapper.tsx similarity index 92% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper/CreateNewWrapper.tsx index e602e7ec55a..f647e15cdb7 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper/CreateNewWrapper.tsx @@ -5,12 +5,12 @@ import { useTranslation } from 'react-i18next'; import { PlusIcon } from '@studio/icons'; import { StudioButton, StudioPopover, StudioTextfield } from '@studio/components'; import { useValidateSchemaName } from 'app-shared/hooks/useValidateSchemaName'; -import { useCreateDataModelMutation } from '../../../../hooks/mutations'; +import { useCreateDataModelMutation } from '../../../../../hooks/mutations'; import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; -import { extractModelNamesFromMetadataList } from '../../../../utils/metadataUtils'; +import { extractModelNamesFromMetadataList } from '../../../../../utils/metadataUtils'; import { useAppMetadataQuery } from 'app-shared/hooks/queries'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; -import { extractDataTypeNamesFromAppMetadata } from '../../utils/validationUtils'; +import { extractDataTypeNamesFromAppMetadata } from '../utils/validationUtils'; export interface CreateNewWrapperProps { disabled: boolean; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper/index.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper/index.ts new file mode 100644 index 00000000000..38059909724 --- /dev/null +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/CreateNewWrapper/index.ts @@ -0,0 +1 @@ +export { CreateNewWrapper } from './CreateNewWrapper'; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper.module.css b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper/DeleteWrapper.module.css similarity index 100% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper.module.css rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper/DeleteWrapper.module.css diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper/DeleteWrapper.test.tsx similarity index 94% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper.test.tsx rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper/DeleteWrapper.test.tsx index 0ef1ed60ccb..ac81274898a 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper/DeleteWrapper.test.tsx @@ -7,11 +7,11 @@ import { textMock } from '@studio/testing/mocks/i18nMock'; import { jsonMetadata1Mock, jsonMetadata2Mock, -} from '../../../../../packages/schema-editor/test/mocks/metadataMocks'; +} from '../../../../../../packages/schema-editor/test/mocks/metadataMocks'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import { QueryKey } from 'app-shared/types/QueryKey'; -import { convertMetadataToOption } from '../../../../utils/metadataUtils'; -import { renderWithProviders } from '../../../../test/mocks'; +import { convertMetadataToOption } from '../../../../../utils/metadataUtils'; +import { renderWithProviders } from '../../../../../test/mocks'; import type { QueryClient } from '@tanstack/react-query'; import { app, org } from '@studio/testing/testids'; import { queriesMock } from 'app-shared/mocks/queriesMock'; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper/DeleteWrapper.tsx similarity index 93% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper.tsx rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper/DeleteWrapper.tsx index 370b3de58ec..1c0c12c5d01 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper/DeleteWrapper.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { StudioButton } from '@studio/components'; import { TrashIcon } from '@studio/icons'; -import { useDeleteDataModelMutation } from '../../../../hooks/mutations'; -import type { MetadataOption } from '../../../../types/MetadataOption'; +import { useDeleteDataModelMutation } from '../../../../../hooks/mutations'; +import type { MetadataOption } from '../../../../../types/MetadataOption'; import { AltinnConfirmDialog } from 'app-shared/components'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; import { useUpdateBpmn } from 'app-shared/hooks/useUpdateBpmn'; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper/index.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper/index.ts new file mode 100644 index 00000000000..84938ce007f --- /dev/null +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/DeleteWrapper/index.ts @@ -0,0 +1 @@ +export { DeleteWrapper } from './DeleteWrapper'; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/GenerateModelsButton.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/GenerateModelsButton/GenerateModelsButton.tsx similarity index 91% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/GenerateModelsButton.tsx rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/GenerateModelsButton/GenerateModelsButton.tsx index 31df3195820..d72df6ccdba 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/GenerateModelsButton.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/GenerateModelsButton/GenerateModelsButton.tsx @@ -2,8 +2,8 @@ import { Spinner } from '@digdir/designsystemet-react'; import { CogIcon } from '@studio/icons'; import React from 'react'; import { useTranslation } from 'react-i18next'; -import { useSchemaQuery } from '../../../../hooks/queries'; -import { useGenerateModelsMutation } from '../../../../hooks/mutations'; +import { useSchemaQuery } from '../../../../../hooks/queries'; +import { useGenerateModelsMutation } from '../../../../../hooks/mutations'; import { toast } from 'react-toastify'; import { StudioButton } from '@studio/components'; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/GenerateModelsButton/index.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/GenerateModelsButton/index.ts new file mode 100644 index 00000000000..d8dacb38f6b --- /dev/null +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/GenerateModelsButton/index.ts @@ -0,0 +1 @@ +export { GenerateModelsButton } from './GenerateModelsButton'; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.module.css b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect/SchemaSelect.module.css similarity index 100% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.module.css rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect/SchemaSelect.module.css diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect/SchemaSelect.test.tsx similarity index 92% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.test.tsx rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect/SchemaSelect.test.tsx index 8bfb78c56f7..47d76d684f9 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect/SchemaSelect.test.tsx @@ -5,9 +5,9 @@ import { SchemaSelect } from './SchemaSelect'; import { jsonMetadata1Mock, jsonMetadata2Mock, -} from '../../../../../packages/schema-editor/test/mocks/metadataMocks'; +} from '../../../../../../packages/schema-editor/test/mocks/metadataMocks'; import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; -import { convertMetadataToOption } from '../../../../utils/metadataUtils'; +import { convertMetadataToOption } from '../../../../../utils/metadataUtils'; import userEvent from '@testing-library/user-event'; const user = userEvent.setup(); diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect/SchemaSelect.tsx similarity index 93% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.tsx rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect/SchemaSelect.tsx index d1ff68acf42..93338b92b2f 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect/SchemaSelect.tsx @@ -3,8 +3,8 @@ import { convertMetadataListToOptions, findMetadataOptionByRelativeUrl, groupMetadataOptions, -} from '../../../../utils/metadataUtils'; -import type { MetadataOption } from '../../../../types/MetadataOption'; +} from '../../../../../utils/metadataUtils'; +import type { MetadataOption } from '../../../../../types/MetadataOption'; import { NativeSelect } from '@digdir/designsystemet-react'; import classes from './SchemaSelect.module.css'; import type { DataModelMetadata } from 'app-shared/types/DataModelMetadata'; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect/index.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect/index.ts new file mode 100644 index 00000000000..b773f162b8c --- /dev/null +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/SchemaSelect/index.ts @@ -0,0 +1 @@ +export { SchemaSelect } from './SchemaSelect'; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload.test.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/XSDUpload.test.tsx similarity index 95% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload.test.tsx rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/XSDUpload.test.tsx index f210f2e926b..af38ca89f6e 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload.test.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/XSDUpload.test.tsx @@ -6,7 +6,7 @@ import { textMock } from '@studio/testing/mocks/i18nMock'; import type { QueryClient } from '@tanstack/react-query'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import { app, org } from '@studio/testing/testids'; -import { renderWithProviders } from '../../../../test/mocks'; +import { renderWithProviders } from '../../../../../test/mocks'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; import { createApiErrorMock } from 'app-shared/mocks/apiErrorMock'; import { QueryKey } from 'app-shared/types/QueryKey'; @@ -14,13 +14,13 @@ import { queriesMock } from 'app-shared/mocks/queriesMock'; const user = userEvent.setup(); -jest.mock('../../../../hooks/mutations/useUploadDataModelMutation', () => ({ +jest.mock('../../../../../hooks/mutations/useUploadDataModelMutation', () => ({ __esModule: true, - ...jest.requireActual('../../../../hooks/mutations/useUploadDataModelMutation'), + ...jest.requireActual('../../../../../hooks/mutations/useUploadDataModelMutation'), })); const useUploadDataModelMutationSpy = jest.spyOn( - require('../../../../hooks/mutations/useUploadDataModelMutation'), + require('../../../../../hooks/mutations/useUploadDataModelMutation'), 'useUploadDataModelMutation', ); diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload.tsx b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/XSDUpload.tsx similarity index 90% rename from frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload.tsx rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/XSDUpload.tsx index 252631b5e6a..9c12c903513 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload.tsx +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/XSDUpload.tsx @@ -2,21 +2,21 @@ import React from 'react'; import type { StudioButtonProps } from '@studio/components'; import { StudioFileUploader, StudioSpinner } from '@studio/components'; import { useTranslation } from 'react-i18next'; -import { useUploadDataModelMutation } from '../../../../hooks/mutations/useUploadDataModelMutation'; +import { useUploadDataModelMutation } from '../../../../../hooks/mutations/useUploadDataModelMutation'; import type { AxiosError } from 'axios'; import type { ApiError } from 'app-shared/types/api/ApiError'; import { toast } from 'react-toastify'; -import type { MetadataOption } from '../../../../types/MetadataOption'; +import type { MetadataOption } from '../../../../../types/MetadataOption'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; import { useAppMetadataQuery } from 'app-shared/hooks/queries'; -import { useValidationAlert } from '../../hooks/useValidationAlert'; +import { useValidationAlert } from './useValidationAlert'; import { removeExtension } from 'app-shared/utils/filenameUtils'; import { doesFileExistInMetadataWithClassRef, doesFileExistInMetadataWithoutClassRef, findFileNameError, -} from '../../utils/validationUtils'; -import type { FileNameError } from '../../types/FileNameError'; +} from '../utils/validationUtils'; +import type { FileNameError } from '../types/FileNameError'; export interface XSDUploadProps { selectedOption?: MetadataOption; diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/index.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/index.ts new file mode 100644 index 00000000000..38579e97fd4 --- /dev/null +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/index.ts @@ -0,0 +1 @@ +export { XSDUpload } from './XSDUpload'; diff --git a/frontend/app-development/features/dataModelling/hooks/useValidationAlert.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/useValidationAlert.ts similarity index 100% rename from frontend/app-development/features/dataModelling/hooks/useValidationAlert.ts rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/XSDUpload/useValidationAlert.ts diff --git a/frontend/app-development/features/dataModelling/types/FileNameError.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/types/FileNameError.ts similarity index 100% rename from frontend/app-development/features/dataModelling/types/FileNameError.ts rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/types/FileNameError.ts diff --git a/frontend/app-development/features/dataModelling/utils/validationUtils.test.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/utils/validationUtils.test.ts similarity index 90% rename from frontend/app-development/features/dataModelling/utils/validationUtils.test.ts rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/utils/validationUtils.test.ts index 707f4d698ba..658a8330d1b 100644 --- a/frontend/app-development/features/dataModelling/utils/validationUtils.test.ts +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/utils/validationUtils.test.ts @@ -1,4 +1,4 @@ -import { mockAppMetadata, mockDataTypeId } from '../../../test/applicationMetadataMock'; +import { mockAppMetadata, mockDataTypeId } from '../../../../../test/applicationMetadataMock'; import { extractDataTypeNamesFromAppMetadata } from './validationUtils'; describe('extractDataTypeNamesFromAppMetadata', () => { diff --git a/frontend/app-development/features/dataModelling/utils/validationUtils.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/utils/validationUtils.ts similarity index 100% rename from frontend/app-development/features/dataModelling/utils/validationUtils.ts rename to frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/utils/validationUtils.ts From 559753c64aacfeb27f6db184af80b3e63c62a30f Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 22 Nov 2024 15:07:36 +0100 Subject: [PATCH 32/37] Move DATA_MODEL_NAME_MAX_LENGTH into the hook where it is used --- frontend/packages/shared/src/constants.js | 1 - .../packages/shared/src/hooks/useValidateSchemaName.test.ts | 3 +-- frontend/packages/shared/src/hooks/useValidateSchemaName.ts | 4 +++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/packages/shared/src/constants.js b/frontend/packages/shared/src/constants.js index 67b503f50be..c43fd18c64f 100644 --- a/frontend/packages/shared/src/constants.js +++ b/frontend/packages/shared/src/constants.js @@ -20,5 +20,4 @@ export const PROTECTED_TASK_NAME_CUSTOM_RECEIPT = 'CustomReceipt'; export const PREVIEW_MOCK_PARTY_ID = '51001'; export const PREVIEW_MOCK_INSTANCE_GUID = 'f1e23d45-6789-1bcd-8c34-56789abcdef0'; export const MEDIA_QUERY_MAX_WIDTH = '(max-width: 1024px)'; -export const DATA_MODEL_NAME_MAX_LENGTH = 100; export const DATA_MODEL_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_\-æÆøØåÅ]*$/; diff --git a/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts b/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts index 968b82a1111..16ceb0f6de6 100644 --- a/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts +++ b/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts @@ -1,7 +1,6 @@ -import { useValidateSchemaName } from './useValidateSchemaName'; +import { DATA_MODEL_NAME_MAX_LENGTH, useValidateSchemaName } from './useValidateSchemaName'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { act, renderHook } from '@testing-library/react'; -import { DATA_MODEL_NAME_MAX_LENGTH } from 'app-shared/constants'; // Test data const existingModelName = 'existingModelName'; diff --git a/frontend/packages/shared/src/hooks/useValidateSchemaName.ts b/frontend/packages/shared/src/hooks/useValidateSchemaName.ts index 4232eb9f09b..e95c031270b 100644 --- a/frontend/packages/shared/src/hooks/useValidateSchemaName.ts +++ b/frontend/packages/shared/src/hooks/useValidateSchemaName.ts @@ -1,6 +1,6 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { DATA_MODEL_NAME_MAX_LENGTH, DATA_MODEL_NAME_REGEX } from 'app-shared/constants'; +import { DATA_MODEL_NAME_REGEX } from 'app-shared/constants'; export const useValidateSchemaName = ( existingDataModelNames: string[], @@ -40,6 +40,8 @@ export const useValidateSchemaName = ( return { validateName, nameError, setNameError }; }; +export const DATA_MODEL_NAME_MAX_LENGTH = 100; + const isCSharpReservedKeyword = (word: string): boolean => { const cSharpKeywords = new Set([ 'abstract', From e3e6531b387756b2d5939bdc56048f7c66ded676 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Tue, 26 Nov 2024 12:10:16 +0100 Subject: [PATCH 33/37] Make extractDataTypeNamesFromAppMetadata more concise --- .../TopToolbar/utils/validationUtils.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/utils/validationUtils.ts b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/utils/validationUtils.ts index c7f99811cb3..d65112503a6 100644 --- a/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/utils/validationUtils.ts +++ b/frontend/app-development/features/dataModelling/SchemaEditorWithToolbar/TopToolbar/utils/validationUtils.ts @@ -51,9 +51,5 @@ const doesFileExistInMetadata = ( export const extractDataTypeNamesFromAppMetadata = ( appMetadata?: ApplicationMetadata, ): string[] => { - if (appMetadata?.dataTypes) { - return appMetadata.dataTypes.map((dataType) => dataType.id); - } else { - return []; - } + return appMetadata?.dataTypes?.map((dataType) => dataType.id) || []; }; From 32cb73905fded08f7d9610e2f40155268645b3e3 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 29 Nov 2024 12:06:07 +0100 Subject: [PATCH 34/37] Disallow Norwegian characters in name The backend does not support Norwegian characters --- frontend/language/src/nb.json | 2 +- frontend/packages/shared/src/constants.js | 2 +- .../src/hooks/useValidateSchemaName.test.ts | 35 ++++++------------- 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 79230da7da7..e587c9df9bb 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -889,7 +889,7 @@ "schema_editor.error_data_type_name_exists": "Modellen kan ikke ha samme navn som datatyper i løsningen.", "schema_editor.error_depth": "Du kan ikke plassere denne gruppen her, fordi skjemaet får for mange nivåer.", "schema_editor.error_invalid_child": "Du kan ikke plassere denne komponenttypen i denne gruppen.", - "schema_editor.error_invalid_datamodel_name": "Navnet er ugyldig. Det første tegnet må være en stor eller liten bokstav fra a til z. Deretter kan du bruke små eller store bokstaver fra a til å, tall, understrek og bindestrek.", + "schema_editor.error_invalid_datamodel_name": "Navnet er ugyldig. Det første tegnet må være en stor eller liten bokstav fra a til z. Deretter kan du bruke små eller store bokstaver, tall, understrek og bindestrek.", "schema_editor.error_model_name_exists": "Modellnavnet {{newModelName}} er allerede i bruk.", "schema_editor.error_reserved_keyword": "Systemet bruker allerede dette navnet. Velg et annet navn.", "schema_editor.error_upload_data_model_id_exists_override_option": "Det finnes allerede en datamodell med dette navnet. Vil du overskrive den?", diff --git a/frontend/packages/shared/src/constants.js b/frontend/packages/shared/src/constants.js index c43fd18c64f..5cb7f7018c3 100644 --- a/frontend/packages/shared/src/constants.js +++ b/frontend/packages/shared/src/constants.js @@ -20,4 +20,4 @@ export const PROTECTED_TASK_NAME_CUSTOM_RECEIPT = 'CustomReceipt'; export const PREVIEW_MOCK_PARTY_ID = '51001'; export const PREVIEW_MOCK_INSTANCE_GUID = 'f1e23d45-6789-1bcd-8c34-56789abcdef0'; export const MEDIA_QUERY_MAX_WIDTH = '(max-width: 1024px)'; -export const DATA_MODEL_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_\-æÆøØåÅ]*$/; +export const DATA_MODEL_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_\-]*$/; diff --git a/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts b/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts index 16ceb0f6de6..4ec42491b28 100644 --- a/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts +++ b/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts @@ -89,31 +89,6 @@ describe('useValidateSchemaName', () => { }); describe('regular expressions', () => { - it('should disallow Norwegian characters at start of name', () => { - const { result } = renderUseValidateSchemaName(); - const invalidFirstCharacters = ['æ', 'ø', 'å', 'Æ', 'Ø', 'Å']; - - invalidFirstCharacters.forEach((char) => { - act(() => { - result.current.validateName(char); - }); - - expect(result.current.nameError).toBe( - textMock('schema_editor.error_invalid_datamodel_name'), - ); - }); - }); - - it('should allow Norwegian characters in rest of name', () => { - const { result } = renderUseValidateSchemaName(); - - act(() => { - result.current.validateName('aÆØÅæøå'); - }); - - expect(result.current.nameError).toBe(''); - }); - it('should disallow numbers at start of name', () => { const { result } = renderUseValidateSchemaName(); const invalidFirstCharacters = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']; @@ -178,6 +153,16 @@ describe('useValidateSchemaName', () => { ); }); }); + + it('should disallow Norwegian characters in name', () => { + const { result } = renderUseValidateSchemaName(); + + act(() => { + result.current.validateName('aÆØÅæøå'); + }); + + expect(result.current.nameError).toBe(textMock('schema_editor.error_invalid_datamodel_name')); + }); }); }); From a48ebcf4705851836f804b9b3b1c48ce768dee94 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 29 Nov 2024 14:43:26 +0100 Subject: [PATCH 35/37] Fix PR comments --- frontend/language/src/nb.json | 2 +- .../packages/shared/src/hooks/useValidateSchemaName.test.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index bbf48bf663f..9c22613e77d 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -895,7 +895,7 @@ "schema_editor.error_data_type_name_exists": "Modellen kan ikke ha samme navn som datatyper i løsningen.", "schema_editor.error_depth": "Du kan ikke plassere denne gruppen her, fordi skjemaet får for mange nivåer.", "schema_editor.error_invalid_child": "Du kan ikke plassere denne komponenttypen i denne gruppen.", - "schema_editor.error_invalid_datamodel_name": "Navnet er ugyldig. Det første tegnet må være en stor eller liten bokstav fra a til z. Deretter kan du bruke små eller store bokstaver, tall, understrek og bindestrek.", + "schema_editor.error_invalid_datamodel_name": "Navnet er ugyldig. Det første tegnet må være en stor eller liten bokstav fra a til z. Deretter kan du bruke små eller store bokstaver fra a til z, tall, understrek og bindestrek.", "schema_editor.error_model_name_exists": "Modellnavnet {{newModelName}} er allerede i bruk.", "schema_editor.error_reserved_keyword": "Systemet bruker allerede dette navnet. Velg et annet navn.", "schema_editor.error_upload_data_model_id_exists_override_option": "Det finnes allerede en datamodell med dette navnet. Vil du overskrive den?", diff --git a/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts b/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts index 4ec42491b28..6218db80302 100644 --- a/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts +++ b/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts @@ -141,7 +141,7 @@ describe('useValidateSchemaName', () => { it('should disallow " " and "." in name', () => { const { result } = renderUseValidateSchemaName(); - const invalidCharacters = [' ', '.']; + const invalidCharacters = [' ', '.', 'a ', 'a.']; invalidCharacters.forEach((char) => { act(() => { @@ -154,8 +154,9 @@ describe('useValidateSchemaName', () => { }); }); - it('should disallow Norwegian characters in name', () => { + it('should disallow Norwegian special characters in name', () => { const { result } = renderUseValidateSchemaName(); + const invalidNames = ['æ', 'ø', 'å', 'Æ', 'Ø', 'Å', 'aæ', 'aø', 'aå', 'aÆ', 'aØ', 'aÅ']; act(() => { result.current.validateName('aÆØÅæøå'); From 2ab7e81d8fc4515b943aeb11393359e125774b7c Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 29 Nov 2024 14:49:55 +0100 Subject: [PATCH 36/37] Fix type error --- .../packages/shared/src/hooks/useValidateSchemaName.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts b/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts index 6218db80302..23ecfedfe40 100644 --- a/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts +++ b/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts @@ -158,8 +158,10 @@ describe('useValidateSchemaName', () => { const { result } = renderUseValidateSchemaName(); const invalidNames = ['æ', 'ø', 'å', 'Æ', 'Ø', 'Å', 'aæ', 'aø', 'aå', 'aÆ', 'aØ', 'aÅ']; - act(() => { - result.current.validateName('aÆØÅæøå'); + invalidNames.forEach((name) => { + act(() => { + result.current.validateName(name); + }); }); expect(result.current.nameError).toBe(textMock('schema_editor.error_invalid_datamodel_name')); From 0eff4c7c00ec4dd81979d1051dc2e25cf93dc554 Mon Sep 17 00:00:00 2001 From: Erling Hauan Date: Fri, 29 Nov 2024 15:42:59 +0100 Subject: [PATCH 37/37] Move expect inside for loop --- .../packages/shared/src/hooks/useValidateSchemaName.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts b/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts index 23ecfedfe40..a9f984bdd7a 100644 --- a/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts +++ b/frontend/packages/shared/src/hooks/useValidateSchemaName.test.ts @@ -162,9 +162,11 @@ describe('useValidateSchemaName', () => { act(() => { result.current.validateName(name); }); - }); - expect(result.current.nameError).toBe(textMock('schema_editor.error_invalid_datamodel_name')); + expect(result.current.nameError).toBe( + textMock('schema_editor.error_invalid_datamodel_name'), + ); + }); }); }); });