From 77121577c8ad5c881a6f86150ff868a68b85fada Mon Sep 17 00:00:00 2001 From: andreastanderen Date: Fri, 6 Sep 2024 13:43:32 +0200 Subject: [PATCH] Move layoutset stuff to accordion in config panel --- frontend/language/src/nb.json | 3 +- .../StudioIconTextfield.module.css | 2 + .../StudioTextfieldToggleView.module.css | 1 + .../ConfigContent/ConfigContent.module.css | 1 + .../ConfigContent/ConfigContent.test.tsx | 24 +++---- .../ConfigContent/ConfigContent.tsx | 25 ++++--- .../EditLayoutSetName.test.tsx | 68 +++++++++++++++++++ .../EditLayoutSetName/EditLayoutSetName.tsx | 8 ++- .../EditTaskId/EditTaskId.module.css | 4 -- .../ConfigContent/EditTaskId/EditTaskId.tsx | 2 - .../CustomReceipt/CustomReceipt.test.tsx | 24 ++++++- .../CustomReceipt/CustomReceipt.tsx | 2 +- .../ConfigSurface/ConfigSurface.module.css | 2 +- .../process-editor/src/hooks/useBpmnEditor.ts | 3 +- .../test/mocks/bpmnContextMock.ts | 2 +- .../hooks/useValidateLayoutSetName.test.ts | 24 +++++++ .../src/hooks/useValidateLayoutSetName.ts | 12 +++- .../shared/src/utils/layoutSetsUtils.test.ts | 13 +++- .../shared/src/utils/layoutSetsUtils.ts | 4 +- 19 files changed, 180 insertions(+), 44 deletions(-) create mode 100644 frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditLayoutSetName/EditLayoutSetName.test.tsx delete mode 100644 frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditTaskId/EditTaskId.module.css create mode 100644 frontend/packages/shared/src/hooks/useValidateLayoutSetName.test.ts diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 83fa8901a56..ef774bbecdf 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -635,6 +635,7 @@ "process_editor.configuration_panel_data_model_selection_description": "Velg en datamodell å knytte til prosessteget", "process_editor.configuration_panel_data_task": "Oppgave: Utfylling", "process_editor.configuration_panel_data_types_to_sign_required": "Du må velge minst én datatype", + "process_editor.configuration_panel_design_title": "Utforming", "process_editor.configuration_panel_element_not_supported_message": "Foreløpig kan du kun se detaljene på oppgaver, ikke redigere dem. Velg en oppgave for å se detaljer.", "process_editor.configuration_panel_element_not_supported_title": "Elementet du har valgt, støttes ikke", "process_editor.configuration_panel_end_event": "Slutthendelse", @@ -647,7 +648,7 @@ "process_editor.configuration_panel_header_help_text_title": "Informasjon om valgt oppgave", "process_editor.configuration_panel_id_label": "ID:", "process_editor.configuration_panel_layout_set_id_not_unique": "Navnet på sidegruppen må være unikt", - "process_editor.configuration_panel_layout_set_name": "Utforming: ", + "process_editor.configuration_panel_layout_set_name": "Utforming av {{taskId}}: ", "process_editor.configuration_panel_layout_set_name_label": "Navn på utforming", "process_editor.configuration_panel_missing_task": "Oppgave", "process_editor.configuration_panel_name_label": "Navn: ", diff --git a/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.module.css b/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.module.css index 523979a60a1..f53f3a5a215 100644 --- a/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.module.css +++ b/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.module.css @@ -1,6 +1,8 @@ .container { display: flex; gap: var(--fds-spacing-2); + padding: var(--fds-spacing-3); + box-sizing: border-box; } .prefixIcon { diff --git a/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioTextfieldToggleView/StudioTextfieldToggleView.module.css b/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioTextfieldToggleView/StudioTextfieldToggleView.module.css index ad56f6be622..b9c56ef52cd 100644 --- a/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioTextfieldToggleView/StudioTextfieldToggleView.module.css +++ b/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioTextfieldToggleView/StudioTextfieldToggleView.module.css @@ -3,6 +3,7 @@ justify-content: flex-start; align-items: center; border-radius: 0; + width: 100%; } .viewModeIconsContainer { diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/ConfigContent.module.css b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/ConfigContent.module.css index 01d8bd11757..f9befdc9d66 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/ConfigContent.module.css +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/ConfigContent.module.css @@ -1,3 +1,4 @@ +.accordion, .configContent { display: flex; flex-direction: column; diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/ConfigContent.test.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/ConfigContent.test.tsx index 57aed386ebe..9750b599a74 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/ConfigContent.test.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/ConfigContent.test.tsx @@ -166,6 +166,14 @@ describe('ConfigContent', () => { expect(editPolicyButton).toBeInTheDocument(); }); + it('should render the Design accordion when a task has a connected layoutset', () => { + renderConfigContent(); + const designAccordion = screen.getByRole('button', { + name: textMock('process_editor.configuration_panel_design_title'), + }); + expect(designAccordion).toBeInTheDocument(); + }); + describe('Unique signature', () => { const element = getMockBpmnElementForTask('signing'); element.businessObject.extensionElements.values[0].signatureConfig.uniqueFromSignaturesInDataTypes = @@ -215,26 +223,16 @@ describe('ConfigContent', () => { ).toBeInTheDocument(); }); - it('should show recommended action when selected task is in recommended action queue', async () => { + it('should show recommended action when task is data and is in recommended action queue', async () => { const shouldDisplayAction = jest.fn().mockReturnValue(true); (useStudioRecommendedNextActionContext as jest.Mock).mockReturnValue({ removeAction: jest.fn(), addAction: jest.fn(), shouldDisplayAction, }); - renderConfigContent( - {}, - { - bpmnDetails: { - ...mockBpmnDetails, - id: 'task_2', - taskType: 'signing', - element, - }, - }, - ); + renderConfigContent(); - expect(shouldDisplayAction).toHaveBeenCalledWith('task_2'); + expect(shouldDisplayAction).toHaveBeenCalledWith(mockBpmnDetails.id); expect( screen.getByRole('textbox', { diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/ConfigContent.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/ConfigContent.tsx index 9ee1a6c0ba1..7f9c51e7b89 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/ConfigContent.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/ConfigContent.tsx @@ -53,16 +53,6 @@ export const ConfigContent = (): React.ReactElement => { className={classes.displayTile} showPadlock={false} /> - {taskHasConnectedLayoutSet && ( - <> - - - - )} {isSigningTask && ( <> @@ -72,6 +62,21 @@ export const ConfigContent = (): React.ReactElement => { )} + {taskHasConnectedLayoutSet && ( + + + {t('process_editor.configuration_panel_design_title')} + + + + + + + )} {t('process_editor.configuration_panel_actions_title')} diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditLayoutSetName/EditLayoutSetName.test.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditLayoutSetName/EditLayoutSetName.test.tsx new file mode 100644 index 00000000000..f1fee2f8477 --- /dev/null +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditLayoutSetName/EditLayoutSetName.test.tsx @@ -0,0 +1,68 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { EditLayoutSetName } from './EditLayoutSetName'; +import { textMock } from '@studio/testing/mocks/i18nMock'; +import { BpmnContext } from '../../../../contexts/BpmnContext'; +import { + mockBpmnApiContextValue, + mockBpmnContextValue, +} from '../../../../../test/mocks/bpmnContextMock'; +import { BpmnApiContext, type BpmnApiContextProps } from '../../../../contexts/BpmnApiContext'; +import userEvent from '@testing-library/user-event'; + +const existingLayoutSetNameMock = 'existingLayoutSetName'; + +describe('EditLayoutSetName', () => { + it('should render the layoutSetName button', () => { + renderEditLayoutSetName(); + const editLayoutSetName = screen.getByRole('button', { + name: textMock('process_editor.configuration_panel_layout_set_name_label'), + }); + expect(editLayoutSetName).toBeInTheDocument(); + }); + it('should render the name of the layoutSetName textfield using the connected taskId', () => { + renderEditLayoutSetName(); + const layoutSetNameViewMode = screen.getByLabelText( + textMock('process_editor.configuration_panel_layout_set_name_label'), + ); + expect(layoutSetNameViewMode).toHaveTextContent( + textMock('process_editor.configuration_panel_layout_set_name', { + taskId: mockBpmnContextValue.bpmnDetails.id, + }) + existingLayoutSetNameMock, + ); + }); + it('should call mutateLayoutSet when changing name', async () => { + const user = userEvent.setup(); + const newLayoutSetName = 'newLayoutSetName'; + const mutateLayoutSetIdMock = jest.fn(); + renderEditLayoutSetName({ mutateLayoutSetId: mutateLayoutSetIdMock }); + const editLayoutSetName = screen.getByRole('button', { + name: textMock('process_editor.configuration_panel_layout_set_name_label'), + }); + await user.click(editLayoutSetName); + const inputNewLayoutSetName = screen.getByRole('textbox', { + name: textMock('process_editor.configuration_panel_layout_set_name_label'), + }); + await user.clear(inputNewLayoutSetName); + await user.type(inputNewLayoutSetName, newLayoutSetName); + await user.tab(); + expect(mutateLayoutSetIdMock).toHaveBeenCalledTimes(1); + expect(mutateLayoutSetIdMock).toHaveBeenCalledWith({ + layoutSetIdToUpdate: existingLayoutSetNameMock, + newLayoutSetId: newLayoutSetName, + }); + }); +}); + +const renderEditLayoutSetName = ( + bpmnApiContextValue: Partial = {}, + existingLayoutSetName = existingLayoutSetNameMock, +) => { + render( + + + + + , + ); +}; diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditLayoutSetName/EditLayoutSetName.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditLayoutSetName/EditLayoutSetName.tsx index eaf7f20db66..19b37455dbd 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditLayoutSetName/EditLayoutSetName.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditLayoutSetName/EditLayoutSetName.tsx @@ -5,6 +5,7 @@ import { KeyVerticalIcon } from '@studio/icons'; import { useBpmnApiContext } from '../../../../contexts/BpmnApiContext'; import { Paragraph } from '@digdir/designsystemet-react'; import { useValidateLayoutSetName } from 'app-shared/hooks/useValidateLayoutSetName'; +import { useBpmnContext } from '../../../../contexts/BpmnContext'; interface EditLayoutSetNameProps { existingLayoutSetName: string; @@ -13,6 +14,7 @@ export const EditLayoutSetName = ({ existingLayoutSetName, }: EditLayoutSetNameProps): React.ReactElement => { const { t } = useTranslation(); + const { bpmnDetails } = useBpmnContext(); const { layoutSets, mutateLayoutSetId } = useBpmnApiContext(); const { validateLayoutSetName } = useValidateLayoutSetName(); @@ -25,7 +27,7 @@ export const EditLayoutSetName = ({ return ( - validateLayoutSetName(newLayoutSetName, layoutSets) + validateLayoutSetName(newLayoutSetName, layoutSets, existingLayoutSetName) } inputProps={{ icon: , @@ -37,7 +39,9 @@ export const EditLayoutSetName = ({ viewProps={{ children: ( - {t('process_editor.configuration_panel_layout_set_name')} + + {t('process_editor.configuration_panel_layout_set_name', { taskId: bpmnDetails.id })} + {existingLayoutSetName} ), diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditTaskId/EditTaskId.module.css b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditTaskId/EditTaskId.module.css deleted file mode 100644 index 6aa9827a6af..00000000000 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditTaskId/EditTaskId.module.css +++ /dev/null @@ -1,4 +0,0 @@ -.textfield { - padding: var(--fds-spacing-3); - box-sizing: border-box; -} diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditTaskId/EditTaskId.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditTaskId/EditTaskId.tsx index 03d1b1c0a0d..58a07ce2942 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditTaskId/EditTaskId.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditTaskId/EditTaskId.tsx @@ -5,7 +5,6 @@ import { KeyVerticalIcon } from '@studio/icons'; import { useBpmnContext } from '../../../../contexts/BpmnContext'; import { useBpmnConfigPanelFormContext } from '../../../../contexts/BpmnConfigPanelContext'; import type Modeling from 'bpmn-js/lib/features/modeling/Modeling'; -import classes from './EditTaskId.module.css'; import type { MetadataForm } from 'app-shared/types/BpmnMetadataForm'; import { useValidateBpmnTaskId } from '../../../../hooks/useValidateBpmnId'; @@ -47,7 +46,6 @@ export const EditTaskId = (): React.ReactElement => { , label: t('process_editor.configuration_panel_change_task_id'), value: bpmnDetails.id, diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceipt/CustomReceipt.test.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceipt/CustomReceipt.test.tsx index e6675b75e8f..a451180dc8b 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceipt/CustomReceipt.test.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceipt/CustomReceipt.test.tsx @@ -47,7 +47,7 @@ const defaultBpmnContextProps: BpmnApiContextProps = { describe('CustomReceipt', () => { afterEach(() => jest.clearAllMocks()); - it('calls "mutateLayoutSetId" when the layoutset id is changed', async () => { + it('calls "mutateLayoutSetId" when the layoutSet id is changed', async () => { const user = userEvent.setup(); renderCustomReceipt(); @@ -72,6 +72,26 @@ describe('CustomReceipt', () => { }); }); + it('does not call "mutateLayoutSetId" when the layoutSet id is changed to the original id', async () => { + const user = userEvent.setup(); + renderCustomReceipt(); + + const toggleableTextfieldButton = screen.getByRole('button', { + name: textMock('process_editor.configuration_panel_custom_receipt_textfield_label'), + }); + + await user.click(toggleableTextfieldButton); + + const textfield = screen.getByLabelText( + textMock('process_editor.configuration_panel_custom_receipt_textfield_label'), + ); + await user.clear(textfield); + await user.type(textfield, existingCustomReceiptLayoutSetId); + await user.tab(); + + expect(mockBpmnApiContextValue.mutateLayoutSetId).not.toHaveBeenCalled(); + }); + it('calls "mutateDataTypes" when the data model id is changed', async () => { const user = userEvent.setup(); renderCustomReceipt(); @@ -133,7 +153,7 @@ describe('CustomReceipt', () => { expect(mockBpmnApiContextValue.mutateLayoutSetId).not.toHaveBeenCalled(); }); - it('calls "deleteLayoutSet" when clicking delete layoutset', async () => { + it('calls "deleteLayoutSet" when clicking delete layoutSet', async () => { const user = userEvent.setup(); jest.spyOn(window, 'confirm').mockImplementation(() => true); renderCustomReceipt(); diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceipt/CustomReceipt.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceipt/CustomReceipt.tsx index fb456c5e387..6064ce7155e 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceipt/CustomReceipt.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceipt/CustomReceipt.tsx @@ -47,7 +47,7 @@ export const CustomReceipt = (): React.ReactElement => {
- validateLayoutSetName(newLayoutSetName, layoutSets) + validateLayoutSetName(newLayoutSetName, layoutSets, existingCustomReceiptLayoutSetId) } inputProps={{ className: classes.textfield, diff --git a/frontend/packages/process-editor/src/components/ConfigSurface/ConfigSurface.module.css b/frontend/packages/process-editor/src/components/ConfigSurface/ConfigSurface.module.css index 4012624c787..df6d3c5f865 100644 --- a/frontend/packages/process-editor/src/components/ConfigSurface/ConfigSurface.module.css +++ b/frontend/packages/process-editor/src/components/ConfigSurface/ConfigSurface.module.css @@ -2,6 +2,6 @@ width: 100%; background-color: var(--fds-semantic-surface-neutral-default); border-left: 1px solid var(--fds-semantic-border-neutral-subtle); - overflow-y: scroll; + overflow-y: auto; min-width: 500px; } diff --git a/frontend/packages/process-editor/src/hooks/useBpmnEditor.ts b/frontend/packages/process-editor/src/hooks/useBpmnEditor.ts index 89e0b6b0e7f..0d8b2a84690 100644 --- a/frontend/packages/process-editor/src/hooks/useBpmnEditor.ts +++ b/frontend/packages/process-editor/src/hooks/useBpmnEditor.ts @@ -36,7 +36,8 @@ export const useBpmnEditor = (): UseBpmnViewerResult => { taskEvent, taskType: bpmnDetails.taskType, }); - addAction(bpmnDetails.id); + if (bpmnDetails.taskType === 'data' || bpmnDetails.taskType === 'payment') + addAction(bpmnDetails.id); }; const handleShapeRemove = (taskEvent: TaskEvent): void => { diff --git a/frontend/packages/process-editor/test/mocks/bpmnContextMock.ts b/frontend/packages/process-editor/test/mocks/bpmnContextMock.ts index 43e2eb54a52..a8842de21fb 100644 --- a/frontend/packages/process-editor/test/mocks/bpmnContextMock.ts +++ b/frontend/packages/process-editor/test/mocks/bpmnContextMock.ts @@ -22,7 +22,7 @@ export const mockLayoutSets: LayoutSets = { { id: 'testId', dataType: 'dataTypeId1', - tasks: ['testId'], + tasks: [mockBpmnDetails.id], }, { id: 'layoutSetId2', diff --git a/frontend/packages/shared/src/hooks/useValidateLayoutSetName.test.ts b/frontend/packages/shared/src/hooks/useValidateLayoutSetName.test.ts new file mode 100644 index 00000000000..8ffcff9fcd6 --- /dev/null +++ b/frontend/packages/shared/src/hooks/useValidateLayoutSetName.test.ts @@ -0,0 +1,24 @@ +import { textMock } from '@studio/testing/mocks/i18nMock'; +import { useValidateLayoutSetName } from 'app-shared/hooks/useValidateLayoutSetName'; + +describe('useValidateLayoutSetName', () => { + it('should return invalid layoutSetName errorText when name is invalid', () => { + const existingLayoutSetName = 'existingLayoutSetName'; + const { validateLayoutSetName } = useValidateLayoutSetName(); + const layoutSetNameValidation = validateLayoutSetName(existingLayoutSetName, { + sets: [{ id: existingLayoutSetName, tasks: [] }], + }); + expect(layoutSetNameValidation).toBe( + textMock('process_editor.configuration_panel_layout_set_id_not_unique'), + ); + }); + + it('should return empty string when name is valid', () => { + const uniqueLayoutSetName = 'uniqueLayoutSetName'; + const { validateLayoutSetName } = useValidateLayoutSetName(); + const layoutSetNameValidation = validateLayoutSetName(uniqueLayoutSetName, { + sets: [{ id: 'existingLayoutSetName', tasks: [] }], + }); + expect(layoutSetNameValidation).toBe(''); + }); +}); diff --git a/frontend/packages/shared/src/hooks/useValidateLayoutSetName.ts b/frontend/packages/shared/src/hooks/useValidateLayoutSetName.ts index e13018571cc..f734afe52e9 100644 --- a/frontend/packages/shared/src/hooks/useValidateLayoutSetName.ts +++ b/frontend/packages/shared/src/hooks/useValidateLayoutSetName.ts @@ -5,8 +5,16 @@ import type { LayoutSets } from 'app-shared/types/api/LayoutSetsResponse'; export const useValidateLayoutSetName = () => { const { t } = useTranslation(); - const validateLayoutSetName = (newLayoutSetId: string, layoutSets: LayoutSets): string => { - const validationResult = getLayoutSetIdValidationErrorKey(layoutSets, newLayoutSetId); + const validateLayoutSetName = ( + newLayoutSetId: string, + layoutSets: LayoutSets, + oldLayoutSetId?: string, + ): string => { + const validationResult = getLayoutSetIdValidationErrorKey( + newLayoutSetId, + layoutSets, + oldLayoutSetId, + ); return validationResult ? t(validationResult) : ''; }; diff --git a/frontend/packages/shared/src/utils/layoutSetsUtils.test.ts b/frontend/packages/shared/src/utils/layoutSetsUtils.test.ts index d03d2da160b..5ac5627ea46 100644 --- a/frontend/packages/shared/src/utils/layoutSetsUtils.test.ts +++ b/frontend/packages/shared/src/utils/layoutSetsUtils.test.ts @@ -62,14 +62,14 @@ describe('getLayoutSetNameForCustomReceipt', () => { describe('getLayoutSetIdValidationErrorKey', () => { it('should return error message when the user types just one character', () => { const newLayoutSetId = 'a'; - expect(getLayoutSetIdValidationErrorKey({ sets: [] }, newLayoutSetId)).toBe( + expect(getLayoutSetIdValidationErrorKey(newLayoutSetId, { sets: [] })).toBe( 'process_editor.configuration_panel_custom_receipt_layout_set_name_validation', ); }); it('should return error message when the user types whitespace', () => { const newLayoutSetId = ' '; - expect(getLayoutSetIdValidationErrorKey({ sets: [] }, newLayoutSetId)).toBe( + expect(getLayoutSetIdValidationErrorKey(newLayoutSetId, { sets: [] })).toBe( 'validation_errors.required', ); }); @@ -84,8 +84,15 @@ describe('getLayoutSetIdValidationErrorKey', () => { }, ], }; - expect(getLayoutSetIdValidationErrorKey(layoutSets, existingLayoutSetId)).toBe( + expect(getLayoutSetIdValidationErrorKey(existingLayoutSetId, layoutSets)).toBe( 'process_editor.configuration_panel_layout_set_id_not_unique', ); }); + + it('should return null when the user types the same name as the original name', () => { + const existingLayoutSetId = 'layoutSetId'; + expect( + getLayoutSetIdValidationErrorKey(existingLayoutSetId, { sets: [] }, existingLayoutSetId), + ).toBe(null); + }); }); diff --git a/frontend/packages/shared/src/utils/layoutSetsUtils.ts b/frontend/packages/shared/src/utils/layoutSetsUtils.ts index 63dac03b631..8c3e0058328 100644 --- a/frontend/packages/shared/src/utils/layoutSetsUtils.ts +++ b/frontend/packages/shared/src/utils/layoutSetsUtils.ts @@ -8,9 +8,11 @@ export const getLayoutSetNameForCustomReceipt = (layoutSets: LayoutSets): string }; export const getLayoutSetIdValidationErrorKey = ( - layoutSets: LayoutSets, newLayoutSetId: string, + layoutSets: LayoutSets, + oldLayoutSetId?: string, ): string => { + if (oldLayoutSetId === newLayoutSetId) return null; if (!newLayoutSetId || newLayoutSetId.trim() === '') return 'validation_errors.required'; if (!validateLayoutNameAndLayoutSetName(newLayoutSetId)) return 'ux_editor.pages_error_format'; if (newLayoutSetId.length === 1)