From 0df80450cc93dec40e349168f3b351e586c6b562 Mon Sep 17 00:00:00 2001 From: JamalAlabdullah <90609090+JamalAlabdullah@users.noreply.github.com> Date: Thu, 20 Feb 2025 17:47:53 +0100 Subject: [PATCH] fix: 14472 should show the value of selector in properties config (#14686) --- frontend/language/src/nb.json | 2 +- .../CollapsiblePropertyEditor.module.css | 23 ----- .../CollapsiblePropertyEditor.test.tsx | 57 ----------- .../CollapsiblePropertyEditor.tsx | 50 ---------- .../config/CollapsiblePropertyEditor/index.ts | 1 - .../config/FormComponentConfig.module.css | 1 + .../config/FormComponentConfig.test.tsx | 99 ++++++++++++++++--- .../components/config/FormComponentConfig.tsx | 59 ++++++++--- .../SelectPropertyEditor.module.css | 27 +++++ .../SelectPropertyEditor.test.tsx | 48 +++++++++ .../SelectPropertyEditor.tsx | 47 +++++++++ .../config/SelectPropertyEditor/index.ts | 1 + 12 files changed, 260 insertions(+), 155 deletions(-) delete mode 100644 frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.module.css delete mode 100644 frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.test.tsx delete mode 100644 frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.tsx delete mode 100644 frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/index.ts create mode 100644 frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.module.css create mode 100644 frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.test.tsx create mode 100644 frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.tsx create mode 100644 frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/index.ts diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 4272bb5fc23..15e54fc8b05 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -1456,7 +1456,7 @@ "ux_editor.component_properties.showIcon": "Vis ikon", "ux_editor.component_properties.showLabelsInTable": "Alternativene skal alltid vises i tabeller", "ux_editor.component_properties.showPageInAccordion": "Vis side i trekkspilliste", - "ux_editor.component_properties.showValidations": "Vis valideringstyper", + "ux_editor.component_properties.showValidations": "Valideringstyper", "ux_editor.component_properties.simplified": "Forenklet visning", "ux_editor.component_properties.size": "Størrelse", "ux_editor.component_properties.sortOrder": "Sorteringsrekkefølge", diff --git a/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.module.css b/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.module.css deleted file mode 100644 index ed1fcde3172..00000000000 --- a/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.module.css +++ /dev/null @@ -1,23 +0,0 @@ -.collapsibleContainer { - display: flex; - flex-direction: row; - align-items: flex-end; - gap: var(--fds-spacing-4); - padding-bottom: var(--fds-spacing-4); - padding-top: var(--fds-spacing-1); -} - -.collapsibleContainerClosed { - margin-top: var(--fds-spacing-1); - margin-bottom: var(--fds-spacing-1); -} - -.editorContent { - flex: 1; -} - -.button { - display: flex; - gap: var(--fds-spacing-3); - padding-left: 0; -} diff --git a/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.test.tsx b/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.test.tsx deleted file mode 100644 index 56f1f156a11..00000000000 --- a/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.test.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import { - CollapsiblePropertyEditor, - type CollapsiblePropertyEditorProps, -} from './CollapsiblePropertyEditor'; -import userEvent from '@testing-library/user-event'; -import { screen } from '@testing-library/react'; -import { renderWithProviders } from 'app-development/test/mocks'; -import { textMock } from '@studio/testing/mocks/i18nMock'; - -// Test data -const label = 'Test label'; -const children =
Test children
; -const icon =
Test icon
; - -describe('CollapsiblePropertyEditor', () => { - it('should render the label', () => { - renderCollapsiblePropertyEditor({ label: label }); - expect(screen.getByText('Test label')).toBeInTheDocument(); - }); - - it('should render the icon', () => { - renderCollapsiblePropertyEditor({ icon:
Test icon
}); - expect(screen.getByText('Test icon')).toBeInTheDocument(); - }); - - it('should render the children', () => { - renderCollapsiblePropertyEditor(); - expect(screen.queryByText('Test children')).not.toBeInTheDocument(); - }); - - it('should render the children when the button is clicked', async () => { - const user = userEvent.setup(); - renderCollapsiblePropertyEditor(); - await user.click(screen.getByText('Test label')); - expect(screen.getByText('Test children')).toBeInTheDocument(); - }); - - it('should hide the children when the close button is clicked', async () => { - const user = userEvent.setup(); - renderCollapsiblePropertyEditor(); - await user.click(screen.getByText('Test label')); - expect(screen.getByText('Test children')).toBeInTheDocument(); - await user.click(screen.getByRole('button', { name: textMock('general.close') })); - expect(screen.queryByText('Test children')).not.toBeInTheDocument(); - }); -}); - -const defaultProps: CollapsiblePropertyEditorProps = { - label, - children, - icon, -}; - -const renderCollapsiblePropertyEditor = (props: Partial = {}) => { - renderWithProviders()(); -}; diff --git a/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.tsx b/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.tsx deleted file mode 100644 index e4ea3c369ce..00000000000 --- a/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/CollapsiblePropertyEditor.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useState } from 'react'; -import { PlusCircleIcon, XMarkIcon } from '@studio/icons'; -import { StudioButton, StudioProperty } from '@studio/components'; -import classes from './CollapsiblePropertyEditor.module.css'; -import { useTranslation } from 'react-i18next'; -import cn from 'classnames'; - -export type CollapsiblePropertyEditorProps = { - label?: string; - children?: React.ReactNode; - icon?: React.ReactNode; - disabledCloseButton?: boolean; -}; - -export const CollapsiblePropertyEditor = ({ - label, - children, - disabledCloseButton = false, - icon = , -}: CollapsiblePropertyEditorProps) => { - const { t } = useTranslation(); - const [isVisible, setIsVisible] = useState(false); - - return ( -
- {!isVisible ? ( - setIsVisible(true)} - property={label} - /> - ) : ( - <> -
{children}
- {!disabledCloseButton && ( - } - onClick={() => setIsVisible(false)} - title={t('general.close')} - variant='secondary' - /> - )} - - )} -
- ); -}; diff --git a/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/index.ts b/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/index.ts deleted file mode 100644 index b7790d086df..00000000000 --- a/frontend/packages/ux-editor/src/components/config/CollapsiblePropertyEditor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { CollapsiblePropertyEditor } from './CollapsiblePropertyEditor'; diff --git a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.module.css b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.module.css index 6f4fe987c4c..1a8653696b4 100644 --- a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.module.css +++ b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.module.css @@ -14,6 +14,7 @@ .gridButton { padding: 0; + margin-bottom: var(--fds-spacing-0); } .gridHeader { diff --git a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.test.tsx b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.test.tsx index c219f0340ff..52e4a0a3e1a 100644 --- a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.test.tsx @@ -6,7 +6,7 @@ import { componentMocks } from '../../testing/componentMocks'; import InputSchema from '../../testing/schemas/json/component/Input.schema.v1.json'; import DatepickerSchema from '../../testing/schemas/json/component/Datepicker.schema.v1.json'; import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; -import { screen } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import { textMock } from '@studio/testing/mocks/i18nMock'; import type { KeyValuePairs } from 'app-shared/types/KeyValuePairs'; import userEvent from '@testing-library/user-event'; @@ -160,7 +160,7 @@ describe('FormComponentConfig', () => { expect(screen.queryByText('unsupportedProperty')).not.toBeInTheDocument(); }); - it('should render CollapsiblePropertyEditor for the "sortOrder" property', async () => { + it('should render property text for the "sortOrder" property', async () => { const user = userEvent.setup(); render({ props: { @@ -187,7 +187,7 @@ describe('FormComponentConfig', () => { ).toBeInTheDocument(); }); - it('should render CollapsiblePropertyEditor for the "showValidations" property and EditStringValue for other properties', () => { + it('should render property text for the "showValidations" property', () => { render({ props: { schema: { @@ -217,7 +217,7 @@ describe('FormComponentConfig', () => { ).toBeInTheDocument(); }); - it('should render CollapsiblePropertyEditor for "preselectedOptionIndex" and EditNumberValue for other properties', () => { + it('should render property text for "preselectedOptionIndex" and EditNumberValue for other properties', () => { render({ props: { schema: { @@ -281,15 +281,56 @@ describe('FormComponentConfig', () => { ).not.toBeInTheDocument(); }); - it('should show description text for objects if key is defined', () => { - render({ - props: { - schema: InputSchema, - }, + it('should call handleComponentUpdate and setSelectedValue when array property is updated', async () => { + const user = userEvent.setup(); + const handleComponentUpdateMock = jest.fn(); + const propertyKey = 'supportedArrayProperty'; + renderWithProviders( + , + ); + const arrayPropertyButton = screen.getByRole('button', { + name: textMock(`ux_editor.component_properties.${propertyKey}`), }); - expect( - screen.getByText(textMock('ux_editor.component_properties_description.pageBreak')), - ).toBeInTheDocument(); + await user.click(arrayPropertyButton); + + const combobox = screen.getByRole('combobox', { + name: textMock(`ux_editor.component_properties.${propertyKey}`), + }); + await user.click(combobox); + + const option1 = screen.getByRole('option', { + name: textMock('ux_editor.component_properties.enum_option1'), + }); + await user.click(option1); + + await waitFor(() => { + expect(handleComponentUpdateMock).toHaveBeenCalledWith( + expect.objectContaining({ + [propertyKey]: ['option1'], + }), + ); + }); + + const selectedValueDisplay = screen.getByRole('option', { + name: textMock('ux_editor.component_properties.enum_option1'), + }); + expect(selectedValueDisplay).toBeInTheDocument(); }); it('should render default boolean values if defined', async () => { @@ -442,6 +483,40 @@ describe('FormComponentConfig', () => { ); }); + it('should render array properties with enum values correctly', async () => { + const user = userEvent.setup(); + const propertyKey = 'supportedArrayProperty'; + const enumValues = ['option1', 'option2']; + render({ + props: { + schema: { + properties: { + [propertyKey]: { + type: 'array', + items: { + type: 'string', + enum: enumValues, + }, + }, + }, + }, + component: { + ...componentMocks.Input, + [propertyKey]: enumValues, + }, + }, + }); + const arrayPropertyButton = screen.getByRole('button', { + name: textMock(`ux_editor.component_properties.${propertyKey}`), + }); + await user.click(arrayPropertyButton); + for (const dataType of enumValues) { + expect( + screen.getByText(textMock(`ux_editor.component_properties.enum_${dataType}`)), + ).toBeInTheDocument(); + } + }); + it('should call handleComponentUpdate with updated component when hasCustomFileEndings is true', async () => { const handleComponentUpdateMock = jest.fn(); render({ diff --git a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx index 872cb79ff9b..88cb3086137 100644 --- a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx +++ b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { Alert, Card, Heading, Paragraph } from '@digdir/designsystemet-react'; import type { FormComponent } from '../../types/FormComponent'; import { EditBooleanValue } from './editModal/EditBooleanValue'; @@ -18,7 +18,8 @@ import classes from './FormComponentConfig.module.css'; import { RedirectToLayoutSet } from './editModal/RedirectToLayoutSet'; import { ChevronDownIcon, ChevronUpIcon, PlusCircleIcon, XMarkIcon } from '@studio/icons'; import { StudioButton, StudioCard, StudioProperty } from '@studio/components'; -import { CollapsiblePropertyEditor } from './CollapsiblePropertyEditor'; +import { useComponentPropertyEnumValue } from '@altinn/ux-editor/hooks/useComponentPropertyEnumValue'; +import { SelectPropertyEditor } from './SelectPropertyEditor/SelectPropertyEditor'; export interface IEditFormComponentProps { editFormId: string; @@ -44,6 +45,27 @@ export const FormComponentConfig = ({ const [showOtherComponents, setShowOtherComponents] = useState(false); const [showGrid, setShowGrid] = useState(false); + const selectedDataType = useComponentPropertyEnumValue(); + + const memoizedGetSelectedValuesDisplay = useMemo( + () => (propertyKey: string) => { + if (!component[propertyKey] || component[propertyKey].length === 0) return undefined; + return component[propertyKey].map((dataType: string) => ( +
{selectedDataType(dataType)}
+ )); + }, + [component, selectedDataType], + ); + + const memoizedSelectedStringPropertiesDisplay = useMemo( + () => (propertyKey: string) => { + const value = component[propertyKey]; + if (Array.isArray(value)) return value.map((dataType) => selectedDataType(dataType)); + return value ? selectedDataType(value) : undefined; + }, + [component, selectedDataType], + ); + if (!schema?.properties) return null; const { properties } = schema; @@ -222,25 +244,33 @@ export const FormComponentConfig = ({ {/** String properties */} {stringPropertyKeys.map((propertyKey) => { return ( - + - + ); })} {/** Number properties (number and integer types) */} {numberPropertyKeys.map((propertyKey) => { return ( - - + ); })} {/** Array properties with enum values) */} {arrayPropertyKeys.map((propertyKey) => { return ( - + { + handleComponentUpdate(updatedComponent); + }} propertyKey={propertyKey} key={propertyKey} enumValues={properties[propertyKey]?.items?.enum} multiple={true} /> - + ); })} - {/** Object properties */} + {/** Object properties */} {objectPropertyKeys.map((propertyKey) => { return ( diff --git a/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.module.css b/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.module.css new file mode 100644 index 00000000000..8998a8c7639 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.module.css @@ -0,0 +1,27 @@ +.container { + display: grid; + gap: var(--fds-spacing-2); + padding-bottom: var(--fds-spacing-1); + align-items: center; +} + +.viewMode { + margin-top: var(--fds-spacing-1); + margin-bottom: var(--fds-spacing-1); +} + +.editSelectProperty { + display: flex; + flex-direction: row; + align-items: end; + gap: var(--fds-spacing-4); + margin-bottom: var(--fds-spacing-3); +} + +.selectProperty { + flex: 1; +} + +.viewSelectProperty { + padding-left: 0; +} diff --git a/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.test.tsx b/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.test.tsx new file mode 100644 index 00000000000..43890b3bba9 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.test.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { screen } from '@testing-library/react'; +import { renderWithProviders } from 'app-development/test/mocks'; +import { textMock } from '@studio/testing/mocks/i18nMock'; +import { SelectPropertyEditor, type SelectPropertyEditorProps } from './SelectPropertyEditor'; + +const children =
Test children
; +const value =
Test value
; +const property = 'Test property'; + +describe('SelectPropertyEditor', () => { + it('should render the children when button is clicked', async () => { + const user = userEvent.setup(); + renderSelectPropertyEditor(); + await user.click(screen.getByText('Test property')); + expect(screen.getByText('Test children')).toBeInTheDocument(); + }); + + it('should hide the children when the close button is clicked', async () => { + const user = userEvent.setup(); + renderSelectPropertyEditor(); + await user.click(screen.getByText('Test property')); + expect(screen.getByText('Test children')).toBeInTheDocument(); + await user.click(screen.getByRole('button', { name: textMock('general.close') })); + expect(screen.queryByText('Test children')).not.toBeInTheDocument(); + }); + + it('should render value', () => { + renderSelectPropertyEditor(); + expect(screen.getByText('Test value')).toBeInTheDocument(); + }); + + it('should render the property', () => { + renderSelectPropertyEditor({ property: 'Test property' }); + expect(screen.getByText('Test property')).toBeInTheDocument(); + }); +}); + +const defaultProps: SelectPropertyEditorProps = { + children, + value, + property, +}; + +const renderSelectPropertyEditor = (props: Partial = {}) => { + renderWithProviders()(); +}; diff --git a/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.tsx b/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.tsx new file mode 100644 index 00000000000..001ed0309e4 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/SelectPropertyEditor.tsx @@ -0,0 +1,47 @@ +import React, { useState } from 'react'; +import { XMarkIcon } from '@studio/icons'; +import { StudioButton, StudioProperty } from '@studio/components'; +import classes from './SelectPropertyEditor.module.css'; +import { useTranslation } from 'react-i18next'; +import cn from 'classnames'; + +export type SelectPropertyEditorProps = { + children?: React.ReactNode; + value?: string | React.ReactNode; + property?: string; + title?: string; +}; + +export const SelectPropertyEditor = ({ + children, + value, + property, + title, +}: SelectPropertyEditorProps) => { + const { t } = useTranslation(); + const [dataTypeSelectVisible, setDataTypeSelectVisible] = useState(false); + + return ( +
+ {dataTypeSelectVisible ? ( +
+
{children}
+ } + onClick={() => setDataTypeSelectVisible(false)} + title={t('general.close')} + variant='secondary' + /> +
+ ) : ( + setDataTypeSelectVisible(true)} + property={property} + title={title} + value={value} + className={classes.viewSelectProperty} + /> + )} +
+ ); +}; diff --git a/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/index.ts b/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/index.ts new file mode 100644 index 00000000000..400bc19d606 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/SelectPropertyEditor/index.ts @@ -0,0 +1 @@ +export { SelectPropertyEditor } from './SelectPropertyEditor';