diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index ff4bda69562..55260d3b940 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -15,7 +15,6 @@ "app_content_library.code_lists.code_list_accordion_title": "Kodeliste: {{codeListTitle}}", "app_content_library.code_lists.code_list_edit_id_label": "Navn på kodeliste", "app_content_library.code_lists.code_list_edit_id_title": "Rediger navn på kodelisten {{codeListName}}", - "app_content_library.code_lists.code_list_view_id_title": "Navn på kodeliste: {{codeListName}}", "app_content_library.code_lists.code_lists_count_info_none": "Det finnes ingen kodelister i biblioteket.", "app_content_library.code_lists.code_lists_count_info_plural": "Det finnes {{codeListsCount}} kodelister i biblioteket.", "app_content_library.code_lists.code_lists_count_info_single": "Det finnes 1 kodeliste i biblioteket.", @@ -689,7 +688,7 @@ "process_editor.configuration_panel_actions_set_server_action_info": "Angir at handlingen knyttes til neste steg i prosessen.", "process_editor.configuration_panel_actions_set_server_action_label": "Knytt handlingen til neste steg.", "process_editor.configuration_panel_actions_title": "Handlinger", - "process_editor.configuration_panel_change_task_id": "Endre ID", + "process_editor.configuration_panel_change_task_id_label": "Oppgave-ID", "process_editor.configuration_panel_confirmation_task": "Oppgave: Bekreftelse", "process_editor.configuration_panel_custom_receipt_accordion_header": "Kvittering", "process_editor.configuration_panel_custom_receipt_cancel_button": "Avbryt", @@ -703,7 +702,6 @@ "process_editor.configuration_panel_custom_receipt_delete_receipt": "Er du sikker på at du vil slette kvitteringen din?", "process_editor.configuration_panel_custom_receipt_heading": "Opprett din egen kvittering", "process_editor.configuration_panel_custom_receipt_info": "Hvis du heller vil lage din egen kvittering, kan du opprette den her. Kvitteringen du lager selv vil overstyre standardkvitteringen.", - "process_editor.configuration_panel_custom_receipt_layout_set_name": "Navn på kvittering: ", "process_editor.configuration_panel_custom_receipt_layout_set_name_validation": "Navnet må ha minst 2 tegn", "process_editor.configuration_panel_custom_receipt_navigate_to_design_button": "Gå til Utforming", "process_editor.configuration_panel_custom_receipt_navigate_to_design_title": "Gå til Utforming for å utforme kvitteringen din", @@ -1553,7 +1551,6 @@ "ux_editor.file_upload_component.valid_file_endings": "Innstillinger for filopplastingskomponent", "ux_editor.formLayout.warning_duplicates": "Du har den samme ID-en på flere komponenter: ", "ux_editor.formLayout.warning_duplicates.cannot_publish": "Du kan ikke publisere appen eller konfigurere komponentene før du har rettet opp feilen.", - "ux_editor.id_identifier": "ID: {{item}}", "ux_editor.image_component.settings": "Innstillinger for bilde", "ux_editor.info": "Informasjon", "ux_editor.input_popover_label": "Gi nytt navn til siden", 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 3de73802696..62bea004556 100644 --- a/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.module.css +++ b/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.module.css @@ -1,15 +1,14 @@ .container { display: flex; gap: var(--fds-spacing-2); - padding: var(--fds-spacing-3); - box-sizing: border-box; + align-items: flex-start; + padding: var(--fds-spacing-2) var(--fds-spacing-2); } .prefixIcon { color: var(--fds-semantic-text-neutral-default); - margin-top: var(--fds-spacing-7); + margin-top: var(--fds-spacing-10); font-size: var(--fds-sizing-4); - align-content: center; } .textfield { diff --git a/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.stories.tsx b/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.stories.tsx index 2e82b53a1b8..c3a3c9f958d 100644 --- a/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.stories.tsx @@ -19,7 +19,7 @@ export default meta; export const Preview: Story = (args) => ; Preview.args = { - icon: , + Icon: PencilIcon, value: 2.3, error: 'Your custom error message!', }; diff --git a/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.test.tsx b/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.test.tsx index f5ac15a1ade..9041543696b 100644 --- a/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.test.tsx @@ -8,35 +8,22 @@ import { testCustomAttributes } from '../../test-utils/testCustomAttributes'; describe('StudioIconTextfield', () => { it('render the icon', async () => { - renderStudioIconTextfield({ - icon: , - }); - expect(screen.getByTitle('my key icon title')).toBeInTheDocument(); + renderStudioIconTextfield(); + expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument(); }); it('should render label', () => { - renderStudioIconTextfield({ - icon:
, - label: 'id', - }); - expect(screen.getByLabelText('id')).toBeInTheDocument(); + renderStudioIconTextfield(); + expect(screen.getByLabelText(label)).toBeInTheDocument(); }); it('should execute onChange callback when input value changes', async () => { const user = userEvent.setup(); - const onChangeMock = jest.fn(); - - renderStudioIconTextfield({ - icon:
, - label: 'Your ID', - onChange: onChangeMock, - }); - - const input = screen.getByLabelText('Your ID'); - + renderStudioIconTextfield(); + const input = screen.getByRole('textbox', { name: label }); const inputValue = 'my id is 123'; await user.type(input, inputValue); - expect(onChangeMock).toHaveBeenCalledTimes(inputValue.length); + expect(onChange).toHaveBeenCalledTimes(inputValue.length); }); it('should forward the rest of the props to the input', () => { @@ -44,6 +31,15 @@ describe('StudioIconTextfield', () => { testCustomAttributes(renderStudioIconTextfield, getTextbox); }); }); -const renderStudioIconTextfield = (props: StudioIconTextfieldProps) => { - return render(); + +const label = 'label'; +const onChange = jest.fn(); +const defaultProps: StudioIconTextfieldProps = { + Icon: KeyVerticalIcon, + label, + onChange, +}; + +const renderStudioIconTextfield = (props: Partial = {}) => { + return render(); }; diff --git a/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.tsx b/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.tsx index 8591ed8d0fd..034d0aef701 100644 --- a/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.tsx +++ b/frontend/libs/studio-components/src/components/StudioIconTextfield/StudioIconTextfield.tsx @@ -1,25 +1,22 @@ import React, { forwardRef } from 'react'; import { StudioTextfield, type StudioTextfieldProps } from '../StudioTextfield'; import cn from 'classnames'; - import classes from './StudioIconTextfield.module.css'; export type StudioIconTextfieldProps = { - icon: React.ReactNode; + Icon?: React.ComponentType>; } & StudioTextfieldProps; export const StudioIconTextfield = forwardRef( ( - { icon, className: givenClassName, ...rest }: StudioIconTextfieldProps, + { Icon, className: givenClassName, ...rest }: StudioIconTextfieldProps, ref, ): React.ReactElement => { const className = cn(givenClassName, classes.container); return (
-
- {icon} -
- + +
); }, diff --git a/frontend/libs/studio-components/src/components/StudioRecommendedNextAction/StudioRecommendedNextAction.stories.tsx b/frontend/libs/studio-components/src/components/StudioRecommendedNextAction/StudioRecommendedNextAction.stories.tsx index 84c2da73005..5560fa62401 100644 --- a/frontend/libs/studio-components/src/components/StudioRecommendedNextAction/StudioRecommendedNextAction.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioRecommendedNextAction/StudioRecommendedNextAction.stories.tsx @@ -55,7 +55,7 @@ export const ExampleUseCase: ExampleUseCase = (args): React.ReactElement => { setName(e.target.value)} - icon={} + Icon={KeyVerticalIcon} size='sm' label='Nytt navn' /> 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 d74c50041e5..2fbf0bbd7e6 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 @@ -11,6 +11,7 @@ grid-template-columns: auto 1fr; text-align: left; align-items: center; + justify-content: space-between; color: var(--fds-semantic-text-neutral-default); width: 100%; gap: var(--fds-spacing-1); @@ -33,18 +34,12 @@ flex-direction: column; } -.editIconWrapper { - flex: 1; - text-align: right; +.editIcon { display: none; } -.button:hover .editIconWrapper, -.button:focus .editIconWrapper { +.button:hover .editIcon, +.button:focus .editIcon { display: flex; align-items: center; } - -.editIcon { - margin-left: auto; -} diff --git a/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioTextfieldToggleView/StudioTextfieldToggleView.test.tsx b/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioTextfieldToggleView/StudioTextfieldToggleView.test.tsx index 46df7dc3c0c..600ea469b56 100644 --- a/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioTextfieldToggleView/StudioTextfieldToggleView.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioTextfieldToggleView/StudioTextfieldToggleView.test.tsx @@ -3,52 +3,47 @@ import { render, screen } from '@testing-library/react'; import { StudioTextfieldToggleView } from './StudioTextfieldToggleView'; import type { StudioTextfieldToggleViewProps } from './StudioTextfieldToggleView'; import userEvent from '@testing-library/user-event'; +import { KeyVerticalIcon } from '@studio/icons'; describe('StudioTextfieldToggleView', () => { it('should render button text', () => { - renderStudioTextfieldToggleView({ children: 'My awesome button' }); - expect(screen.getByRole('button', { name: 'My awesome button' })).toBeInTheDocument(); + renderStudioTextfieldToggleView(); + expect(screen.getByRole('button', { name: value })).toBeInTheDocument(); }); it('should execute the "onClick" method when button is clicked', async () => { const user = userEvent.setup(); - const onClickMock = jest.fn(); - - renderStudioTextfieldToggleView({ children: 'My awesome button text', onClick: onClickMock }); - - await user.click(screen.getByRole('button', { name: 'My awesome button text' })); - expect(onClickMock).toHaveBeenCalledTimes(1); - }); - - it('should render the KeyVerticalIcon', () => { - renderStudioTextfieldToggleView({ children: 'My awesome button text' }); - - // Uses testId to find the KeyVerticalIcon, since it's not available for screen reader. - expect(screen.getByTestId('keyIcon')).toBeInTheDocument(); + renderStudioTextfieldToggleView(); + await user.click(screen.getByRole('button', { name: value })); + expect(onClick).toHaveBeenCalledTimes(1); }); - it('should render the PencilIcon', () => { - renderStudioTextfieldToggleView({ children: 'My awesome button text' }); - - // Uses testId to find the EditIcon, since it's not available for screen reader. - expect(screen.getByTestId('editIcon')).toBeInTheDocument(); + it('should render the both given Icon and pencilIcon', () => { + renderStudioTextfieldToggleView(); + expect(screen.getAllByRole('img', { hidden: true })).toHaveLength(2); }); it('should forward the rest of the props to the button', () => { - renderStudioTextfieldToggleView({ children: 'My awesome button text', disabled: true }); - expect(screen.getByRole('button', { name: 'My awesome button text' })).toBeDisabled(); + renderStudioTextfieldToggleView({ disabled: true }); + expect(screen.getByRole('button', { name: value })).toBeDisabled(); }); it('should show label if defined', () => { - const studioTextfieldToggleViewLabel = 'studioTextfieldToggleViewLabel'; - renderStudioTextfieldToggleView({ - children: 'My awesome button text', - label: studioTextfieldToggleViewLabel, - }); - expect(screen.getByText(studioTextfieldToggleViewLabel)).toBeInTheDocument(); + renderStudioTextfieldToggleView(); + expect(screen.getByText(label)).toBeInTheDocument(); }); }); -const renderStudioTextfieldToggleView = (props: Partial) => { - return render(); +const value = 'value'; +const label = 'label'; +const onClick = jest.fn(); +const defaultProps: StudioTextfieldToggleViewProps = { + value, + label, + onClick, + Icon: KeyVerticalIcon, +}; + +const renderStudioTextfieldToggleView = (props: Partial = {}) => { + return render(); }; diff --git a/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioTextfieldToggleView/StudioTextfieldToggleView.tsx b/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioTextfieldToggleView/StudioTextfieldToggleView.tsx index adb59aebb7d..58825ad6a52 100644 --- a/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioTextfieldToggleView/StudioTextfieldToggleView.tsx +++ b/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioTextfieldToggleView/StudioTextfieldToggleView.tsx @@ -1,36 +1,37 @@ import React from 'react'; -import { PencilIcon, KeyVerticalIcon } from '@studio/icons'; +import { PencilIcon } from '@studio/icons'; import { StudioButton, type StudioButtonProps } from '@studio/components'; import classes from './StudioTextfieldToggleView.module.css'; import cn from 'classnames'; -export type StudioTextfieldToggleViewProps = StudioButtonProps & { +export type StudioTextfieldToggleViewProps = Omit & { + Icon?: React.ComponentType>; label?: string; }; export const StudioTextfieldToggleView = ({ onClick, - children, - title, label, className: givenClass, - icon = , + Icon, ...rest }: StudioTextfieldToggleViewProps) => { const className = cn(classes.button, givenClass); return ( - - - {icon} + + + - {label && {label}} - {children} + {label && ( + + {label} + + )} + {rest.value} - - - + ); }; diff --git a/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioToggleableTextfield.stories.tsx b/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioToggleableTextfield.stories.tsx index a196435ca38..6cfe57ac258 100644 --- a/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioToggleableTextfield.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioToggleableTextfield.stories.tsx @@ -1,7 +1,6 @@ import React from 'react'; import type { Meta, StoryFn } from '@storybook/react'; import { StudioToggleableTextfield } from './StudioToggleableTextfield'; -import { KeyVerticalIcon } from '@studio/icons'; type Story = StoryFn; @@ -16,19 +15,10 @@ export const Preview: Story = (args) => ( Preview.args = { onIsViewMode: () => {}, - viewProps: { - variant: 'tertiary', - size: 'small', - label: 'My awesome label', - children: 'My awesome value', - }, - inputProps: { - icon: , - label: 'My awesome label', - size: 'small', - placeholder: 'Placeholder', - error: '', - }, + label: 'My awesome label', + title: 'My awesome title', + value: 'My awesome value', + error: '', }; export default meta; diff --git a/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioToggleableTextfield.test.tsx b/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioToggleableTextfield.test.tsx index 5f72290b1a4..e93ac27431a 100644 --- a/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioToggleableTextfield.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioToggleableTextfield.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { StudioToggleableTextfield, type StudioToggleableTextfieldProps, @@ -7,129 +7,96 @@ import { import userEvent from '@testing-library/user-event'; +const value: string = 'value'; +const label: string = 'label'; +const customValidation = jest.fn(); +const onBlur = jest.fn(); +const onChange = jest.fn(); + describe('StudioToggleableTextfield', () => { + afterEach(jest.clearAllMocks); + it('Renders the view mode by default', () => { - renderStudioTextField({ - viewProps: { children: 'Edit binding' }, - }); - expect(screen.getByRole('button', { name: 'Edit binding' })).toBeInTheDocument(); + renderStudioTextField(); + expect(screen.getByRole('button', { name: value })).toBeInTheDocument(); }); it('should toggle to edit-mode when edit button is clicked', async () => { const user = userEvent.setup(); - renderStudioTextField({ - viewProps: { children: 'Edit name' }, - inputProps: { value: '', icon:
, label: 'Your name' }, - }); - await user.click(screen.getByRole('button', { name: 'Edit name' })); - expect(screen.getByLabelText('Your name')).toBeEnabled(); + renderStudioTextField(); + await user.click(screen.getByRole('button', { name: value })); + expect(screen.getByRole('textbox', { name: label })).toBeInTheDocument(); }); it('should run custom validation when value changes', async () => { - const customValidation = jest.fn(); const user = userEvent.setup(); - renderStudioTextField({ - viewProps: { children: 'Edit name' }, - inputProps: { value: '', label: 'Your name', icon:
}, - customValidation, - }); - await user.click(screen.getByRole('button', { name: 'Edit name' })); - + renderStudioTextField({ customValidation }); + await user.click(screen.getByRole('button', { name: value })); const typedInputValue = 'John'; - await user.type(screen.getByLabelText('Your name'), typedInputValue); - + await user.type(screen.getByRole('textbox', { name: label }), typedInputValue); expect(customValidation).toHaveBeenCalledTimes(typedInputValue.length); }); - it('should be toggle back to view mode on blur', async () => { + it('should toggle back to view mode on blur', async () => { const user = userEvent.setup(); - - renderStudioTextField({ - viewProps: { children: 'edit' }, - inputProps: { value: 'value', label: 'Your name', icon:
}, - }); - - await user.click(screen.getByRole('button', { name: 'edit' })); - expect(screen.getByLabelText('Your name')).toBeEnabled(); - expect(screen.queryByRole('button', { name: 'edit' })).not.toBeInTheDocument(); - - fireEvent.blur(screen.getByLabelText('Your name')); - await screen.findByRole('button', { name: 'edit' }); + renderStudioTextField(); + const viewButton = screen.getByRole('button', { name: value }); + await user.click(viewButton); + const editTextfield = screen.getByRole('textbox', { name: label }); + expect(editTextfield).toBeInTheDocument(); + await user.tab(); + expect(screen.getByRole('button', { name: value })).toBeInTheDocument(); }); it('should execute onBlur method when input is blurred', async () => { - const onBlurMock = jest.fn(); const user = userEvent.setup(); - renderStudioTextField({ - viewProps: { children: 'Edit name' }, - inputProps: { onBlur: onBlurMock, label: 'Your name', icon:
}, - }); - - await user.click(screen.getByRole('button', { name: 'Edit name' })); - fireEvent.blur(screen.getByLabelText('Your name')); - expect(onBlurMock).toHaveBeenCalledTimes(1); + renderStudioTextField(); + await user.click(screen.getByRole('button', { name: value })); + await user.tab(); + expect(onBlur).toHaveBeenCalledTimes(1); }); it('should not toggle view on blur when input field has error', async () => { const user = userEvent.setup(); - - renderStudioTextField({ - viewProps: { children: 'Edit your name' }, - inputProps: { label: 'Your name', icon:
, error: 'Your name is a required field' }, - }); - - await user.click(screen.getByRole('button', { name: 'Edit your name' })); - - const inputField = screen.getByLabelText('Your name'); - fireEvent.blur(inputField); - - expect(inputField).toHaveAttribute('aria-invalid', 'true'); - expect(screen.getByText('Your name is a required field')).toBeInTheDocument(); - expect(screen.queryByRole('button', { name: 'Edit your name' })).not.toBeInTheDocument(); + const error = 'Your name is a required field'; + renderStudioTextField({ error }); + await user.click(screen.getByRole('button', { name: value })); + await user.tab(); + expect(screen.getByRole('textbox', { name: label })).toHaveAttribute('aria-invalid', 'true'); + expect(screen.getByText(error)).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: value })).not.toBeInTheDocument(); }); it('should execute onChange method when input value changes', async () => { - const onChangeMock = jest.fn(); const user = userEvent.setup(); - - renderStudioTextField({ - viewProps: { children: 'edit' }, - inputProps: { onChange: onChangeMock, label: 'Your name', icon:
}, - }); - + renderStudioTextField(); const inputValue = 'John'; - await user.click(screen.getByRole('button', { name: 'edit' })); - await user.type(screen.getByLabelText('Your name'), inputValue); - - expect(onChangeMock).toHaveBeenCalledTimes(inputValue.length); + await user.click(screen.getByRole('button', { name: value })); + await user.type(screen.getByRole('textbox', { name: label }), inputValue); + expect(onChange).toHaveBeenCalledTimes(inputValue.length); }); it('should render error message if customValidation occured', async () => { const user = userEvent.setup(); - + const customError = 'Your name cannot include "test"'; renderStudioTextField({ - viewProps: { children: 'Edit name' }, - inputProps: { label: 'Your name', icon:
}, - customValidation: (value: string) => - value === 'test' ? 'Your name cannot be "test"' : undefined, + customValidation: (valueToValidate: string) => + valueToValidate.includes('test') ? customError : undefined, }); - - await user.click(screen.getByRole('button', { name: 'Edit name' })); - await user.type(screen.getByLabelText('Your name'), 'test'); - expect(screen.getByText('Your name cannot be "test"')); + await user.click(screen.getByRole('button', { name: value })); + await user.type(screen.getByRole('textbox', { name: label }), 'test'); + expect(screen.getByText(customError)); }); }); -const renderStudioTextField = (props: Partial) => { - const defaultProps: StudioToggleableTextfieldProps = { - inputProps: { - value: 'value', - icon:
, - }, - viewProps: { - children: 'edit', - }, - customValidation: jest.fn(), - }; +const defaultProps: StudioToggleableTextfieldProps = { + label, + value, + onBlur, + onChange, + customValidation, +}; + +const renderStudioTextField = (props: Partial = {}) => { return render(); }; diff --git a/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioToggleableTextfield.tsx b/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioToggleableTextfield.tsx index f5eb7472a15..9493c2f33fd 100644 --- a/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioToggleableTextfield.tsx +++ b/frontend/libs/studio-components/src/components/StudioToggleableTextfield/StudioToggleableTextfield.tsx @@ -1,33 +1,37 @@ import React, { forwardRef, useEffect, useState } from 'react'; -import { - StudioTextfieldToggleView, - type StudioTextfieldToggleViewProps, -} from './StudioTextfieldToggleView'; +import { StudioTextfieldToggleView } from './StudioTextfieldToggleView'; -import { StudioIconTextfield, type StudioIconTextfieldProps } from '../StudioIconTextfield'; +import { StudioIconTextfield } from '../StudioIconTextfield'; +import { KeyVerticalIcon } from '../../../../studio-icons'; export type StudioToggleableTextfieldProps = { customValidation?: (value: string) => string | undefined; - inputProps: StudioIconTextfieldProps; - viewProps: Omit; + error?: string; + Icon?: React.ComponentType>; + label: string; + onBlur?: (event: React.ChangeEvent) => void; + onChange?: (event: React.ChangeEvent) => void; onIsViewMode?: (isViewMode: boolean) => void; - setViewModeByDefault?: boolean; - autoFocus?: boolean; + title?: string; + value: string; }; export const StudioToggleableTextfield = forwardRef( ( { - inputProps, - viewProps, customValidation, + error, + Icon = KeyVerticalIcon, + label, + onBlur, + onChange, onIsViewMode, - setViewModeByDefault = true, - autoFocus = true, + title, + value, }: StudioToggleableTextfieldProps, ref, ) => { - const [isViewMode, setIsViewMode] = useState(setViewModeByDefault); + const [isViewMode, setIsViewMode] = useState(true); const [errorMessage, setErrorMessage] = useState(null); useEffect(() => { @@ -51,12 +55,12 @@ export const StudioToggleableTextfield = forwardRef): void => { // Should not close the view mode or blur if there is an error - if (errorMessage || inputProps.error) { + if (errorMessage || error) { return; } toggleViewMode(); - inputProps.onBlur?.(event); + onBlur?.(event); }; const handleOnChange = (event: React.ChangeEvent) => { @@ -64,19 +68,31 @@ export const StudioToggleableTextfield = forwardRef; + if (isViewMode) + return ( + + ); return ( ); }, diff --git a/frontend/libs/studio-components/src/components/StudioToggleableTextfieldSchema/StudioToggleableTextfieldSchema.stories.tsx b/frontend/libs/studio-components/src/components/StudioToggleableTextfieldSchema/StudioToggleableTextfieldSchema.stories.tsx index 5ef03fddad9..b10b419bc0e 100644 --- a/frontend/libs/studio-components/src/components/StudioToggleableTextfieldSchema/StudioToggleableTextfieldSchema.stories.tsx +++ b/frontend/libs/studio-components/src/components/StudioToggleableTextfieldSchema/StudioToggleableTextfieldSchema.stories.tsx @@ -1,7 +1,6 @@ import React from 'react'; import type { Meta, StoryFn } from '@storybook/react'; import { StudioToggleableTextfieldSchema } from './StudioToggleableTextfieldSchema'; -import { KeyVerticalIcon } from '@studio/icons'; type Story = StoryFn; @@ -15,18 +14,10 @@ export const Preview: Story = (args) => ( ); Preview.args = { - viewProps: { - variant: 'tertiary', - size: 'small', - children: 'My awesome value', - }, - inputProps: { - icon: , - label: 'My awesome label', - size: 'small', - placeholder: 'Placeholder', - error: '', - }, + label: 'My awesome label', + title: 'My awesome title', + value: 'My awesome value', + error: '', }; export default meta; diff --git a/frontend/libs/studio-components/src/components/StudioToggleableTextfieldSchema/StudioToggleableTextfieldSchema.test.tsx b/frontend/libs/studio-components/src/components/StudioToggleableTextfieldSchema/StudioToggleableTextfieldSchema.test.tsx index 05a217fcf11..399da1f1650 100644 --- a/frontend/libs/studio-components/src/components/StudioToggleableTextfieldSchema/StudioToggleableTextfieldSchema.test.tsx +++ b/frontend/libs/studio-components/src/components/StudioToggleableTextfieldSchema/StudioToggleableTextfieldSchema.test.tsx @@ -4,7 +4,7 @@ import { StudioToggleableTextfieldSchema, type StudioToggleableTextfieldSchemaProps, } from './StudioToggleableTextfieldSchema'; -import { fireEvent, render, screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; const defaultLayoutSchemaMock: JsonSchema = { @@ -27,112 +27,54 @@ const defaultLayoutSchemaMock: JsonSchema = { }, }, }; - +const value: string = 'value'; +const label: string = 'label'; const defaultProps: StudioToggleableTextfieldSchemaProps = { layoutSchema: defaultLayoutSchemaMock, relatedSchemas: [], - viewProps: { - value: '', - onChange: () => {}, - }, - inputProps: { - value: '', - onChange: () => {}, - icon:
, - }, + label, + value, + onChange: jest.fn(), propertyPath: 'definitions/component/properties/id', onError: jest.fn(), }; describe('StudioToggleableTextfieldSchema', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - it('should render as view mode as default and support rest props', () => { - renderStudioTextfieldSchema({ - viewProps: { - children: 'Edit id', - className: 'test-class', - }, - }); - const editButton = screen.getByRole('button', { name: 'Edit id' }); - expect(editButton).toBeInTheDocument(); - expect(editButton).toHaveClass('test-class'); - }); + beforeEach(jest.clearAllMocks); it('should toggle to edit mode when clicking edit', async () => { const user = userEvent.setup(); - - renderStudioTextfieldSchema({ - viewProps: { - children: 'Edit id', - }, - inputProps: { - ...defaultProps.inputProps, - label: 'Your id', - }, - }); - - await user.click(screen.getByRole('button', { name: 'Edit id' })); - expect(screen.getByLabelText('Your id')).toBeInTheDocument(); + renderStudioTextfieldSchema(); + await user.click(screen.getByRole('button', { name: value })); + expect(screen.getByRole('textbox', { name: label })).toBeInTheDocument(); }); it('should toggle to view mode on blur', async () => { const user = userEvent.setup(); - - renderStudioTextfieldSchema({ - viewProps: { - children: 'Edit id', - }, - inputProps: { - ...defaultProps.inputProps, - label: 'Your id', - }, - }); - - await user.click(screen.getByRole('button', { name: 'Edit id' })); - expect(screen.queryByRole('button', { name: 'Edit id' })).not.toBeInTheDocument(); - - fireEvent.blur(screen.getByLabelText('Your id')); - expect(screen.getByRole('button', { name: 'Edit id' })).toBeInTheDocument(); + renderStudioTextfieldSchema(); + await user.click(screen.getByRole('button', { name: value })); + expect(screen.queryByRole('button', { name: value })).not.toBeInTheDocument(); + await user.tab(); + expect(screen.getByRole('button', { name: value })).toBeInTheDocument(); }); it('should not toggle to view mode on blur if input is invalid', async () => { const user = userEvent.setup(); - + const error: string = 'error message'; renderStudioTextfieldSchema({ - viewProps: { - children: 'Edit id', - }, - inputProps: { - ...defaultProps.inputProps, - label: 'Your id', - error: 'my awesome error message', - }, + ...defaultProps, + error, }); - - await user.click(screen.getByRole('button', { name: 'Edit id' })); - expect(screen.queryByRole('button', { name: 'Edit id' })).not.toBeInTheDocument(); - - fireEvent.blur(screen.getByLabelText('Your id')); - expect(screen.queryByRole('button', { name: 'Edit id' })).not.toBeInTheDocument(); + await user.click(screen.getByRole('button', { name: value })); + await user.tab(); + expect(screen.queryByRole('button', { name: value })).not.toBeInTheDocument(); }); it('should validate field against json schema and invoke "onError" if validation has errors', async () => { const user = userEvent.setup(); - - renderStudioTextfieldSchema({ - viewProps: { - children: 'Edit id', - }, - inputProps: { - ...defaultProps.inputProps, - label: 'Your id', - }, - }); - await user.click(screen.getByRole('button', { name: 'Edit id' })); - - await user.type(screen.getByLabelText('Your id'), 'invalid-value-01'); + renderStudioTextfieldSchema(); + await user.click(screen.getByRole('button', { name: value })); + await user.type(screen.getByRole('textbox', { name: label }), 'invalid-value-01'); expect(defaultProps.onError).toHaveBeenCalledWith({ errorCode: 'pattern', details: 'Result of validate property', @@ -141,21 +83,9 @@ describe('StudioToggleableTextfieldSchema', () => { it('should validate field against json schema and invoke "onError" if field is required', async () => { const user = userEvent.setup(); - - renderStudioTextfieldSchema({ - viewProps: { - children: 'Edit id', - }, - inputProps: { - ...defaultProps.inputProps, - label: 'Your id', - }, - }); - await user.click(screen.getByRole('button', { name: 'Edit id' })); - - await user.type(screen.getByLabelText('Your id'), 'first-id'); - await user.clear(screen.getByLabelText('Your id')); - + renderStudioTextfieldSchema(); + await user.click(screen.getByRole('button', { name: value })); + await user.clear(screen.getByRole('textbox', { name: label })); expect(defaultProps.onError).toHaveBeenCalledWith({ errorCode: 'required', details: 'Property value is required', @@ -164,30 +94,15 @@ describe('StudioToggleableTextfieldSchema', () => { it('should invoke onChange and onError when input changes with error', async () => { const user = userEvent.setup(); - const onErrorMock = jest.fn(); - const onChangeMock = jest.fn(); - - renderStudioTextfieldSchema({ - onError: onErrorMock, - viewProps: { - children: 'Edit id', - }, - inputProps: { - ...defaultProps.inputProps, - label: 'Your id', - onChange: onChangeMock, - }, - }); - - await user.click(screen.getByRole('button', { name: 'Edit id' })); - - const invalidValue = '1'; - await user.type(screen.getByLabelText('Your id'), invalidValue); - expect(onErrorMock).toHaveBeenCalledWith({ - details: 'Result of validate property', + const invalidValue = 'invalid-value-01'; + renderStudioTextfieldSchema(); + await user.click(screen.getByRole('button', { name: value })); + await user.type(screen.getByRole('textbox', { name: label }), invalidValue); + expect(defaultProps.onError).toHaveBeenCalledWith({ errorCode: 'pattern', + details: 'Result of validate property', }); - expect(onChangeMock).toHaveBeenCalledTimes(1); + expect(defaultProps.onChange).toHaveBeenCalledTimes(invalidValue.length); }); }); diff --git a/frontend/libs/studio-components/src/components/StudioToggleableTextfieldSchema/StudioToggleableTextfieldSchema.tsx b/frontend/libs/studio-components/src/components/StudioToggleableTextfieldSchema/StudioToggleableTextfieldSchema.tsx index fe22cdae465..79b1ba5ac76 100644 --- a/frontend/libs/studio-components/src/components/StudioToggleableTextfieldSchema/StudioToggleableTextfieldSchema.tsx +++ b/frontend/libs/studio-components/src/components/StudioToggleableTextfieldSchema/StudioToggleableTextfieldSchema.tsx @@ -25,10 +25,11 @@ export const StudioToggleableTextfieldSchema = forwardRef< >( ( { + error, layoutSchema, relatedSchemas, - inputProps, propertyPath, + onChange, onError, onIsViewMode, ...rest @@ -48,8 +49,8 @@ export const StudioToggleableTextfieldSchema = forwardRef< } if (propertyId) { - const error = jsonSchemaValidator.validateProperty(propertyId, newValue); - return error ? createSchemaError(error, 'Result of validate property') : null; + const schemaError = jsonSchemaValidator.validateProperty(propertyId, newValue); + return schemaError ? createSchemaError(schemaError, 'Result of validate property') : null; } return null; @@ -59,18 +60,15 @@ export const StudioToggleableTextfieldSchema = forwardRef< const validationError = validateAgainstSchema(event); onError?.(validationError || null); - inputProps.onChange?.(event); + onChange?.(event); }; return ( ) => handleOnChange(event), - error: inputProps.error, - }} + onChange={(event: React.ChangeEvent) => handleOnChange(event)} + error={error} onIsViewMode={onIsViewMode} /> ); diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.test.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.test.tsx index e02e6d3ced9..df654659b57 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.test.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.test.tsx @@ -11,7 +11,7 @@ import { codeListsDataMock } from '../../../../../mocks/mockPagesConfig'; const onUpdateCodeListIdMock = jest.fn(); const onUpdateCodeListMock = jest.fn(); const onUploadCodeListMock = jest.fn(); -const codeListName = codeListsDataMock[0].title; +const codeListTitle = codeListsDataMock[0].title; const codeListMock: StudioComponentCodeList = [{ value: 'value', label: 'label' }]; const uploadedCodeListName = 'uploadedCodeListName'; @@ -51,18 +51,10 @@ describe('CodeListPage', () => { expect(codeListUploadButton).toBeInTheDocument(); }); - it('renders the code list as a clickable element', () => { - renderCodeListPage(); - const codeListAccordion = screen.getByRole('button', { name: codeListName }); - expect(codeListAccordion).toBeInTheDocument(); - }); - it('renders the code list accordion', () => { renderCodeListPage(); const codeListAccordion = screen.getByTitle( - textMock('app_content_library.code_lists.code_list_accordion_title', { - codeListTitle: codeListName, - }), + textMock('app_content_library.code_lists.code_list_accordion_title', { codeListTitle }), ); expect(codeListAccordion).toBeInTheDocument(); }); @@ -71,7 +63,7 @@ describe('CodeListPage', () => { const user = userEvent.setup(); const { rerender } = renderCodeListPage(); const codeListAccordionClosed = screen.getByRole('button', { - name: codeListName, + name: codeListTitle, expanded: false, }); expect(codeListAccordionClosed).toHaveAttribute('aria-expanded', 'false'); @@ -91,9 +83,9 @@ describe('CodeListPage', () => { it('calls onUpdateCodeListId when Id is changed', async () => { const user = userEvent.setup(); renderCodeListPage(); - await changeCodeListId(user, codeListName); + await changeCodeListId(user, codeListTitle); expect(onUpdateCodeListIdMock).toHaveBeenCalledTimes(1); - expect(onUpdateCodeListIdMock).toHaveBeenCalledWith(codeListName, codeListName + '2'); + expect(onUpdateCodeListIdMock).toHaveBeenCalledWith(codeListTitle, codeListTitle + '2'); }); it('calls onUpdateCodeList when code list is changed', async () => { @@ -104,7 +96,7 @@ describe('CodeListPage', () => { expect(onUpdateCodeListMock).toHaveBeenCalledTimes(1); expect(onUpdateCodeListMock).toHaveBeenLastCalledWith({ codeList: [{ ...codeListsDataMock[0].data[0], value: newValueText }], - title: codeListName, + title: codeListTitle, }); }); @@ -119,16 +111,14 @@ describe('CodeListPage', () => { const changeCodeListId = async (user: UserEvent, codeListNameToChange: string) => { const codeListIdToggleTextfield = screen.getByTitle( - textMock('app_content_library.code_lists.code_list_view_id_title', { - codeListName: codeListNameToChange, - }), - ); - await user.click(codeListIdToggleTextfield); - const codeListIdInput = screen.getByTitle( textMock('app_content_library.code_lists.code_list_edit_id_title', { codeListName: codeListNameToChange, }), ); + await user.click(codeListIdToggleTextfield); + const codeListIdInput = screen.getByRole('textbox', { + name: textMock('app_content_library.code_lists.code_list_edit_id_label'), + }); await user.type(codeListIdInput, '2'); await user.tab(); }; diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.test.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.test.tsx index 00136dc7cde..b4bda75c4dd 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.test.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.test.tsx @@ -104,16 +104,14 @@ describe('CodeLists', () => { const changeCodeListId = async (user: UserEvent, oldCodeListId: string, newCodeListId: string) => { const codeListIdToggleTextfield = screen.getByTitle( - textMock('app_content_library.code_lists.code_list_view_id_title', { - codeListName: oldCodeListId, - }), - ); - await user.click(codeListIdToggleTextfield); - const codeListIdInput = screen.getByTitle( textMock('app_content_library.code_lists.code_list_edit_id_title', { codeListName: oldCodeListId, }), ); + await user.click(codeListIdToggleTextfield); + const codeListIdInput = screen.getByRole('textbox', { + name: textMock('app_content_library.code_lists.code_list_edit_id_label'), + }); await user.clear(codeListIdInput); await user.type(codeListIdInput, newCodeListId); await user.tab(); diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.tsx index adee15ed216..b36a1c00951 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.tsx @@ -4,7 +4,6 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import type { CodeListWithMetadata } from '../../CodeListPage'; import { useCodeListEditorTexts } from '../../hooks/useCodeListEditorTexts'; -import { KeyVerticalIcon } from '@studio/icons'; import { updateCodeListWithMetadata } from '../CodeLists'; import { ArrayUtils, FileNameUtils } from '@studio/pure-functions'; import { useInputCodeListNameErrorMessage } from '../../hooks/useInputCodeListNameErrorMessage'; @@ -51,24 +50,12 @@ export function EditCodeList({
, - title: t('app_content_library.code_lists.code_list_edit_id_title', { - codeListName: codeListTitle, - }), - value: codeListTitle, - onBlur: (event) => handleUpdateCodeListId(event.target.value), - size: 'small', - }} - viewProps={{ - label: t('app_content_library.code_lists.code_list_edit_id_label'), - children: codeListTitle, - variant: 'tertiary', - title: t('app_content_library.code_lists.code_list_view_id_title', { - codeListName: codeListTitle, - }), - }} + label={t('app_content_library.code_lists.code_list_edit_id_label')} + onBlur={(event) => handleUpdateCodeListId(event.target.value)} + title={t('app_content_library.code_lists.code_list_edit_id_title', { + codeListName: codeListTitle, + })} + value={codeListTitle} />
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 9750b599a74..a9a17b552f3 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 @@ -91,7 +91,7 @@ describe('ConfigContent', () => { expect( screen.getByRole('button', { - name: textMock('process_editor.configuration_panel_change_task_id'), + name: mockBpmnDetails.id, }), ).toBeInTheDocument(); }); 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 index 222ac4bbfa4..4c97207eb28 100644 --- 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 @@ -12,28 +12,18 @@ describe('EditLayoutSetName', () => { it('should render the layoutSetName button', () => { renderEditLayoutSetName(); const editLayoutSetName = screen.getByRole('button', { - name: textMock('process_editor.configuration_panel_layout_set_name_label'), + name: existingLayoutSetNameMock, }); 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') + 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'), + name: existingLayoutSetNameMock, }); await user.click(editLayoutSetName); const inputNewLayoutSetName = screen.getByRole('textbox', { @@ -54,7 +44,7 @@ describe('EditLayoutSetName', () => { const mutateLayoutSetIdMock = jest.fn(); renderEditLayoutSetName({ mutateLayoutSetId: mutateLayoutSetIdMock }); const editLayoutSetName = screen.getByRole('button', { - name: textMock('process_editor.configuration_panel_layout_set_name_label'), + name: existingLayoutSetNameMock, }); await user.click(editLayoutSetName); const inputNewLayoutSetName = screen.getByRole('textbox', { 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 0731f0ee424..aaf52dcc02b 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 @@ -1,9 +1,7 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { StudioToggleableTextfield } from '@studio/components'; -import { KeyVerticalIcon } from '@studio/icons'; import { useBpmnApiContext } from '../../../../contexts/BpmnApiContext'; -import { Paragraph } from '@digdir/designsystemet-react'; import { useValidateLayoutSetName } from 'app-shared/hooks/useValidateLayoutSetName'; interface EditLayoutSetNameProps { @@ -27,23 +25,10 @@ export const EditLayoutSetName = ({ customValidation={(newLayoutSetName: string) => validateLayoutSetName(newLayoutSetName, layoutSets, existingLayoutSetName) } - inputProps={{ - icon: , - label: t('process_editor.configuration_panel_layout_set_name_label'), - value: existingLayoutSetName, - onBlur: (event) => handleOnLayoutSetNameBlur(event), - size: 'small', - }} - viewProps={{ - children: ( - - {t('process_editor.configuration_panel_layout_set_name')} - {existingLayoutSetName} - - ), - variant: 'tertiary', - 'aria-label': t('process_editor.configuration_panel_layout_set_name_label'), - }} + label={t('process_editor.configuration_panel_layout_set_name_label')} + onBlur={handleOnLayoutSetNameBlur} + title={t('process_editor.configuration_panel_layout_set_name_label')} + value={existingLayoutSetName} /> ); }; diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditLayoutSetNameRecommendedAction/RecommendedActionChangeName.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditLayoutSetNameRecommendedAction/RecommendedActionChangeName.tsx index aa2a37c720a..49397ca09b8 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditLayoutSetNameRecommendedAction/RecommendedActionChangeName.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditLayoutSetNameRecommendedAction/RecommendedActionChangeName.tsx @@ -45,8 +45,7 @@ export const RecommendedActionChangeName = (): React.ReactElement => { > } - size='sm' + Icon={KeyVerticalIcon} label={t('process_editor.recommended_action.new_name_label')} onChange={(event: React.ChangeEvent) => { setNewName(event.target.value); diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditTaskId/EditTaskId.test.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditTaskId/EditTaskId.test.tsx index fdab121c0d1..d95fb32ad34 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditTaskId/EditTaskId.test.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigContent/EditTaskId/EditTaskId.test.tsx @@ -46,7 +46,7 @@ describe('EditTaskId', () => { expect( screen.getByRole('button', { - name: textMock('process_editor.configuration_panel_change_task_id'), + name: mockBpmnDetails.id, }), ).toBeInTheDocument(); }); @@ -56,12 +56,14 @@ describe('EditTaskId', () => { render(); const editButton = screen.getByRole('button', { - name: textMock('process_editor.configuration_panel_change_task_id'), + name: mockBpmnDetails.id, }); await user.click(editButton); expect( - screen.getByLabelText(textMock('process_editor.configuration_panel_change_task_id')), + screen.getByRole('textbox', { + name: textMock('process_editor.configuration_panel_change_task_id_label'), + }), ).toBeInTheDocument(); }); @@ -76,13 +78,13 @@ describe('EditTaskId', () => { render(); const editButton = screen.getByRole('button', { - name: textMock('process_editor.configuration_panel_change_task_id'), + name: mockBpmnDetails.id, }); await user.click(editButton); - const input = screen.getByLabelText( - textMock('process_editor.configuration_panel_change_task_id'), - ); + const input = screen.getByRole('textbox', { + name: textMock('process_editor.configuration_panel_change_task_id_label'), + }); await user.clear(input); await user.type(input, newId); @@ -146,13 +148,13 @@ describe('EditTaskId', () => { render(); const editButton = screen.getByRole('button', { - name: textMock('process_editor.configuration_panel_change_task_id'), + name: mockBpmnDetails.id, }); await user.click(editButton); - const input = screen.getByLabelText( - textMock('process_editor.configuration_panel_change_task_id'), - ); + const input = screen.getByRole('textbox', { + name: textMock('process_editor.configuration_panel_change_task_id_label'), + }); await user.clear(input); if (inputValue !== '') await user.type(input, inputValue); @@ -174,13 +176,13 @@ describe('EditTaskId', () => { render(); const editButton = screen.getByRole('button', { - name: textMock('process_editor.configuration_panel_change_task_id'), + name: mockBpmnDetails.id, }); await user.click(editButton); - const input = screen.getByLabelText( - textMock('process_editor.configuration_panel_change_task_id'), - ); + const input = screen.getByRole('textbox', { + name: textMock('process_editor.configuration_panel_change_task_id_label'), + }); await user.clear(input); await user.type(input, mockBpmnDetails.id); 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 58a07ce2942..fcaedcc795e 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 @@ -1,7 +1,6 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { StudioToggleableTextfield } from '@studio/components'; -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'; @@ -45,22 +44,9 @@ export const EditTaskId = (): React.ReactElement => { return ( , - label: t('process_editor.configuration_panel_change_task_id'), - value: bpmnDetails.id, - onBlur: (event) => handleOnTaskIdBlur(event), - size: 'small', - }} - viewProps={{ - children: ( - - ID: {bpmnDetails.id} - - ), - variant: 'tertiary', - 'aria-label': t('process_editor.configuration_panel_change_task_id'), - }} + label={t('process_editor.configuration_panel_change_task_id_label')} + onBlur={handleOnTaskIdBlur} + value={bpmnDetails.id} /> ); }; diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceipt/CustomReceipt.module.css b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceipt/CustomReceipt.module.css index 8fcd79e026e..c4da8ec1012 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceipt/CustomReceipt.module.css +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigEndEvent/CustomReceiptContent/CustomReceipt/CustomReceipt.module.css @@ -21,8 +21,3 @@ gap: var(--fds-spacing-2); padding-inline: var(--custom-receipt-spacing); } - -.textfield { - padding-inline: var(--custom-receipt-spacing); - padding-block: var(--fds-spacing-1); -} 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 7dfe88f4107..05e8cec8fc5 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 @@ -40,7 +40,7 @@ const mockAllDataModelIds: string[] = [ const defaultBpmnContextProps: BpmnApiContextProps = { ...mockBpmnApiContextValue, - existingCustomReceiptLayoutSetId: existingCustomReceiptLayoutSetId, + existingCustomReceiptLayoutSetId, allDataModelIds: mockAllDataModelIds, }; @@ -52,7 +52,7 @@ describe('CustomReceipt', () => { renderCustomReceipt(); const toggleableTextfieldButton = screen.getByRole('button', { - name: textMock('process_editor.configuration_panel_custom_receipt_textfield_label'), + name: existingCustomReceiptLayoutSetId, }); await user.click(toggleableTextfieldButton); @@ -77,7 +77,7 @@ describe('CustomReceipt', () => { renderCustomReceipt(); const toggleableTextfieldButton = screen.getByRole('button', { - name: textMock('process_editor.configuration_panel_custom_receipt_textfield_label'), + name: existingCustomReceiptLayoutSetId, }); await user.click(toggleableTextfieldButton); @@ -130,7 +130,7 @@ describe('CustomReceipt', () => { }); const toggleableTextfieldButton = screen.getByRole('button', { - name: textMock('process_editor.configuration_panel_custom_receipt_textfield_label'), + name: existingCustomReceiptLayoutSetId, }); await user.click(toggleableTextfieldButton); 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 6064ce7155e..58b952aeae6 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 @@ -1,14 +1,12 @@ import React from 'react'; import classes from './CustomReceipt.module.css'; import { StudioDeleteButton, StudioToggleableTextfield } from '@studio/components'; -import { KeyVerticalIcon } from '@studio/icons'; import { useBpmnApiContext } from '../../../../../contexts/BpmnApiContext'; import { getDataTypeFromLayoutSetsWithExistingId } from '../../../../../utils/configPanelUtils'; import { RedirectToCreatePageButton } from '../RedirectToCreatePageButton'; import { useTranslation } from 'react-i18next'; import { EditDataTypes } from '../../../ConfigContent/EditDataTypes'; import { PROTECTED_TASK_NAME_CUSTOM_RECEIPT } from 'app-shared/constants'; -import { Paragraph } from '@digdir/designsystemet-react'; import { useValidateLayoutSetName } from 'app-shared/hooks/useValidateLayoutSetName'; export const CustomReceipt = (): React.ReactElement => { @@ -49,26 +47,9 @@ export const CustomReceipt = (): React.ReactElement => { customValidation={(newLayoutSetName: string) => validateLayoutSetName(newLayoutSetName, layoutSets, existingCustomReceiptLayoutSetId) } - inputProps={{ - className: classes.textfield, - icon: , - label: t('process_editor.configuration_panel_custom_receipt_textfield_label'), - value: existingCustomReceiptLayoutSetId, - onBlur: handleEditLayoutSetId, - size: 'small', - }} - viewProps={{ - children: ( - - - {t('process_editor.configuration_panel_custom_receipt_layout_set_name')} - - {existingCustomReceiptLayoutSetId} - - ), - variant: 'tertiary', - 'aria-label': t('process_editor.configuration_panel_custom_receipt_textfield_label'), - }} + label={t('process_editor.configuration_panel_custom_receipt_textfield_label')} + onBlur={handleEditLayoutSetId} + value={existingCustomReceiptLayoutSetId} /> { }); it('shows the custom receipt when there is an existing custom receipt layout set id', () => { + const existingCustomReceiptLayoutSetId = 'existingCustomReceiptLayoutSetId'; renderCustomReceiptContent({ - existingCustomReceiptLayoutSetId: 'testId', + existingCustomReceiptLayoutSetId, }); const toggleableTextfieldButton = screen.getByRole('button', { - name: textMock('process_editor.configuration_panel_custom_receipt_textfield_label'), + name: existingCustomReceiptLayoutSetId, }); expect(toggleableTextfieldButton).toBeInTheDocument(); }); diff --git a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigPanel.test.tsx b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigPanel.test.tsx index 3385729a270..e869177e602 100644 --- a/frontend/packages/process-editor/src/components/ConfigPanel/ConfigPanel.test.tsx +++ b/frontend/packages/process-editor/src/components/ConfigPanel/ConfigPanel.test.tsx @@ -43,7 +43,7 @@ describe('ConfigPanel', () => { bpmnDetails: { ...mockBpmnDetails, type: BpmnTypeEnum.Task }, }); const editTaskIdButton = screen.getByRole('button', { - name: textMock('process_editor.configuration_panel_change_task_id'), + name: mockBpmnDetails.id, }); expect(editTaskIdButton).toBeInTheDocument(); }); diff --git a/frontend/packages/ux-editor/src/components/Properties/PageConfigPanel/EditPageId.module.css b/frontend/packages/ux-editor/src/components/Properties/PageConfigPanel/EditPageId.module.css index eca00a24f2b..ae93910060b 100644 --- a/frontend/packages/ux-editor/src/components/Properties/PageConfigPanel/EditPageId.module.css +++ b/frontend/packages/ux-editor/src/components/Properties/PageConfigPanel/EditPageId.module.css @@ -1,7 +1,3 @@ .changePageId { background-color: var(--fds-semantic-surface-neutral-default); } - -.idInput { - padding: var(--fds-spacing-5); -} diff --git a/frontend/packages/ux-editor/src/components/Properties/PageConfigPanel/EditPageId.test.tsx b/frontend/packages/ux-editor/src/components/Properties/PageConfigPanel/EditPageId.test.tsx index 5438ed19694..f2fe20385bd 100644 --- a/frontend/packages/ux-editor/src/components/Properties/PageConfigPanel/EditPageId.test.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/PageConfigPanel/EditPageId.test.tsx @@ -23,7 +23,7 @@ const layoutSetName = layoutSet1NameMock; describe('EditPageId', () => { it('renders given page ID', () => { renderEditPageId(); - screen.getByRole('button', { name: textMock('ux_editor.id_identifier') }); + screen.getByRole('button', { name: selectedLayout }); }); it('calls updateFormLayoutName and textIdMutation with new page ID when changed', async () => { @@ -36,11 +36,11 @@ describe('EditPageId', () => { updateFormLayoutName, }; renderEditPageId(mockQueries); - const pageIdButton = screen.getByRole('button', { name: textMock('ux_editor.id_identifier') }); + const pageIdButton = screen.getByRole('button', { name: selectedLayout }); await user.click(pageIdButton); - const editPageId = screen.getByLabelText( - textMock('ux_editor.modal_properties_textResourceBindings_page_id'), - ); + const editPageId = screen.getByRole('textbox', { + name: textMock('ux_editor.modal_properties_textResourceBindings_page_id'), + }); await user.clear(editPageId); await user.type(editPageId, newPageName); await user.tab(); @@ -66,11 +66,11 @@ describe('EditPageId', () => { updateFormLayoutName, }; renderEditPageId(mockQueries); - const pageIdButton = screen.getByRole('button', { name: textMock('ux_editor.id_identifier') }); + const pageIdButton = screen.getByRole('button', { name: selectedLayout }); await user.click(pageIdButton); - const editPageId = screen.getByLabelText( - textMock('ux_editor.modal_properties_textResourceBindings_page_id'), - ); + const editPageId = screen.getByRole('textbox', { + name: textMock('ux_editor.modal_properties_textResourceBindings_page_id'), + }); await user.click(editPageId); await user.tab(); expect(updateFormLayoutName).not.toHaveBeenCalled(); @@ -83,7 +83,7 @@ describe('EditPageId', () => { renderEditPageId(); const notUniqueErrorMessage = screen.queryByText(textMock('ux_editor.pages_error_unique')); expect(notUniqueErrorMessage).not.toBeInTheDocument(); - const pageIdButton = screen.getByRole('button', { name: textMock('ux_editor.id_identifier') }); + const pageIdButton = screen.getByRole('button', { name: selectedLayout }); await user.click(pageIdButton); const editPageId = screen.getByRole('textbox', { name: textMock('ux_editor.modal_properties_textResourceBindings_page_id'), diff --git a/frontend/packages/ux-editor/src/components/Properties/PageConfigPanel/EditPageId.tsx b/frontend/packages/ux-editor/src/components/Properties/PageConfigPanel/EditPageId.tsx index 551a9f50f72..e900a4172b2 100644 --- a/frontend/packages/ux-editor/src/components/Properties/PageConfigPanel/EditPageId.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/PageConfigPanel/EditPageId.tsx @@ -1,6 +1,5 @@ import React from 'react'; import classes from './EditPageId.module.css'; -import { KeyVerticalIcon } from '@studio/icons'; import { getPageNameErrorKey } from '../../../utils/designViewUtils'; import { useUpdateLayoutNameMutation } from '../../../hooks/mutations/useUpdateLayoutNameMutation'; import { StudioToggleableTextfield } from '@studio/components'; @@ -8,7 +7,6 @@ import { useTextIdMutation } from 'app-development/hooks/mutations'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; import { useAppContext, useText } from '../../../hooks'; import { useFormLayoutSettingsQuery } from '../../../hooks/queries/useFormLayoutSettingsQuery'; -import { Trans } from 'react-i18next'; export interface EditPageIdProps { layoutName: string; @@ -47,23 +45,14 @@ export const EditPageId = ({ layoutName }: EditPageIdProps) => { return (
, - variant: 'tertiary', - fullWidth: true, - }} - inputProps={{ - icon: , - value: layoutName, - onBlur: (event) => handleSaveNewName(event.target.value), - label: t('ux_editor.modal_properties_textResourceBindings_page_id'), - size: 'small', - className: classes.idInput, - }} customValidation={(value: string) => { const validationResult = getPageNameErrorKey(value, layoutName, layoutOrder); return validationResult ? t(validationResult) : undefined; }} + label={t('ux_editor.modal_properties_textResourceBindings_page_id')} + onBlur={(event) => handleSaveNewName(event.target.value)} + title={t('ux_editor.modal_properties_textResourceBindings_page_id')} + value={layoutName} />
); diff --git a/frontend/packages/ux-editor/src/components/Properties/PageConfigPanel/PageConfigPanel.test.tsx b/frontend/packages/ux-editor/src/components/Properties/PageConfigPanel/PageConfigPanel.test.tsx index 8bbe3c3dc6d..23471edc68f 100644 --- a/frontend/packages/ux-editor/src/components/Properties/PageConfigPanel/PageConfigPanel.test.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/PageConfigPanel/PageConfigPanel.test.tsx @@ -79,7 +79,7 @@ describe('PageConfigPanel', () => { }); expect(screen.queryByRole('heading', { name: newSelectedPage })).not.toBeInTheDocument(); screen.getByRole('heading', { name: newVisualPageName }); - screen.getByRole('button', { name: textMock('ux_editor.id_identifier') }); + screen.getByRole('button', { name: newSelectedPage }); }); it('render warning when layout is selected and has duplicated ids', () => { diff --git a/frontend/packages/ux-editor/src/components/Properties/Properties.test.tsx b/frontend/packages/ux-editor/src/components/Properties/Properties.test.tsx index b86d7839142..10938dd9cc7 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Properties.test.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Properties.test.tsx @@ -51,9 +51,7 @@ jest.mock('../config/Expressions', () => ({ })); describe('Properties', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); + beforeEach(jest.clearAllMocks); describe('Page config', () => { it('shows page config when formItem is undefined', () => { @@ -83,7 +81,7 @@ describe('Properties', () => { }); expect(heading).toBeInTheDocument(); const editComponentIdButton = screen.getByRole('button', { - name: textMock('ux_editor.id_identifier'), + name: componentMocks[ComponentType.Input].id, }); expect(editComponentIdButton).toBeInTheDocument(); await user.click(editComponentIdButton); @@ -100,7 +98,9 @@ describe('Properties', () => { it('should not invoke handleUpdate when the id is invalid', async () => { const user = userEvent.setup(); renderProperties(); - await user.click(screen.getByRole('button', { name: textMock('ux_editor.id_identifier') })); + await user.click( + screen.getByRole('button', { name: componentMocks[ComponentType.Input].id }), + ); const invalidId = 'invalidId-01'; await user.type( diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditComponentIdRow/EditComponentIdRow.test.tsx b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditComponentIdRow/EditComponentIdRow.test.tsx index 7d4ce6659e5..371b3cd49a8 100644 --- a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditComponentIdRow/EditComponentIdRow.test.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditComponentIdRow/EditComponentIdRow.test.tsx @@ -11,43 +11,26 @@ import { QueryKey } from 'app-shared/types/QueryKey'; import { layout1NameMock, layoutMock } from '@altinn/ux-editor/testing/layoutMock'; import { layoutSet1NameMock } from '@altinn/ux-editor/testing/layoutSetsMock'; import { app, org } from '@studio/testing/testids'; +import { componentMocks } from '@altinn/ux-editor/testing/componentMocks'; const layoutSetName = layoutSet1NameMock; const layouts: IFormLayouts = { [layout1NameMock]: layoutMock, }; -const studioRender = async (props: Partial = {}) => { - queryClientMock.setQueryData([QueryKey.FormLayouts, org, app, layoutSetName], layouts); - return renderWithProviders( - , - ); -}; - describe('EditComponentIdRow', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); + beforeEach(jest.clearAllMocks); it('should render button ', async () => { - await studioRender(); - const testIdButton = screen.getByRole('button', { name: textMock('ux_editor.id_identifier') }); + await renderEditComponentIdRow(); + const testIdButton = screen.getByRole('button', { name: componentId }); expect(testIdButton).toBeInTheDocument(); }); it('should render textField when the button is clicked', async () => { const user = userEvent.setup(); - await studioRender(); - const testIdButton = screen.getByRole('button', { name: textMock('ux_editor.id_identifier') }); + await renderEditComponentIdRow(); + const testIdButton = screen.getByRole('button', { name: componentId }); await user.click(testIdButton); const textField = screen.getByRole('textbox', { name: textMock('ux_editor.modal_properties_component_change_id'), @@ -57,8 +40,8 @@ describe('EditComponentIdRow', () => { it('should not render the textfield when changing from edit mode to view mode ', async () => { const user = userEvent.setup(); - await studioRender(); - const testIdButton = screen.getByRole('button', { name: textMock('ux_editor.id_identifier') }); + await renderEditComponentIdRow(); + const testIdButton = screen.getByRole('button', { name: componentId }); await user.click(testIdButton); const textField = screen.getByRole('textbox', { name: textMock('ux_editor.modal_properties_component_change_id'), @@ -69,22 +52,27 @@ describe('EditComponentIdRow', () => { it('should call onChange when user change the input in text filed.', async () => { const user = userEvent.setup(); - const handleComponentUpdate = jest.fn(); - await studioRender({ handleComponentUpdate }); - const testIdButton = screen.getByRole('button', { name: textMock('ux_editor.id_identifier') }); + await renderEditComponentIdRow(); + const testIdButton = screen.getByRole('button', { name: componentId }); await user.click(testIdButton); const textField = screen.getByRole('textbox', { name: textMock('ux_editor.modal_properties_component_change_id'), }); - await user.type(textField, 'newTestId'); + const newTestId = 'newTestId'; + await user.clear(textField); + await user.type(textField, newTestId); await user.click(document.body); - expect(handleComponentUpdate).toHaveBeenCalled(); + expect(handleComponentUpdate).toHaveBeenCalledTimes(1); + expect(handleComponentUpdate).toHaveBeenCalledWith({ + ...componentMocks[ComponentType.Input], + id: newTestId, + }); }); it('should show error required error message when id is empty', async () => { const user = userEvent.setup(); - await studioRender(); - const testIdButton = screen.getByRole('button', { name: textMock('ux_editor.id_identifier') }); + await renderEditComponentIdRow(); + const testIdButton = screen.getByRole('button', { name: componentId }); await user.click(testIdButton); const textField = screen.getByRole('textbox', { name: textMock('ux_editor.modal_properties_component_change_id'), @@ -95,8 +83,8 @@ describe('EditComponentIdRow', () => { it('should show error message when id is not unique', async () => { const user = userEvent.setup(); - await studioRender(); - const testIdButton = screen.getByRole('button', { name: textMock('ux_editor.id_identifier') }); + await renderEditComponentIdRow(); + const testIdButton = screen.getByRole('button', { name: componentId }); await user.click(testIdButton); const textField = screen.getByRole('textbox', { name: textMock('ux_editor.modal_properties_component_change_id'), @@ -110,32 +98,39 @@ describe('EditComponentIdRow', () => { it('should show error message when id of an attachment component type has duplicate id', async () => { const user = userEvent.setup(); + const idOccupiedByDataType = 'idOccupiedByDataType'; queryClientMock.setQueryData([QueryKey.AppMetadata, org, app], { - dataTypes: [{ id: 'newTestId' }], + dataTypes: [{ id: idOccupiedByDataType }], }); - await studioRender({ - component: { - type: ComponentType.FileUpload, - id: '', - itemType: 'COMPONENT', - description: 'test', - displayMode: 'test', - hasCustomFileEndings: false, - maxFileSizeInMB: 100, - maxNumberOfAttachments: 2, - minNumberOfAttachments: 1, - }, + await renderEditComponentIdRow({ + component: componentMocks[ComponentType.FileUpload], + }); + const testIdButton = screen.getByRole('button', { + name: componentMocks[ComponentType.FileUpload].id, }); - const testIdButton = screen.getByRole('button', { name: textMock('ux_editor.id_identifier') }); await user.click(testIdButton); const textField = screen.getByRole('textbox', { name: textMock('ux_editor.modal_properties_component_change_id'), }); await user.clear(textField); - await user.type(textField, 'newTestId'); + await user.type(textField, idOccupiedByDataType); await user.click(document.body); expect( screen.getByText(textMock('ux_editor.error_component_id_exists_as_data_type')), ).toBeInTheDocument(); }); }); + +const componentId = componentMocks[ComponentType.Input].id; +const handleComponentUpdate = jest.fn(); +const helpText = 'helpText'; +const defaultProps: EditComponentIdRowProps = { + component: componentMocks[ComponentType.Input], + handleComponentUpdate, + helpText, +}; + +const renderEditComponentIdRow = async (props: Partial = {}) => { + queryClientMock.setQueryData([QueryKey.FormLayouts, org, app, layoutSetName], layouts); + return renderWithProviders(); +}; diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditComponentIdRow/EditComponentIdRow.tsx b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditComponentIdRow/EditComponentIdRow.tsx index 110efc4fe10..286e509a554 100644 --- a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditComponentIdRow/EditComponentIdRow.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/EditComponentIdRow/EditComponentIdRow.tsx @@ -1,10 +1,9 @@ import React, { useState } from 'react'; import { StudioToggleableTextfieldSchema, type SchemaValidationError } from '@studio/components'; import { Alert } from '@digdir/designsystemet-react'; -import { KeyVerticalIcon } from '@studio/icons'; import classes from './EditComponentIdRow.module.css'; import { idExists } from '../../../../utils/formLayoutsUtils'; -import { Trans, useTranslation } from 'react-i18next'; +import { useTranslation } from 'react-i18next'; import type { FormItem } from '../../../../types/FormItem'; import { useLayoutSchemaQuery } from '../../../../hooks/queries/useLayoutSchemaQuery'; import { useFormLayouts } from '../../../../hooks'; @@ -85,29 +84,20 @@ export const EditComponentIdRow = ({ return (
, - title: component.id, - variant: 'tertiary', - fullWidth: true, - }} - inputProps={{ - icon: , - value: idInputValue, - onBlur: (event) => saveComponentUpdate(event.target.value), - label: t('ux_editor.modal_properties_component_change_id'), - size: 'small', - error: errorMessage, - }} customValidation={(value) => { return validateId(value); }} + error={errorMessage} + key={component.id} + label={t('ux_editor.modal_properties_component_change_id')} + layoutSchema={layoutSchema} + onBlur={(event) => saveComponentUpdate(event.target.value)} + onError={handleValidationError} onIsViewMode={setIsViewMode} + propertyPath='definitions/component/properties/id' + relatedSchemas={[expressionSchema, numberFormatSchema]} + title={component.id} + value={component.id} /> {!isViewMode && (
diff --git a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/PropertiesHeader.test.tsx b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/PropertiesHeader.test.tsx index de05ced479d..74674655c7a 100644 --- a/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/PropertiesHeader.test.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/PropertiesHeader/PropertiesHeader.test.tsx @@ -65,7 +65,7 @@ describe('PropertiesHeader', () => { renderPropertiesHeader(); const editComponentIdButton = screen.getByRole('button', { - name: textMock('ux_editor.id_identifier'), + name: component1Mock.id, }); await user.click(editComponentIdButton); @@ -83,7 +83,7 @@ describe('PropertiesHeader', () => { renderPropertiesHeader(); const editComponentIdButton = screen.getByRole('button', { - name: textMock('ux_editor.id_identifier'), + name: component1Mock.id, }); await user.click(editComponentIdButton); diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditImage/EditImage.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditImage/EditImage.test.tsx index c628bcb4fb3..eba48c3105f 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditImage/EditImage.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditImage/EditImage.test.tsx @@ -6,6 +6,7 @@ import { ComponentType } from 'app-shared/types/ComponentType'; import { componentMocks } from '../../../../testing/componentMocks'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { renderWithProviders } from '../../../../testing/mocks'; +import type { UserEvent } from '@testing-library/user-event'; import userEvent from '@testing-library/user-event'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; import { queriesMock } from 'app-shared/mocks/queriesMock'; @@ -186,7 +187,7 @@ const getTabs = (): { addImageTab: HTMLElement; pasteUrlTab: HTMLElement } => { }; }; -const goToExternalUrlTab = async (user) => { +const goToExternalUrlTab = async (user: UserEvent) => { await user.click( screen.getByRole('tab', { name: textMock('ux_editor.properties_panel.images.enter_external_url_tab_title'), @@ -194,15 +195,14 @@ const goToExternalUrlTab = async (user) => { ); }; -const clickExistingUrlButton = async (user, existingExternalUrl: string) => { +const clickExistingUrlButton = async (user: UserEvent, existingExternalUrl: string) => { const existingUrlButton = screen.getByRole('button', { - name: - textMock('ux_editor.properties_panel.images.enter_external_url') + ' ' + existingExternalUrl, + name: existingExternalUrl, }); await user.click(existingUrlButton); }; -const enterUrlInField = async (user, url: string | undefined) => { +const enterUrlInField = async (user: UserEvent, url: string | undefined) => { const enterUrlField = screen.getByRole('textbox', { name: textMock('ux_editor.properties_panel.images.enter_external_url'), }); diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditImage/ExternalImage/ExternalImage.module.css b/frontend/packages/ux-editor/src/components/config/editModal/EditImage/ExternalImage/ExternalImage.module.css index 5fa067d03b6..4a0b85b0d44 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditImage/ExternalImage/ExternalImage.module.css +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditImage/ExternalImage/ExternalImage.module.css @@ -1,7 +1,3 @@ -.missingUrl { - font-style: italic; -} - .alertContainer { padding: 0 var(--fds-spacing-3); } diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditImage/ExternalImage/ExternalImage.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditImage/ExternalImage/ExternalImage.test.tsx index 212282cf99a..c6ee29f68cc 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditImage/ExternalImage/ExternalImage.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditImage/ExternalImage/ExternalImage.test.tsx @@ -9,6 +9,7 @@ import { QueryKey } from 'app-shared/types/QueryKey'; import { app, org } from '@studio/testing/testids'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; import { queriesMock } from 'app-shared/mocks/queriesMock'; +import type { UserEvent } from '@testing-library/user-event'; import userEvent from '@testing-library/user-event'; import type { ExternalImageUrlValidationResponse } from 'app-shared/types/api/ExternalImageUrlValidationResponse'; @@ -223,23 +224,17 @@ const getInvalidUrlErrorMessage = () => const getNotAnImageErrorMessage = () => screen.getByText(textMock('ux_editor.properties_panel.images.invalid_external_url_not_an_image')); -const getExistingUrlButton = (url: string) => - screen.getByRole('button', { - name: textMock('ux_editor.properties_panel.images.enter_external_url') + ' ' + url, - }); +const getExistingUrlButton = (url: string) => screen.getByRole('button', { name: url }); const getEnterUrlWithPlaceholderButton = () => screen.getByRole('button', { - name: - textMock('ux_editor.properties_panel.images.enter_external_url') + - ' ' + - textMock('ux_editor.properties_panel.images.external_url_not_added'), + name: textMock('ux_editor.properties_panel.images.external_url_not_added'), }); -const inputUrlInField = async (user, url: string) => { +const inputUrlInField = async (user: UserEvent, url: string) => { const inputUrlField = getInputUrlField(); + await user.clear(inputUrlField); if (url) await user.type(inputUrlField, url); - else await user.clear(inputUrlField); await waitFor(() => inputUrlField.blur()); }; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditImage/ExternalImage/ExternalImage.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditImage/ExternalImage/ExternalImage.tsx index 1e413c8f286..bab2fd9b87b 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditImage/ExternalImage/ExternalImage.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditImage/ExternalImage/ExternalImage.tsx @@ -1,7 +1,6 @@ -import type { ChangeEvent } from 'react'; import React, { useEffect, useState } from 'react'; import { LinkIcon } from '@studio/icons'; -import { StudioToggleableTextfield } from '@studio/components'; +import { StudioIconTextfield, StudioToggleableTextfield } from '@studio/components'; import { useTranslation } from 'react-i18next'; import classes from './ExternalImage.module.css'; import { useValidateImageExternalUrlQuery } from 'app-shared/hooks/queries/useValidateImageExternalUrlQuery'; @@ -22,7 +21,6 @@ export const ExternalImage = ({ onUrlDelete, imageOriginsFromLibrary, }: ExternalImageProps) => { - const { t } = useTranslation(); const { org, app } = useStudioEnvironmentParams(); const [url, setUrl] = useState(existingImageUrl); const { data: validationResult, status: validationStatus } = useValidateImageExternalUrlQuery( @@ -38,39 +36,18 @@ export const ExternalImage = ({ }, [validationResult, validationStatus, onUrlChange, url, existingImageUrl]); const handleBlur = async (newUrl: string) => { - if (isBLurInitialWithEmptyInput(url, newUrl)) return; - if (newUrl === '') { + if (newUrl === '' && !isBLurInitialWithEmptyInput(url, newUrl)) { onUrlDelete(); - setUrl(undefined); - return; } setUrl(newUrl); }; return ( <> - - {t('ux_editor.properties_panel.images.external_url_not_added')} - - ), - label: t('ux_editor.properties_panel.images.enter_external_url'), - title: url, - variant: 'tertiary', - fullWidth: true, - icon: , - }} - inputProps={{ - icon: , - value: existingImageUrl, - onBlur: ({ target }: ChangeEvent) => handleBlur(target.value), - label: t('ux_editor.properties_panel.images.enter_external_url'), - size: 'small', - }} - setViewModeByDefault={!!existingImageUrl} - autoFocus={false} + handleBlur(event.target.value)} /> {!!url && ( ) => void; +}; + +const EditUrl = ({ url, existingImageUrl, onBlur }: EditUrlProps): React.ReactElement => { + const { t } = useTranslation(); + const [isViewMode, setIsViewMode] = useState(false); + + const noUrlProvided = url === undefined && !existingImageUrl; + const label = t('ux_editor.properties_panel.images.enter_external_url'); + const noUrlText = t('ux_editor.properties_panel.images.external_url_not_added'); + const currentUrl = !url ? noUrlText : url; + const showValue = currentUrl !== noUrlText || isViewMode; + const value = showValue ? currentUrl : undefined; + + return noUrlProvided ? ( + + ) : ( + + ); +}; + const isBLurInitialWithEmptyInput = (existingUrl: string, newUrl: string) => newUrl === '' && existingUrl === undefined; diff --git a/frontend/packages/ux-editor/src/containers/DesignView/AddItem/ItemInfo/ItemInfo.tsx b/frontend/packages/ux-editor/src/containers/DesignView/AddItem/ItemInfo/ItemInfo.tsx index f8a333fc05f..42f8372ae90 100644 --- a/frontend/packages/ux-editor/src/containers/DesignView/AddItem/ItemInfo/ItemInfo.tsx +++ b/frontend/packages/ux-editor/src/containers/DesignView/AddItem/ItemInfo/ItemInfo.tsx @@ -55,7 +55,7 @@ export const ItemInfo = ({ item, onAddItem, onCancel, setItem }: ItemInfoProps) description={t('ux_editor.add_item.component_info_generated_id_description')} > } + Icon={PencilIcon} label={t('Komponent ID')} value={item.componentId} onChange={(event: any) => { diff --git a/frontend/testing/playwright/pages/ProcessEditorPage/ProcessEditorPage.ts b/frontend/testing/playwright/pages/ProcessEditorPage/ProcessEditorPage.ts index 10caad4cbf7..f2a4318c9a2 100644 --- a/frontend/testing/playwright/pages/ProcessEditorPage/ProcessEditorPage.ts +++ b/frontend/testing/playwright/pages/ProcessEditorPage/ProcessEditorPage.ts @@ -86,7 +86,7 @@ export class ProcessEditorPage extends BasePage { public async waitForEditIdInputFieldToBeVisible(): Promise { const inputField = this.page.getByRole('textbox', { - name: this.textMock('process_editor.configuration_panel_change_task_id'), + name: this.textMock('process_editor.configuration_panel_change_task_id_label'), }); await expect(inputField).toBeVisible(); } @@ -94,7 +94,7 @@ export class ProcessEditorPage extends BasePage { public async emptyIdTextfield(): Promise { await this.page .getByRole('textbox', { - name: this.textMock('process_editor.configuration_panel_change_task_id'), + name: this.textMock('process_editor.configuration_panel_change_task_id_label'), }) .clear(); } @@ -102,14 +102,14 @@ export class ProcessEditorPage extends BasePage { public async writeNewId(id: string): Promise { await this.page .getByRole('textbox', { - name: this.textMock('process_editor.configuration_panel_change_task_id'), + name: this.textMock('process_editor.configuration_panel_change_task_id_label'), }) .fill(id); } public async waitForTextBoxToHaveValue(id: string): Promise { const textBox = this.page.getByRole('textbox', { - name: this.textMock('process_editor.configuration_panel_change_task_id'), + name: this.textMock('process_editor.configuration_panel_change_task_id_label'), }); await expect(textBox).toHaveValue(id); } @@ -117,7 +117,7 @@ export class ProcessEditorPage extends BasePage { public async saveNewId(): Promise { await this.page .getByRole('textbox', { - name: this.textMock('process_editor.configuration_panel_change_task_id'), + name: this.textMock('process_editor.configuration_panel_change_task_id_label'), }) .blur(); }