From b2bc769ba766a70ae018d04ac9c5ee76ceeb548a Mon Sep 17 00:00:00 2001 From: Andrea Standeren Date: Thu, 1 Feb 2024 10:00:18 +0100 Subject: [PATCH 01/18] Move text-config to separate section --- frontend/language/src/nb.json | 1 + .../src/components/Properties/Properties.tsx | 15 ++++++- .../src/components/Properties/Text.tsx | 44 +++++++++++++++++++ .../components/config/EditFormContainer.tsx | 19 +------- .../components/config/FormComponentConfig.tsx | 19 -------- .../ux-editor/src/data/formItemConfig.ts | 1 + .../hooks/queries/useComponentSchemaQuery.ts | 2 +- .../src/testing/componentSchemaMocks.ts | 5 ++- 8 files changed, 64 insertions(+), 42 deletions(-) create mode 100644 frontend/packages/ux-editor/src/components/Properties/Text.tsx diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 90d15ebbf7d..fddbac511f1 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -1032,6 +1032,7 @@ "right_menu.rules_conditional_rendering_add_alt": "Legg til regel for vis/skjul felt", "right_menu.rules_empty": "Ingen regler lagt til...", "right_menu.show_old_dynamics": "Vis gammelt verktøy for dynamikk", + "right_menu.text": "Tekst", "right_menu.warning_dynamics_deprecated": "Denne funksjonaliteten utgår og vi kommer ikke til å vedlikeholde den. Vi anbefaler å bruke <0 href=\"{{expressionDocs}}\" >dynamiske uttrykk.", "schema_editor.active_languages": "Aktive språk:", "schema_editor.add": "Legg til", diff --git a/frontend/packages/ux-editor/src/components/Properties/Properties.tsx b/frontend/packages/ux-editor/src/components/Properties/Properties.tsx index 5879dff6ebd..4d74f1e5703 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Properties.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Properties.tsx @@ -1,6 +1,7 @@ import React, { useEffect } from 'react'; import { Calculations } from './Calculations'; import { Content } from './Content'; +import { Text } from './Text'; import { useTranslation } from 'react-i18next'; import { Accordion } from '@digdir/design-system-react'; import { useFormContext } from '../../containers/FormContext'; @@ -28,10 +29,18 @@ export const Properties = () => { setOpenList([...openList, id]); } }; - + return (
+ + toggleOpen('text')}> + {t('right_menu.text')} + + + {formId && } + + toggleOpen('content')}> {t('right_menu.content')} @@ -44,7 +53,9 @@ export const Properties = () => { toggleOpen('dynamics')}> {t('right_menu.dynamics')} - {formId && } + + {formId && } + toggleOpen('calculations')}> diff --git a/frontend/packages/ux-editor/src/components/Properties/Text.tsx b/frontend/packages/ux-editor/src/components/Properties/Text.tsx new file mode 100644 index 00000000000..cd8520e4a79 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/Properties/Text.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { useFormContext } from '../../containers/FormContext'; +import { useTranslation } from 'react-i18next'; +import { Heading } from '@digdir/design-system-react'; +import {EditTextResourceBindings} from "../config/editModal/EditTextResourceBindings"; +import {useLayoutSchemaQuery} from "../../hooks/queries/useLayoutSchemaQuery"; +import {useComponentSchemaQuery} from "../../hooks/queries/useComponentSchemaQuery"; +import {selectedLayoutNameSelector} from "../../selectors/formLayoutSelectors"; +import {LayoutItemType} from "../../types/global"; +import {TextResource} from "../TextResource"; + +export const Text = () => { + const { formId, form, handleUpdate, debounceSave } = useFormContext(); + const { t } = useTranslation(); + + useLayoutSchemaQuery(); // Ensure we load the layout schemas so that component schemas can be loaded + const { data: schema, isPending } = useComponentSchemaQuery(form.type); + const selectedLayout = useSelector(selectedLayoutNameSelector); + + if (!schema?.properties) return null; + + return ( + <> + {schema.properties.textResourceBindings?.properties && ( + <> + + {t('general.text')} + + { + handleUpdate(updatedComponent); + debounceSave(formId, updatedComponent); + }} + textResourceBindingKeys={Object.keys(schema.properties.textResourceBindings.properties)} + editFormId={formId} + layoutName={selectedLayout} + /> + + )} + + ); +}; diff --git a/frontend/packages/ux-editor/src/components/config/EditFormContainer.tsx b/frontend/packages/ux-editor/src/components/config/EditFormContainer.tsx index 2b87df6ecdb..3fb0d82dee3 100644 --- a/frontend/packages/ux-editor/src/components/config/EditFormContainer.tsx +++ b/frontend/packages/ux-editor/src/components/config/EditFormContainer.tsx @@ -14,7 +14,6 @@ import { Paragraph, } from '@digdir/design-system-react'; import classes from './EditFormContainer.module.css'; -import { TextResource } from '../TextResource'; import { useDatamodelMetadataQuery } from '../../hooks/queries/useDatamodelMetadataQuery'; import { useText } from '../../hooks'; import { useSelectedFormLayout, useTextResourcesSelector } from '../../hooks'; @@ -87,16 +86,6 @@ export const EditFormContainer = ({ }); }; - const handleButtonTextChange = (id: string) => { - handleContainerUpdate({ - ...container, - textResourceBindings: { - ...container.textResourceBindings, - add_button: id, - }, - }); - }; - const handleTableHeadersChange = (ids: string[]) => { const updatedContainer = { ...container }; updatedContainer.tableHeaders = [...ids]; @@ -137,7 +126,7 @@ export const EditFormContainer = ({ id, }); }; - + return container.type === ComponentType.Group ? ( )} /> - {items?.length > 0 && ( { - const selectedLayout = useSelector(selectedLayoutNameSelector); const t = useText(); if (!schema?.properties) return null; const { - textResourceBindings, dataModelBindings, required, readOnly, @@ -67,20 +62,6 @@ export const FormComponentConfig = ({ helpText={id.description} /> )} - {textResourceBindings?.properties && ( - <> - - {t('general.text')} - - - - )} {dataModelBindings?.properties && ( <> diff --git a/frontend/packages/ux-editor/src/data/formItemConfig.ts b/frontend/packages/ux-editor/src/data/formItemConfig.ts index d61971c5c7b..2b1ebfc3c03 100644 --- a/frontend/packages/ux-editor/src/data/formItemConfig.ts +++ b/frontend/packages/ux-editor/src/data/formItemConfig.ts @@ -65,6 +65,7 @@ export const formItemConfigs: FormItemConfigs = { itemType: 'CONTAINER', type: ComponentType.Accordion, propertyPath: 'definitions/accordionComponent', + children: [], }, icon: Accordion, validChildTypes: [ComponentType.Paragraph], diff --git a/frontend/packages/ux-editor/src/hooks/queries/useComponentSchemaQuery.ts b/frontend/packages/ux-editor/src/hooks/queries/useComponentSchemaQuery.ts index 81501796a6e..9942d1ebf5d 100644 --- a/frontend/packages/ux-editor/src/hooks/queries/useComponentSchemaQuery.ts +++ b/frontend/packages/ux-editor/src/hooks/queries/useComponentSchemaQuery.ts @@ -13,7 +13,7 @@ export interface UseComponentSchemaQueryResult { // Currently use local mocks rather than fetching from CDN. This is because the CDN schemas are not ready to use, and // we also have made some modifications locally to the schemas. -// When the schemas are available on CDN, we can remove the mocks and use the querys instead. +// When the schemas are available on CDN, we can remove the mocks and use the queries instead. export const useComponentSchemaQuery = (component: string): UseQueryResult => { const queryClient = useQueryClient(); diff --git a/frontend/packages/ux-editor/src/testing/componentSchemaMocks.ts b/frontend/packages/ux-editor/src/testing/componentSchemaMocks.ts index ba5013341af..f7a6053c413 100644 --- a/frontend/packages/ux-editor/src/testing/componentSchemaMocks.ts +++ b/frontend/packages/ux-editor/src/testing/componentSchemaMocks.ts @@ -1,6 +1,6 @@ import AlertSchema from './schemas/json/component/Alert.schema.v1.json'; // TODO: Add schemas for the commented out components (https://github.com/Altinn/altinn-studio/issues/10868): -// import AccordionSchema from './schemas/json/component/Accordion.schema.v1.json'; +import AccordionSchema from './schemas/json/component/Accordion.schema.v1.json'; // import AccordionGroupSchema from './schemas/json/component/AccordionGroup.schema.v1.json'; import ActionButtonSchema from './schemas/json/component/ActionButton.schema.v1.json'; import AddressComponentSchema from './schemas/json/component/AddressComponent.schema.v1.json'; @@ -36,9 +36,10 @@ import SummarySchema from './schemas/json/component/Summary.schema.v1.json'; import TextAreaSchema from './schemas/json/component/TextArea.schema.v1.json'; export const componentSchemaMocks = { - Alert: AlertSchema, + Accordion: AccordionSchema, ActionButton: ActionButtonSchema, AddressComponent: AddressComponentSchema, + Alert: AlertSchema, AttachmentList: AttachmentListSchema, Button: ButtonSchema, ButtonGroup: ButtonGroupSchema, From f9d20ff1e171e33e658cddb442d4e01e037e52f4 Mon Sep 17 00:00:00 2001 From: Andrea Standeren Date: Thu, 1 Feb 2024 10:54:54 +0100 Subject: [PATCH 02/18] Add texts for textResourceBindings on group-component --- frontend/language/src/nb.json | 10 ++++++++++ .../ux-editor/src/components/Properties/Content.tsx | 7 +------ .../ux-editor/src/components/Properties/Text.tsx | 12 ++++++++---- .../config/editModal/EditTextResourceBinding.tsx | 4 ++-- .../config/editModal/EditTextResourceBindings.tsx | 12 +++++++++--- .../packages/ux-editor/src/data/formItemConfig.ts | 1 - 6 files changed, 30 insertions(+), 16 deletions(-) diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index fddbac511f1..5579143246e 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -1610,6 +1610,8 @@ "ux_editor.modal_properties_read_only": "Read-only.", "ux_editor.modal_properties_read_only_description": "Om dette feltet ikke skal kunne redigeres, kan du skru på read-only.", "ux_editor.modal_properties_tag_helper": "Søk etter merkebeskrivelse", + "ux_editor.modal_properties_textResourceBindings_add_button": "'Legg til ny'-knapp", + "ux_editor.modal_properties_textResourceBindings_add_button_add": "Legg til tekst for 'Legg til ny'-knapp", "ux_editor.modal_properties_textResourceBindings_altTextImg": "Alternativ tekst for bilde", "ux_editor.modal_properties_textResourceBindings_altTextImg_add": "Legg til alternativ tekst for bilde", "ux_editor.modal_properties_textResourceBindings_back": "Tekst på tilbake-knapp", @@ -1618,10 +1620,18 @@ "ux_editor.modal_properties_textResourceBindings_body_add": "Legg til tekstinnhold", "ux_editor.modal_properties_textResourceBindings_description": "Beskrivelse", "ux_editor.modal_properties_textResourceBindings_description_add": "Legg til beskrivelse", + "ux_editor.modal_properties_textResourceBindings_edit_button_close": "'Rediger'-knapp (lukket gruppe)", + "ux_editor.modal_properties_textResourceBindings_edit_button_close_add": "Legg til tekst for 'Rediger'-knapp (lukket gruppe)", + "ux_editor.modal_properties_textResourceBindings_edit_button_open": "'Rediger'-knapp (åpen gruppe)", + "ux_editor.modal_properties_textResourceBindings_edit_button_open_add": "Legg til tekst for 'Rediger'-knapp (åpen gruppe)", "ux_editor.modal_properties_textResourceBindings_help": "Hjelpetekst", "ux_editor.modal_properties_textResourceBindings_help_add": "Legg til hjelpetekst", "ux_editor.modal_properties_textResourceBindings_next": "Tekst på neste-knapp", "ux_editor.modal_properties_textResourceBindings_next_add": "Legg til tekst på neste-knapp", + "ux_editor.modal_properties_textResourceBindings_save_and_next_button": "'Lagre og neste'-knapp", + "ux_editor.modal_properties_textResourceBindings_save_and_next_button_add": "Legg til tekst for 'Lagre og neste'-knapp", + "ux_editor.modal_properties_textResourceBindings_save_button": "'Lagre'-knapp", + "ux_editor.modal_properties_textResourceBindings_save_button_add": "'Legg til tekst for Lagre'-knapp", "ux_editor.modal_properties_textResourceBindings_shortName": "Kortnavn", "ux_editor.modal_properties_textResourceBindings_shortName_add": "Legg til kortnavn", "ux_editor.modal_properties_textResourceBindings_tableTitle": "Tittel i tabell", diff --git a/frontend/packages/ux-editor/src/components/Properties/Content.tsx b/frontend/packages/ux-editor/src/components/Properties/Content.tsx index 8a287f5baab..b42189cbe2c 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Content.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Content.tsx @@ -1,19 +1,14 @@ import React from 'react'; -import { TextResourceEdit } from '../TextResourceEdit'; import { EditFormComponent } from '../config/EditFormComponent'; import { EditFormContainer } from '../config/EditFormContainer'; -import { getCurrentEditId } from '../../selectors/textResourceSelectors'; -import { useSelector } from 'react-redux'; import { useFormContext } from '../../containers/FormContext'; import { useTranslation } from 'react-i18next'; import { isContainer } from '../../utils/formItemUtils'; export const Content = () => { const { formId, form, handleUpdate, debounceSave } = useFormContext(); - const editId = useSelector(getCurrentEditId); const { t } = useTranslation(); - - if (editId) return ; + if (!formId || !form) return t('right_menu.content_empty'); return isContainer(form) ? ( diff --git a/frontend/packages/ux-editor/src/components/Properties/Text.tsx b/frontend/packages/ux-editor/src/components/Properties/Text.tsx index cd8520e4a79..60457daa09f 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Text.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Text.tsx @@ -7,8 +7,9 @@ import {EditTextResourceBindings} from "../config/editModal/EditTextResourceBind import {useLayoutSchemaQuery} from "../../hooks/queries/useLayoutSchemaQuery"; import {useComponentSchemaQuery} from "../../hooks/queries/useComponentSchemaQuery"; import {selectedLayoutNameSelector} from "../../selectors/formLayoutSelectors"; -import {LayoutItemType} from "../../types/global"; -import {TextResource} from "../TextResource"; +import { StudioSpinner } from '@studio/components'; +import {getCurrentEditId} from "../../selectors/textResourceSelectors"; +import {TextResourceEdit} from "../TextResourceEdit"; export const Text = () => { const { formId, form, handleUpdate, debounceSave } = useFormContext(); @@ -17,12 +18,15 @@ export const Text = () => { useLayoutSchemaQuery(); // Ensure we load the layout schemas so that component schemas can be loaded const { data: schema, isPending } = useComponentSchemaQuery(form.type); const selectedLayout = useSelector(selectedLayoutNameSelector); - + const editId = useSelector(getCurrentEditId); + + if (editId) return ; if (!schema?.properties) return null; return ( <> - {schema.properties.textResourceBindings?.properties && ( + {isPending && } + {schema.properties.textResourceBindings?.properties && !isPending && ( <> {t('general.text')} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBinding.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBinding.tsx index fb71990e25b..1364971b68c 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBinding.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBinding.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import type { IGenericEditComponent } from '../componentConfig'; import { TextResource } from '../../TextResource'; import type { TranslationKey } from 'language/type'; import type { IAppState } from '../../../types/global'; import { useTranslation } from 'react-i18next'; +import {EditTextResourceBindingsProps} from "./EditTextResourceBindings"; -export interface EditTextResourceBindingProps extends IGenericEditComponent { +export interface EditTextResourceBindingProps extends EditTextResourceBindingsProps { textKey: string; labelKey: TranslationKey; descriptionKey?: TranslationKey; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx index 5a204eb86cf..1141cc657d3 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx @@ -1,15 +1,20 @@ import React, { useMemo } from 'react'; -import type { IGenericEditComponent } from '../componentConfig'; import { EditTextResourceBinding } from './EditTextResourceBinding'; import classes from './EditTextResourceBindings.module.css'; import type { TranslationKey } from 'language/type'; import { useTranslation } from 'react-i18next'; import { LegacySelect } from '@digdir/design-system-react'; +import {FormContainer} from "../../../types/FormContainer"; +import {FormComponent} from "../../../types/FormComponent"; export type TextResourceBindingKey = 'description' | 'title' | 'help' | 'body'; -export interface EditTextResourceBindingsProps extends IGenericEditComponent { - textResourceBindingKeys: string[]; +export interface EditTextResourceBindingsProps { + editFormId?: string; + component: FormComponent | FormContainer; + handleComponentChange: (component: FormComponent | FormContainer) => void; + textResourceBindingKeys?: string[]; + layoutName?: string; } export const EditTextResourceBindings = ({ @@ -21,6 +26,7 @@ export const EditTextResourceBindings = ({ const [keysSet, setKeysSet] = React.useState(Object.keys(component.textResourceBindings || {})); + debugger; const keysToAdd = useMemo( () => textResourceBindingKeys.filter((key) => !keysSet.includes(key)), [keysSet, textResourceBindingKeys], diff --git a/frontend/packages/ux-editor/src/data/formItemConfig.ts b/frontend/packages/ux-editor/src/data/formItemConfig.ts index 2b1ebfc3c03..d61971c5c7b 100644 --- a/frontend/packages/ux-editor/src/data/formItemConfig.ts +++ b/frontend/packages/ux-editor/src/data/formItemConfig.ts @@ -65,7 +65,6 @@ export const formItemConfigs: FormItemConfigs = { itemType: 'CONTAINER', type: ComponentType.Accordion, propertyPath: 'definitions/accordionComponent', - children: [], }, icon: Accordion, validChildTypes: [ComponentType.Paragraph], From d3eeb845cbab1def3dfe4f26415051299113aec9 Mon Sep 17 00:00:00 2001 From: Andrea Standeren Date: Thu, 1 Feb 2024 12:22:15 +0100 Subject: [PATCH 03/18] Update tests --- .../components/Properties/Content.test.tsx | 15 --- .../src/components/Properties/Content.tsx | 4 - .../components/Properties/Properties.test.tsx | 8 ++ .../src/components/Properties/Properties.tsx | 10 +- .../src/components/Properties/Text.test.tsx | 120 ++++++++++++++++++ .../src/components/Properties/Text.tsx | 70 +++++----- .../config/EditFormComponent.test.tsx | 1 - .../editModal/EditTextResourceBinding.tsx | 2 +- .../editModal/EditTextResourceBindings.tsx | 6 +- 9 files changed, 171 insertions(+), 65 deletions(-) create mode 100644 frontend/packages/ux-editor/src/components/Properties/Text.test.tsx diff --git a/frontend/packages/ux-editor/src/components/Properties/Content.test.tsx b/frontend/packages/ux-editor/src/components/Properties/Content.test.tsx index aabed980cfd..3122eb2fb69 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Content.test.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Content.test.tsx @@ -19,24 +19,9 @@ import { useLayoutSchemaQuery } from '../../hooks/queries/useLayoutSchemaQuery'; const user = userEvent.setup(); -// Test data: -const textResourceEditTestId = 'text-resource-edit'; - -// Mocks: -jest.mock('../TextResourceEdit', () => ({ - TextResourceEdit: () =>
, -})); - describe('ContentTab', () => { afterEach(jest.clearAllMocks); - describe('when editing a text resource', () => { - it('should render the component', async () => { - await render({ props: {}, editId: 'test' }); - expect(screen.getByTestId(textResourceEditTestId)).toBeInTheDocument(); - }); - }); - describe('when editing a container', () => { const props = { formId: container1IdMock, diff --git a/frontend/packages/ux-editor/src/components/Properties/Content.tsx b/frontend/packages/ux-editor/src/components/Properties/Content.tsx index b42189cbe2c..1825b36eb15 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Content.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Content.tsx @@ -2,14 +2,10 @@ import React from 'react'; import { EditFormComponent } from '../config/EditFormComponent'; import { EditFormContainer } from '../config/EditFormContainer'; import { useFormContext } from '../../containers/FormContext'; -import { useTranslation } from 'react-i18next'; import { isContainer } from '../../utils/formItemUtils'; export const Content = () => { const { formId, form, handleUpdate, debounceSave } = useFormContext(); - const { t } = useTranslation(); - - if (!formId || !form) return t('right_menu.content_empty'); return isContainer(form) ? ( ({ + Text: () =>
, +})); jest.mock('./Content', () => ({ Content: () =>
, })); @@ -109,9 +115,11 @@ describe('Properties', () => { it('Renders accordion', () => { const formIdMock = 'test-id'; render({ formId: formIdMock }); + expect(screen.getByText(textText)).toBeInTheDocument(); expect(screen.getByText(contentText)).toBeInTheDocument(); expect(screen.getByText(dynamicsText)).toBeInTheDocument(); expect(screen.getByText(calculationsText)).toBeInTheDocument(); + expect(screen.getByTestId(textTestId)).toBeInTheDocument(); expect(screen.getByTestId(contentTestId)).toBeInTheDocument(); expect(screen.getByTestId(expressionsTestId)).toBeInTheDocument(); expect(screen.getByTestId(calculationsTestId)).toBeInTheDocument(); diff --git a/frontend/packages/ux-editor/src/components/Properties/Properties.tsx b/frontend/packages/ux-editor/src/components/Properties/Properties.tsx index 4d74f1e5703..d871b3e34f1 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Properties.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Properties.tsx @@ -18,7 +18,7 @@ export const Properties = () => { useEffect(() => { if (formIdRef.current !== formId) { formIdRef.current = formId; - if (formId && openList.length === 0) setOpenList(['content']); + if (formId && openList.length === 0) setOpenList(['text']); } }, [formId, openList.length]); @@ -29,7 +29,7 @@ export const Properties = () => { setOpenList([...openList, id]); } }; - + return (
@@ -38,7 +38,7 @@ export const Properties = () => { {t('right_menu.text')} - {formId && } + {formId ? : t('right_menu.content_empty')} @@ -46,7 +46,7 @@ export const Properties = () => { {t('right_menu.content')} - + {formId ? : t('right_menu.content_empty')} @@ -54,7 +54,7 @@ export const Properties = () => { {t('right_menu.dynamics')} - {formId && } + {formId ? : t('right_menu.content_empty')} diff --git a/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx b/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx new file mode 100644 index 00000000000..1a8b34668a1 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx @@ -0,0 +1,120 @@ +import React from 'react'; +import { Text } from './Text'; +import { act, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { textMock } from '../../../../../testing/mocks/i18nMock'; +import { FormContext } from '../../containers/FormContext'; +import { + component1IdMock, + component1Mock, + container1IdMock, + layoutMock, +} from '../../testing/layoutMock'; +import type { IAppDataState } from '../../features/appData/appDataReducers'; +import type { ITextResourcesState } from '../../features/appData/textResources/textResourcesSlice'; +import { renderWithMockStore, renderHookWithMockStore } from '../../testing/mocks'; +import { appDataMock, textResourcesMock } from '../../testing/stateMocks'; +import { formContextProviderMock } from '../../testing/formContextMocks'; +import { useLayoutSchemaQuery } from '../../hooks/queries/useLayoutSchemaQuery'; + +const user = userEvent.setup(); + +// Test data: +const textResourceEditTestId = 'text-resource-edit'; + +// Mocks: +jest.mock('../TextResourceEdit', () => ({ + TextResourceEdit: () =>
, +})); + +describe('TextTab', () => { + afterEach(jest.clearAllMocks); + + describe('when editing a text resource', () => { + it('should render the component', async () => { + await render({ props: {}, editId: 'test' }); + expect(screen.getByTestId(textResourceEditTestId)).toBeInTheDocument(); + }); + }); + + describe('when editing a container', () => { + const props = { + formId: container1IdMock, + form: { ...layoutMock.containers[container1IdMock], id: 'id' }, + }; + + it('should render the component', async () => { + await render({ props }); + expect( + screen.getByText(textMock('ux_editor.modal_properties_group_change_id')), + ).toBeInTheDocument(); + }); + + it('should auto-save when updating a field', async () => { + await render({ props }); + + const idInput = screen.getByLabelText(textMock('ux_editor.modal_properties_group_change_id')); + await act(() => user.type(idInput, 'test')); + + expect(formContextProviderMock.handleUpdate).toHaveBeenCalledTimes(4); + expect(formContextProviderMock.debounceSave).toHaveBeenCalledTimes(4); + }); + }); + + describe('when editing a component', () => { + const props = { + formId: component1IdMock, + form: { ...component1Mock, dataModelBindings: {} }, + }; + + it('should render the component', async () => { + jest.spyOn(console, 'error').mockImplementation(); // Silence error from Select component + await render({ props }); + expect( + screen.getByText(textMock('ux_editor.modal_properties_component_change_id')), + ).toBeInTheDocument(); + }); + + it('should auto-save when updating a field', async () => { + await render({ props }); + + const idInput = screen.getByLabelText( + textMock('ux_editor.modal_properties_component_change_id'), + ); + await act(() => user.type(idInput, 'test')); + + expect(formContextProviderMock.handleUpdate).toHaveBeenCalledTimes(4); + expect(formContextProviderMock.debounceSave).toHaveBeenCalledTimes(4); + }); + }); +}); + +const waitForData = async () => { + const layoutSchemaResult = renderHookWithMockStore()(() => useLayoutSchemaQuery()) + .renderHookResult.result; + await waitFor(() => expect(layoutSchemaResult.current[0].isSuccess).toBe(true)); +}; + +const render = async ({ props = {}, editId }: { props: Partial; editId?: string }) => { + const textResources: ITextResourcesState = { + ...textResourcesMock, + currentEditId: editId, + }; + const appData: IAppDataState = { + ...appDataMock, + textResources, + }; + + await waitForData(); + + return renderWithMockStore({ appData })( + + + , + ); +}; diff --git a/frontend/packages/ux-editor/src/components/Properties/Text.tsx b/frontend/packages/ux-editor/src/components/Properties/Text.tsx index 60457daa09f..eb7d4b55be1 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Text.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Text.tsx @@ -3,46 +3,46 @@ import { useSelector } from 'react-redux'; import { useFormContext } from '../../containers/FormContext'; import { useTranslation } from 'react-i18next'; import { Heading } from '@digdir/design-system-react'; -import {EditTextResourceBindings} from "../config/editModal/EditTextResourceBindings"; -import {useLayoutSchemaQuery} from "../../hooks/queries/useLayoutSchemaQuery"; -import {useComponentSchemaQuery} from "../../hooks/queries/useComponentSchemaQuery"; -import {selectedLayoutNameSelector} from "../../selectors/formLayoutSelectors"; +import { EditTextResourceBindings } from '../config/editModal/EditTextResourceBindings'; +import { useLayoutSchemaQuery } from '../../hooks/queries/useLayoutSchemaQuery'; +import { useComponentSchemaQuery } from '../../hooks/queries/useComponentSchemaQuery'; +import { selectedLayoutNameSelector } from '../../selectors/formLayoutSelectors'; import { StudioSpinner } from '@studio/components'; -import {getCurrentEditId} from "../../selectors/textResourceSelectors"; -import {TextResourceEdit} from "../TextResourceEdit"; +import { getCurrentEditId } from '../../selectors/textResourceSelectors'; +import { TextResourceEdit } from '../TextResourceEdit'; export const Text = () => { - const { formId, form, handleUpdate, debounceSave } = useFormContext(); - const { t } = useTranslation(); - - useLayoutSchemaQuery(); // Ensure we load the layout schemas so that component schemas can be loaded - const { data: schema, isPending } = useComponentSchemaQuery(form.type); - const selectedLayout = useSelector(selectedLayoutNameSelector); - const editId = useSelector(getCurrentEditId); + const { formId, form, handleUpdate, debounceSave } = useFormContext(); + const { t } = useTranslation(); - if (editId) return ; - if (!schema?.properties) return null; + useLayoutSchemaQuery(); // Ensure we load the layout schemas so that component schemas can be loaded + const { data: schema, isPending } = useComponentSchemaQuery(form.type); + const selectedLayout = useSelector(selectedLayoutNameSelector); + const editId = useSelector(getCurrentEditId); - return ( + if (editId) return ; + if (!schema?.properties) return null; + + return ( + <> + {isPending && } + {schema.properties.textResourceBindings?.properties && !isPending && ( <> - {isPending && } - {schema.properties.textResourceBindings?.properties && !isPending && ( - <> - - {t('general.text')} - - { - handleUpdate(updatedComponent); - debounceSave(formId, updatedComponent); - }} - textResourceBindingKeys={Object.keys(schema.properties.textResourceBindings.properties)} - editFormId={formId} - layoutName={selectedLayout} - /> - - )} + + {t('general.text')} + + { + handleUpdate(updatedComponent); + debounceSave(formId, updatedComponent); + }} + textResourceBindingKeys={Object.keys(schema.properties.textResourceBindings.properties)} + editFormId={formId} + layoutName={selectedLayout} + /> - ); + )} + + ); }; diff --git a/frontend/packages/ux-editor/src/components/config/EditFormComponent.test.tsx b/frontend/packages/ux-editor/src/components/config/EditFormComponent.test.tsx index 969a11680a1..c238c7800f3 100644 --- a/frontend/packages/ux-editor/src/components/config/EditFormComponent.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/EditFormComponent.test.tsx @@ -226,7 +226,6 @@ const waitForData = async () => { { getDatamodelMetadata }, )(() => useDatamodelMetadataQuery('test-org', 'test-app')).renderHookResult.result; await waitFor(() => expect(dataModelMetadataResult.current.isSuccess).toBe(true)); - await waitFor(() => expect(layoutSchemaResult.current[0].isSuccess).toBe(true)); }; const render = async ({ diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBinding.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBinding.tsx index 1364971b68c..522961cd8dc 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBinding.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBinding.tsx @@ -4,7 +4,7 @@ import { TextResource } from '../../TextResource'; import type { TranslationKey } from 'language/type'; import type { IAppState } from '../../../types/global'; import { useTranslation } from 'react-i18next'; -import {EditTextResourceBindingsProps} from "./EditTextResourceBindings"; +import type { EditTextResourceBindingsProps } from './EditTextResourceBindings'; export interface EditTextResourceBindingProps extends EditTextResourceBindingsProps { textKey: string; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx index 1141cc657d3..cbe0b9297ac 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx @@ -4,10 +4,8 @@ import classes from './EditTextResourceBindings.module.css'; import type { TranslationKey } from 'language/type'; import { useTranslation } from 'react-i18next'; import { LegacySelect } from '@digdir/design-system-react'; -import {FormContainer} from "../../../types/FormContainer"; -import {FormComponent} from "../../../types/FormComponent"; - -export type TextResourceBindingKey = 'description' | 'title' | 'help' | 'body'; +import type { FormContainer } from '../../../types/FormContainer'; +import type { FormComponent } from '../../../types/FormComponent'; export interface EditTextResourceBindingsProps { editFormId?: string; From 1551eed276aa571df381b1a56e196c6c1d5ec0d4 Mon Sep 17 00:00:00 2001 From: Andrea Standeren Date: Thu, 1 Feb 2024 13:33:26 +0100 Subject: [PATCH 04/18] Render all available textResourceBindings from schema by default --- .../src/components/Properties/Text.test.tsx | 117 ++++++++++++------ .../src/components/Properties/Text.tsx | 8 +- .../src/components/TextResource.module.css | 9 +- .../ux-editor/src/components/TextResource.tsx | 14 +-- .../components/config/FormComponentConfig.tsx | 1 + .../editModal/EditTextResourceBindings.tsx | 38 +----- 6 files changed, 100 insertions(+), 87 deletions(-) diff --git a/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx b/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx index 1a8b34668a1..b403dee52d8 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Text } from './Text'; -import { act, screen, waitFor } from '@testing-library/react'; +import { act, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { textMock } from '../../../../../testing/mocks/i18nMock'; import { FormContext } from '../../containers/FormContext'; @@ -12,52 +12,82 @@ import { } from '../../testing/layoutMock'; import type { IAppDataState } from '../../features/appData/appDataReducers'; import type { ITextResourcesState } from '../../features/appData/textResources/textResourcesSlice'; -import { renderWithMockStore, renderHookWithMockStore } from '../../testing/mocks'; +import {renderWithMockStore } from '../../testing/mocks'; import { appDataMock, textResourcesMock } from '../../testing/stateMocks'; import { formContextProviderMock } from '../../testing/formContextMocks'; -import { useLayoutSchemaQuery } from '../../hooks/queries/useLayoutSchemaQuery'; +import {QueryKey} from "app-shared/types/QueryKey"; +import {queryClientMock} from "app-shared/mocks/queryClientMock"; +import {componentSchemaMocks} from "../../testing/componentSchemaMocks"; +import type {ITextResource, ITextResources} from "app-shared/types/global"; +import {DEFAULT_LANGUAGE} from "app-shared/constants"; const user = userEvent.setup(); // Test data: -const textResourceEditTestId = 'text-resource-edit'; +const org = 'org'; +const app = 'app'; +const labelTextId = 'labelTextId'; +const addButtonTextId = 'customAddButtonTextId'; +const labelTextValue = 'Label for group'; +const addButtonTextValue = 'Custom text for add button for group'; +const titleTextResource1: ITextResource = { + id: labelTextId, + value: labelTextValue, +}; +const titleTextResource2: ITextResource = { + id: addButtonTextId, + value: addButtonTextValue, +}; +const textResources: ITextResources = { + [DEFAULT_LANGUAGE]: [titleTextResource1, titleTextResource2], +}; -// Mocks: -jest.mock('../TextResourceEdit', () => ({ - TextResourceEdit: () =>
, -})); +const textResourceBindingsPropertiesForComponentType = (componentType: string) => Object.keys(componentSchemaMocks[componentType].properties.textResourceBindings.properties); describe('TextTab', () => { afterEach(jest.clearAllMocks); - describe('when editing a text resource', () => { - it('should render the component', async () => { - await render({ props: {}, editId: 'test' }); - expect(screen.getByTestId(textResourceEditTestId)).toBeInTheDocument(); - }); - }); - describe('when editing a container', () => { const props = { formId: container1IdMock, - form: { ...layoutMock.containers[container1IdMock], id: 'id' }, + form: { ...layoutMock.containers[container1IdMock]}, }; it('should render the component', async () => { await render({ props }); expect( - screen.getByText(textMock('ux_editor.modal_properties_group_change_id')), + screen.getByRole('heading', {name: textMock('general.text')}), ).toBeInTheDocument(); }); - - it('should auto-save when updating a field', async () => { + + it('should render all available textResourceBinding properties for the group component', async () => { await render({ props }); + (textResourceBindingsPropertiesForComponentType(props.form.type)).forEach(trbProperty => { + // INVESTIGATE WHY IT DOES NOT WORK WITHOUT MOCKED TEXT + const textResourcePropertyLabel = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty})]`; + expect( + screen.getByText(textMock(textResourcePropertyLabel)), + ).toBeInTheDocument() + const textResourcePropertyPlaceHolder = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty}_add)]`; + expect( + screen.getByText(textMock(textResourcePropertyPlaceHolder)), + ).toBeInTheDocument() + } + ); + }); - const idInput = screen.getByLabelText(textMock('ux_editor.modal_properties_group_change_id')); - await act(() => user.type(idInput, 'test')); + it('should render already defined textResourceBinding properties for the group component when exist', async () => { + await render({ props: {...props, form: { ...layoutMock.containers[container1IdMock], textResourceBindings: { 'title': labelTextId, 'add_button': addButtonTextId} }}}); + expect(screen.getByText(labelTextValue)).toBeInTheDocument(); + expect(screen.getByText(addButtonTextValue)).toBeInTheDocument(); + }); - expect(formContextProviderMock.handleUpdate).toHaveBeenCalledTimes(4); - expect(formContextProviderMock.debounceSave).toHaveBeenCalledTimes(4); + it('should render editable field in nb when a text is in editMode', async () => { + await render({ props: {...props, form: { ...layoutMock.containers[container1IdMock], textResourceBindings: { 'title': labelTextId, 'add_button': addButtonTextId} }}, editId: labelTextId}); + + expect(screen.getByText(textMock('ux_editor.edit_text_resource'))).toBeInTheDocument(); + const labelTextField = screen.getByRole('textbox', { name: textMock('language.nb') }); + expect(labelTextField).toBeInTheDocument(); }); }); @@ -68,13 +98,36 @@ describe('TextTab', () => { }; it('should render the component', async () => { - jest.spyOn(console, 'error').mockImplementation(); // Silence error from Select component await render({ props }); expect( - screen.getByText(textMock('ux_editor.modal_properties_component_change_id')), + screen.getByRole('heading', {name: textMock('general.text')}), ).toBeInTheDocument(); }); + it('should render all available textResourceBinding properties for the input component', async () => { + await render({ props }); + (textResourceBindingsPropertiesForComponentType(props.form.type)).forEach(trbProperty => { + // INVESTIGATE WHY IT DOES NOT WORK WITHOUT MOCKED TEXT + const textResourcePropertyLabel = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty})]`; + expect( + screen.getByText(textMock(textResourcePropertyLabel)), + ).toBeInTheDocument() + const textResourcePropertyPlaceHolder = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty}_add)]`; + expect( + screen.getByText(textMock(textResourcePropertyPlaceHolder)), + ).toBeInTheDocument() + } + ); + }); + + it('should render editable field in nb when a text is in editMode', async () => { + await render({ props: {...props, form: { ...layoutMock.components[component1IdMock], textResourceBindings: { 'title': labelTextId, 'add_button': addButtonTextId} }}, editId: labelTextId}); + + expect(screen.getByText(textMock('ux_editor.edit_text_resource'))).toBeInTheDocument(); + const labelTextField = screen.getByRole('textbox', { name: textMock('language.nb') }); + expect(labelTextField).toBeInTheDocument(); + }); + it('should auto-save when updating a field', async () => { await render({ props }); @@ -89,24 +142,18 @@ describe('TextTab', () => { }); }); -const waitForData = async () => { - const layoutSchemaResult = renderHookWithMockStore()(() => useLayoutSchemaQuery()) - .renderHookResult.result; - await waitFor(() => expect(layoutSchemaResult.current[0].isSuccess).toBe(true)); -}; - const render = async ({ props = {}, editId }: { props: Partial; editId?: string }) => { - const textResources: ITextResourcesState = { + queryClientMock.setQueryData([QueryKey.FormComponent, props.form.type], componentSchemaMocks[props.form.type]); + queryClientMock.setQueryData([QueryKey.TextResources, org, app], textResources); + const textResourcesState: ITextResourcesState = { ...textResourcesMock, currentEditId: editId, }; const appData: IAppDataState = { ...appDataMock, - textResources, + textResources: textResourcesState, }; - await waitForData(); - return renderWithMockStore({ appData })( { const { t } = useTranslation(); useLayoutSchemaQuery(); // Ensure we load the layout schemas so that component schemas can be loaded - const { data: schema, isPending } = useComponentSchemaQuery(form.type); + const { data: schema } = useComponentSchemaQuery(form.type); const selectedLayout = useSelector(selectedLayoutNameSelector); const editId = useSelector(getCurrentEditId); if (editId) return ; if (!schema?.properties) return null; - + return ( <> - {isPending && } - {schema.properties.textResourceBindings?.properties && !isPending && ( + {!schema && } + {schema.properties.textResourceBindings?.properties && ( <> {t('general.text')} diff --git a/frontend/packages/ux-editor/src/components/TextResource.module.css b/frontend/packages/ux-editor/src/components/TextResource.module.css index 110d7dc5e36..bcfcdb0a37c 100644 --- a/frontend/packages/ux-editor/src/components/TextResource.module.css +++ b/frontend/packages/ux-editor/src/components/TextResource.module.css @@ -1,9 +1,12 @@ .root { --frame-offset: 4px; - border-radius: 2px; + border-radius: 1rem; + padding: 1rem; display: flex; flex-direction: column; - gap: 1rem; + gap: 4px; + margin-top: 8px; + background-color: white; } .root.isEditing { @@ -35,7 +38,7 @@ } .textResource { - align-items: stretch; + align-items: center; display: inline-flex; } diff --git a/frontend/packages/ux-editor/src/components/TextResource.tsx b/frontend/packages/ux-editor/src/components/TextResource.tsx index 2cff0882d5d..4c92ebec24f 100644 --- a/frontend/packages/ux-editor/src/components/TextResource.tsx +++ b/frontend/packages/ux-editor/src/components/TextResource.tsx @@ -24,7 +24,6 @@ import { prepend } from 'app-shared/utils/arrayUtils'; import cn from 'classnames'; import type { ITextResource } from 'app-shared/types/global'; import { useTextResourcesSelector } from '../hooks'; -import { FormField } from './FormField'; import { AltinnConfirmDialog } from 'app-shared/components/AltinnConfirmDialog'; import { useTranslation } from 'react-i18next'; import { shouldDisplayFeature } from 'app-shared/utils/featureToggleUtils'; @@ -60,7 +59,6 @@ export const TextResource = ({ handleRemoveTextResource, label, placeholder, - previewMode, textResourceId, generateIdOptions, }: TextResourceProps) => { @@ -109,7 +107,6 @@ export const TextResource = ({ ); - return previewMode ? ( - renderTextResource() - ) : ( - renderTextResource()} - /> - ); + return renderTextResource(); }; export interface TextResourceOptionProps { diff --git a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx index 03535e19315..ce3777c2d5a 100644 --- a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx +++ b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx @@ -38,6 +38,7 @@ export const FormComponentConfig = ({ required, readOnly, id, + textResourceBindings, type, options, optionsId, diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx index cbe0b9297ac..1f0cf89c190 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx @@ -1,9 +1,7 @@ -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { EditTextResourceBinding } from './EditTextResourceBinding'; import classes from './EditTextResourceBindings.module.css'; -import type { TranslationKey } from 'language/type'; import { useTranslation } from 'react-i18next'; -import { LegacySelect } from '@digdir/design-system-react'; import type { FormContainer } from '../../../types/FormContainer'; import type { FormComponent } from '../../../types/FormComponent'; @@ -22,56 +20,32 @@ export const EditTextResourceBindings = ({ }: EditTextResourceBindingsProps) => { const { t } = useTranslation(); - const [keysSet, setKeysSet] = React.useState(Object.keys(component.textResourceBindings || {})); - - debugger; + const [keysSet, setKeysSet] = useState(Object.keys( textResourceBindingKeys || component.textResourceBindings || {})); + const keysToAdd = useMemo( () => textResourceBindingKeys.filter((key) => !keysSet.includes(key)), [keysSet, textResourceBindingKeys], ); - const handleAddKey = (key: string) => { - setKeysSet([...keysSet, key]); - handleComponentChange({ - ...component, - textResourceBindings: { - ...component.textResourceBindings, - [key]: '', - }, - }); - }; - const handleRemoveKey = (key: string) => { setKeysSet((prevKeysSet) => prevKeysSet.filter((k) => k !== key)); }; return (
- {keysSet.map((key: string) => ( + {keysToAdd.map((key: string) => ( handleRemoveKey(key)} textKey={key} - labelKey={`ux_editor.modal_properties_textResourceBindings_${key}` as TranslationKey} + labelKey={t(`ux_editor.modal_properties_textResourceBindings_${key}`)} placeholderKey={ - `ux_editor.modal_properties_textResourceBindings_${key}_add` as TranslationKey + t(`ux_editor.modal_properties_textResourceBindings_${key}_add`) } /> ))} - {keysToAdd.length > 0 && ( -
- ({ - label: t(`ux_editor.modal_properties_textResourceBindings_${key}`), - value: key, - }))} - onChange={(value) => handleAddKey(value)} - label={t('ux_editor.text_resource_bindings.add_label')} - /> -
- )}
); }; From ada091a4f8021adc9d8f7256a11f02ead05679b1 Mon Sep 17 00:00:00 2001 From: Andrea Standeren Date: Fri, 2 Feb 2024 14:41:20 +0100 Subject: [PATCH 05/18] Adapt container editing limitations information --- frontend/language/src/nb.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 5579143246e..ec2fd86ba9b 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -1475,7 +1475,7 @@ "ux_editor.component_unknown": "Ukjent komponent", "ux_editor.conditional_rendering_connection_header": "Betingede renderingstilkoblinger", "ux_editor.container_empty": "Tomt, dra noe inn her...", - "ux_editor.container_not_editable_info": "Noen egenskaper for denne komponenten er ikke redigerbare for øyeblikket. Du kan legge til underkomponenter i kolonnen til venstre.", + "ux_editor.container_not_editable_info": "Noen egenskaper for denne komponenten er ikke redigerbare for øyeblikket. Du kan legge til underkomponenter i kolonnen til venstre og redigere tekster.", "ux_editor.edit_component.show_beta_func": "Vis ny konfigurasjon (BETA)", "ux_editor.edit_component.show_beta_func_help_text": "Vi jobber med å få på plass støtte for å redigere alle innstillinger. Ved å huke av her kan du ta i bruk den nye konfigurasjonsvisningen, som støtter flere innstillinger. Merk at denne visningen fortsatt er under utvikling, og vil kunne oppleves som noe ustabil.", "ux_editor.edit_component.unknown_component": "Komponenten {{componentName}} gjenkjennes ikke av Studio og kan derfor ikke konfigureres.", From 090215408fdf831be9eca040d9282810f09ffc34 Mon Sep 17 00:00:00 2001 From: Andrea Standeren Date: Fri, 2 Feb 2024 15:08:19 +0100 Subject: [PATCH 06/18] Update tests --- .../components/Properties/Content.test.tsx | 15 - .../src/components/Properties/Content.tsx | 9 - .../components/Properties/Properties.test.tsx | 8 + .../src/components/Properties/Properties.tsx | 15 +- .../src/components/Properties/Text.test.tsx | 188 ++++++++++++ .../src/components/Properties/Text.tsx | 48 +++ .../src/components/TextResource.module.css | 9 +- .../src/components/TextResource.tsx | 14 +- .../config/EditFormComponent.test.tsx | 1 - .../components/config/EditFormContainer.tsx | 17 -- .../components/config/FormComponentConfig.tsx | 20 +- .../editModal/EditTextResourceBinding.tsx | 4 +- .../editModal/EditTextResourceBindings.tsx | 54 +--- .../hooks/queries/useComponentSchemaQuery.ts | 2 +- .../src/testing/componentSchemaMocks.ts | 5 +- .../src/components/Properties/Properties.tsx | 4 +- .../src/components/Properties/Text.test.tsx | 289 ++++++++++-------- .../src/components/Properties/Text.tsx | 2 +- .../components/config/EditFormContainer.tsx | 2 +- .../editModal/EditTextResourceBindings.tsx | 10 +- 20 files changed, 449 insertions(+), 267 deletions(-) create mode 100644 frontend/packages/ux-editor-v3/src/components/Properties/Text.test.tsx create mode 100644 frontend/packages/ux-editor-v3/src/components/Properties/Text.tsx diff --git a/frontend/packages/ux-editor-v3/src/components/Properties/Content.test.tsx b/frontend/packages/ux-editor-v3/src/components/Properties/Content.test.tsx index aabed980cfd..3122eb2fb69 100644 --- a/frontend/packages/ux-editor-v3/src/components/Properties/Content.test.tsx +++ b/frontend/packages/ux-editor-v3/src/components/Properties/Content.test.tsx @@ -19,24 +19,9 @@ import { useLayoutSchemaQuery } from '../../hooks/queries/useLayoutSchemaQuery'; const user = userEvent.setup(); -// Test data: -const textResourceEditTestId = 'text-resource-edit'; - -// Mocks: -jest.mock('../TextResourceEdit', () => ({ - TextResourceEdit: () =>
, -})); - describe('ContentTab', () => { afterEach(jest.clearAllMocks); - describe('when editing a text resource', () => { - it('should render the component', async () => { - await render({ props: {}, editId: 'test' }); - expect(screen.getByTestId(textResourceEditTestId)).toBeInTheDocument(); - }); - }); - describe('when editing a container', () => { const props = { formId: container1IdMock, diff --git a/frontend/packages/ux-editor-v3/src/components/Properties/Content.tsx b/frontend/packages/ux-editor-v3/src/components/Properties/Content.tsx index 8a287f5baab..1825b36eb15 100644 --- a/frontend/packages/ux-editor-v3/src/components/Properties/Content.tsx +++ b/frontend/packages/ux-editor-v3/src/components/Properties/Content.tsx @@ -1,20 +1,11 @@ import React from 'react'; -import { TextResourceEdit } from '../TextResourceEdit'; import { EditFormComponent } from '../config/EditFormComponent'; import { EditFormContainer } from '../config/EditFormContainer'; -import { getCurrentEditId } from '../../selectors/textResourceSelectors'; -import { useSelector } from 'react-redux'; import { useFormContext } from '../../containers/FormContext'; -import { useTranslation } from 'react-i18next'; import { isContainer } from '../../utils/formItemUtils'; export const Content = () => { const { formId, form, handleUpdate, debounceSave } = useFormContext(); - const editId = useSelector(getCurrentEditId); - const { t } = useTranslation(); - - if (editId) return ; - if (!formId || !form) return t('right_menu.content_empty'); return isContainer(form) ? ( ({ + Text: () =>
, +})); jest.mock('./Content', () => ({ Content: () =>
, })); @@ -109,9 +115,11 @@ describe('Properties', () => { it('Renders accordion', () => { const formIdMock = 'test-id'; render({ formId: formIdMock }); + expect(screen.getByText(textText)).toBeInTheDocument(); expect(screen.getByText(contentText)).toBeInTheDocument(); expect(screen.getByText(dynamicsText)).toBeInTheDocument(); expect(screen.getByText(calculationsText)).toBeInTheDocument(); + expect(screen.getByTestId(textTestId)).toBeInTheDocument(); expect(screen.getByTestId(contentTestId)).toBeInTheDocument(); expect(screen.getByTestId(expressionsTestId)).toBeInTheDocument(); expect(screen.getByTestId(calculationsTestId)).toBeInTheDocument(); diff --git a/frontend/packages/ux-editor-v3/src/components/Properties/Properties.tsx b/frontend/packages/ux-editor-v3/src/components/Properties/Properties.tsx index 5879dff6ebd..6531019b17d 100644 --- a/frontend/packages/ux-editor-v3/src/components/Properties/Properties.tsx +++ b/frontend/packages/ux-editor-v3/src/components/Properties/Properties.tsx @@ -1,6 +1,7 @@ import React, { useEffect } from 'react'; import { Calculations } from './Calculations'; import { Content } from './Content'; +import { Text } from './Text'; import { useTranslation } from 'react-i18next'; import { Accordion } from '@digdir/design-system-react'; import { useFormContext } from '../../containers/FormContext'; @@ -17,7 +18,7 @@ export const Properties = () => { useEffect(() => { if (formIdRef.current !== formId) { formIdRef.current = formId; - if (formId && openList.length === 0) setOpenList(['content']); + if (formId && openList.length === 0) setOpenList(['text']); } }, [formId, openList.length]); @@ -32,19 +33,27 @@ export const Properties = () => { return (
+ + toggleOpen('text')}> + {t('right_menu.text')} + + {formId ? : t('right_menu.content_empty')} + toggleOpen('content')}> {t('right_menu.content')} - + {formId ? : t('right_menu.content_empty')} toggleOpen('dynamics')}> {t('right_menu.dynamics')} - {formId && } + + {formId ? : t('right_menu.content_empty')} + toggleOpen('calculations')}> diff --git a/frontend/packages/ux-editor-v3/src/components/Properties/Text.test.tsx b/frontend/packages/ux-editor-v3/src/components/Properties/Text.test.tsx new file mode 100644 index 00000000000..4a475f860bf --- /dev/null +++ b/frontend/packages/ux-editor-v3/src/components/Properties/Text.test.tsx @@ -0,0 +1,188 @@ +import React from 'react'; +import { Text } from './Text'; +import { screen } from '@testing-library/react'; +import { textMock } from '../../../../../testing/mocks/i18nMock'; +import { FormContext } from '../../containers/FormContext'; +import { + component1IdMock, + component1Mock, + container1IdMock, + layoutMock, +} from '../../testing/layoutMock'; +import type { IAppDataState } from '../../features/appData/appDataReducers'; +import type { ITextResourcesState } from '../../features/appData/textResources/textResourcesSlice'; +import { renderWithMockStore } from '../../testing/mocks'; +import { appDataMock, textResourcesMock } from '../../testing/stateMocks'; +import { formContextProviderMock } from '../../testing/formContextMocks'; +import { QueryKey } from 'app-shared/types/QueryKey'; +import { queryClientMock } from 'app-shared/mocks/queryClientMock'; +import { componentSchemaMocks } from '../../testing/componentSchemaMocks'; +import type { ITextResource, ITextResources } from 'app-shared/types/global'; +import { DEFAULT_LANGUAGE } from 'app-shared/constants'; + +// Test data: +const org = 'org'; +const app = 'app'; +const labelTextId = 'labelTextId'; +const descriptionTextId = 'descriptionTextId'; +const addButtonTextId = 'customAddButtonTextId'; +const labelTextValue = 'Label for component'; +const descriptionTextValue = 'Description for component'; +const addButtonTextValue = 'Custom text for add button for group'; +const titleTextResource1: ITextResource = { + id: labelTextId, + value: labelTextValue, +}; +const titleTextResource2: ITextResource = { + id: addButtonTextId, + value: addButtonTextValue, +}; +const titleTextResource3: ITextResource = { + id: descriptionTextId, + value: descriptionTextValue, +}; +const textResources: ITextResources = { + [DEFAULT_LANGUAGE]: [titleTextResource1, titleTextResource2, titleTextResource3], +}; + +const textResourceBindingsPropertiesForComponentType = (componentType: string) => + Object.keys(componentSchemaMocks[componentType].properties.textResourceBindings.properties); + +describe('TextTab', () => { + afterEach(jest.clearAllMocks); + + describe('when editing a container', () => { + const props = { + formId: container1IdMock, + form: { ...layoutMock.containers[container1IdMock] }, + }; + + it('should render the component', async () => { + await render({ props }); + expect(screen.getByRole('heading', { name: textMock('general.text') })).toBeInTheDocument(); + }); + + it('should render all available textResourceBinding properties for the group component', async () => { + await render({ props }); + textResourceBindingsPropertiesForComponentType(props.form.type).forEach((trbProperty) => { + // INVESTIGATE WHY IT DOES NOT WORK WITHOUT MOCKED TEXT + const textResourcePropertyLabel = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty})]`; + expect(screen.getByText(textMock(textResourcePropertyLabel))).toBeInTheDocument(); + const textResourcePropertyPlaceHolder = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty}_add)]`; + expect(screen.getByText(textMock(textResourcePropertyPlaceHolder))).toBeInTheDocument(); + }); + }); + + it('should render already defined textResourceBinding properties for the group component when exist', async () => { + await render({ + props: { + ...props, + form: { + ...layoutMock.containers[container1IdMock], + textResourceBindings: { title: labelTextId, add_button: addButtonTextId }, + }, + }, + }); + expect(screen.getByText(labelTextValue)).toBeInTheDocument(); + expect(screen.getByText(addButtonTextValue)).toBeInTheDocument(); + }); + + it('should render editable field in nb when a text is in editMode', async () => { + await render({ + props: { + ...props, + form: { + ...layoutMock.containers[container1IdMock], + textResourceBindings: { title: labelTextId, add_button: addButtonTextId }, + }, + }, + editId: labelTextId, + }); + + expect(screen.getByText(textMock('ux_editor.edit_text_resource'))).toBeInTheDocument(); + const labelTextField = screen.getByRole('textbox', { name: textMock('language.nb') }); + expect(labelTextField).toBeInTheDocument(); + }); + }); + + describe('when editing a component', () => { + const props = { + formId: component1IdMock, + form: { ...component1Mock, dataModelBindings: {} }, + }; + + it('should render the component', async () => { + await render({ props }); + expect(screen.getByRole('heading', { name: textMock('general.text') })).toBeInTheDocument(); + }); + + it('should render all available textResourceBinding properties for the input component', async () => { + await render({ props }); + textResourceBindingsPropertiesForComponentType(props.form.type).forEach((trbProperty) => { + // INVESTIGATE WHY IT DOES NOT WORK WITHOUT MOCKED TEXT + const textResourcePropertyLabel = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty})]`; + expect(screen.getByText(textMock(textResourcePropertyLabel))).toBeInTheDocument(); + const textResourcePropertyPlaceHolder = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty}_add)]`; + expect(screen.getByText(textMock(textResourcePropertyPlaceHolder))).toBeInTheDocument(); + }); + }); + + it('should render already defined textResourceBinding properties for the input component when exist', async () => { + await render({ + props: { + ...props, + form: { + ...layoutMock.components[component1IdMock], + textResourceBindings: { title: labelTextId, description: descriptionTextId }, + }, + }, + }); + expect(screen.getByText(labelTextValue)).toBeInTheDocument(); + expect(screen.getByText(descriptionTextValue)).toBeInTheDocument(); + }); + + it('should render editable field in nb when a text is in editMode', async () => { + await render({ + props: { + ...props, + form: { + ...layoutMock.components[component1IdMock], + textResourceBindings: { title: labelTextId, description: descriptionTextId }, + }, + }, + editId: labelTextId, + }); + + expect(screen.getByText(textMock('ux_editor.edit_text_resource'))).toBeInTheDocument(); + const labelTextField = screen.getByRole('textbox', { name: textMock('language.nb') }); + expect(labelTextField).toBeInTheDocument(); + }); + }); +}); + +const render = async ({ props = {}, editId }: { props: Partial; editId?: string }) => { + queryClientMock.setQueryData( + [QueryKey.FormComponent, props.form.type], + componentSchemaMocks[props.form.type], + ); + queryClientMock.setQueryData([QueryKey.TextResources, org, app], textResources); + const textResourcesState: ITextResourcesState = { + ...textResourcesMock, + currentEditId: editId, + }; + const appData: IAppDataState = { + ...appDataMock, + textResources: textResourcesState, + }; + + return renderWithMockStore({ appData })( + + + , + ); +}; diff --git a/frontend/packages/ux-editor-v3/src/components/Properties/Text.tsx b/frontend/packages/ux-editor-v3/src/components/Properties/Text.tsx new file mode 100644 index 00000000000..a5280752118 --- /dev/null +++ b/frontend/packages/ux-editor-v3/src/components/Properties/Text.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { useFormContext } from '../../containers/FormContext'; +import { useTranslation } from 'react-i18next'; +import { Heading } from '@digdir/design-system-react'; +import { EditTextResourceBindings } from '../config/editModal/EditTextResourceBindings'; +import { useLayoutSchemaQuery } from '../../hooks/queries/useLayoutSchemaQuery'; +import { useComponentSchemaQuery } from '../../hooks/queries/useComponentSchemaQuery'; +import { selectedLayoutNameSelector } from '../../selectors/formLayoutSelectors'; +import { StudioSpinner } from '@studio/components'; +import { getCurrentEditId } from '../../selectors/textResourceSelectors'; +import { TextResourceEdit } from '../TextResourceEdit'; + +export const Text = () => { + const { formId, form, handleUpdate, debounceSave } = useFormContext(); + const { t } = useTranslation(); + + useLayoutSchemaQuery(); // Ensure we load the layout schemas so that component schemas can be loaded + const { data: schema } = useComponentSchemaQuery(form.type); + const selectedLayout = useSelector(selectedLayoutNameSelector); + const editId = useSelector(getCurrentEditId); + + if (editId) return ; + if (!schema?.properties) return null; + + return ( + <> + {!schema && } + {schema.properties.textResourceBindings?.properties && ( + <> + + {t('general.text')} + + { + handleUpdate(updatedComponent); + debounceSave(formId, updatedComponent); + }} + textResourceBindingKeys={Object.keys(schema.properties.textResourceBindings.properties)} + editFormId={formId} + layoutName={selectedLayout} + /> + + )} + + ); +}; diff --git a/frontend/packages/ux-editor-v3/src/components/TextResource.module.css b/frontend/packages/ux-editor-v3/src/components/TextResource.module.css index 110d7dc5e36..bcfcdb0a37c 100644 --- a/frontend/packages/ux-editor-v3/src/components/TextResource.module.css +++ b/frontend/packages/ux-editor-v3/src/components/TextResource.module.css @@ -1,9 +1,12 @@ .root { --frame-offset: 4px; - border-radius: 2px; + border-radius: 1rem; + padding: 1rem; display: flex; flex-direction: column; - gap: 1rem; + gap: 4px; + margin-top: 8px; + background-color: white; } .root.isEditing { @@ -35,7 +38,7 @@ } .textResource { - align-items: stretch; + align-items: center; display: inline-flex; } diff --git a/frontend/packages/ux-editor-v3/src/components/TextResource.tsx b/frontend/packages/ux-editor-v3/src/components/TextResource.tsx index 2cff0882d5d..4c92ebec24f 100644 --- a/frontend/packages/ux-editor-v3/src/components/TextResource.tsx +++ b/frontend/packages/ux-editor-v3/src/components/TextResource.tsx @@ -24,7 +24,6 @@ import { prepend } from 'app-shared/utils/arrayUtils'; import cn from 'classnames'; import type { ITextResource } from 'app-shared/types/global'; import { useTextResourcesSelector } from '../hooks'; -import { FormField } from './FormField'; import { AltinnConfirmDialog } from 'app-shared/components/AltinnConfirmDialog'; import { useTranslation } from 'react-i18next'; import { shouldDisplayFeature } from 'app-shared/utils/featureToggleUtils'; @@ -60,7 +59,6 @@ export const TextResource = ({ handleRemoveTextResource, label, placeholder, - previewMode, textResourceId, generateIdOptions, }: TextResourceProps) => { @@ -109,7 +107,6 @@ export const TextResource = ({ ); - return previewMode ? ( - renderTextResource() - ) : ( - renderTextResource()} - /> - ); + return renderTextResource(); }; export interface TextResourceOptionProps { diff --git a/frontend/packages/ux-editor-v3/src/components/config/EditFormComponent.test.tsx b/frontend/packages/ux-editor-v3/src/components/config/EditFormComponent.test.tsx index 969a11680a1..c238c7800f3 100644 --- a/frontend/packages/ux-editor-v3/src/components/config/EditFormComponent.test.tsx +++ b/frontend/packages/ux-editor-v3/src/components/config/EditFormComponent.test.tsx @@ -226,7 +226,6 @@ const waitForData = async () => { { getDatamodelMetadata }, )(() => useDatamodelMetadataQuery('test-org', 'test-app')).renderHookResult.result; await waitFor(() => expect(dataModelMetadataResult.current.isSuccess).toBe(true)); - await waitFor(() => expect(layoutSchemaResult.current[0].isSuccess).toBe(true)); }; const render = async ({ diff --git a/frontend/packages/ux-editor-v3/src/components/config/EditFormContainer.tsx b/frontend/packages/ux-editor-v3/src/components/config/EditFormContainer.tsx index 2b87df6ecdb..467bedd4c43 100644 --- a/frontend/packages/ux-editor-v3/src/components/config/EditFormContainer.tsx +++ b/frontend/packages/ux-editor-v3/src/components/config/EditFormContainer.tsx @@ -14,7 +14,6 @@ import { Paragraph, } from '@digdir/design-system-react'; import classes from './EditFormContainer.module.css'; -import { TextResource } from '../TextResource'; import { useDatamodelMetadataQuery } from '../../hooks/queries/useDatamodelMetadataQuery'; import { useText } from '../../hooks'; import { useSelectedFormLayout, useTextResourcesSelector } from '../../hooks'; @@ -87,16 +86,6 @@ export const EditFormContainer = ({ }); }; - const handleButtonTextChange = (id: string) => { - handleContainerUpdate({ - ...container, - textResourceBindings: { - ...container.textResourceBindings, - add_button: id, - }, - }); - }; - const handleTableHeadersChange = (ids: string[]) => { const updatedContainer = { ...container }; updatedContainer.tableHeaders = [...ids]; @@ -204,12 +193,6 @@ export const EditFormContainer = ({ /> )} /> - {items?.length > 0 && ( { - const selectedLayout = useSelector(selectedLayoutNameSelector); const t = useText(); if (!schema?.properties) return null; const { - textResourceBindings, dataModelBindings, required, readOnly, id, + textResourceBindings, type, options, optionsId, @@ -67,20 +63,6 @@ export const FormComponentConfig = ({ helpText={id.description} /> )} - {textResourceBindings?.properties && ( - <> - - {t('general.text')} - - - - )} {dataModelBindings?.properties && ( <> diff --git a/frontend/packages/ux-editor-v3/src/components/config/editModal/EditTextResourceBinding.tsx b/frontend/packages/ux-editor-v3/src/components/config/editModal/EditTextResourceBinding.tsx index fb71990e25b..522961cd8dc 100644 --- a/frontend/packages/ux-editor-v3/src/components/config/editModal/EditTextResourceBinding.tsx +++ b/frontend/packages/ux-editor-v3/src/components/config/editModal/EditTextResourceBinding.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import type { IGenericEditComponent } from '../componentConfig'; import { TextResource } from '../../TextResource'; import type { TranslationKey } from 'language/type'; import type { IAppState } from '../../../types/global'; import { useTranslation } from 'react-i18next'; +import type { EditTextResourceBindingsProps } from './EditTextResourceBindings'; -export interface EditTextResourceBindingProps extends IGenericEditComponent { +export interface EditTextResourceBindingProps extends EditTextResourceBindingsProps { textKey: string; labelKey: TranslationKey; descriptionKey?: TranslationKey; diff --git a/frontend/packages/ux-editor-v3/src/components/config/editModal/EditTextResourceBindings.tsx b/frontend/packages/ux-editor-v3/src/components/config/editModal/EditTextResourceBindings.tsx index 5a204eb86cf..b5622ad4a0f 100644 --- a/frontend/packages/ux-editor-v3/src/components/config/editModal/EditTextResourceBindings.tsx +++ b/frontend/packages/ux-editor-v3/src/components/config/editModal/EditTextResourceBindings.tsx @@ -1,15 +1,16 @@ -import React, { useMemo } from 'react'; -import type { IGenericEditComponent } from '../componentConfig'; +import React, { useMemo, useState } from 'react'; import { EditTextResourceBinding } from './EditTextResourceBinding'; import classes from './EditTextResourceBindings.module.css'; -import type { TranslationKey } from 'language/type'; import { useTranslation } from 'react-i18next'; -import { LegacySelect } from '@digdir/design-system-react'; - -export type TextResourceBindingKey = 'description' | 'title' | 'help' | 'body'; - -export interface EditTextResourceBindingsProps extends IGenericEditComponent { - textResourceBindingKeys: string[]; +import type { FormContainer } from '../../../types/FormContainer'; +import type { FormComponent } from '../../../types/FormComponent'; + +export interface EditTextResourceBindingsProps { + editFormId?: string; + component: FormComponent | FormContainer; + handleComponentChange: (component: FormComponent | FormContainer) => void; + textResourceBindingKeys?: string[]; + layoutName?: string; } export const EditTextResourceBindings = ({ @@ -19,55 +20,32 @@ export const EditTextResourceBindings = ({ }: EditTextResourceBindingsProps) => { const { t } = useTranslation(); - const [keysSet, setKeysSet] = React.useState(Object.keys(component.textResourceBindings || {})); + const [keysSet, setKeysSet] = useState( + Object.keys(textResourceBindingKeys || component.textResourceBindings || {}), + ); const keysToAdd = useMemo( () => textResourceBindingKeys.filter((key) => !keysSet.includes(key)), [keysSet, textResourceBindingKeys], ); - const handleAddKey = (key: string) => { - setKeysSet([...keysSet, key]); - handleComponentChange({ - ...component, - textResourceBindings: { - ...component.textResourceBindings, - [key]: '', - }, - }); - }; - const handleRemoveKey = (key: string) => { setKeysSet((prevKeysSet) => prevKeysSet.filter((k) => k !== key)); }; return (
- {keysSet.map((key: string) => ( + {keysToAdd.map((key: string) => ( handleRemoveKey(key)} textKey={key} - labelKey={`ux_editor.modal_properties_textResourceBindings_${key}` as TranslationKey} - placeholderKey={ - `ux_editor.modal_properties_textResourceBindings_${key}_add` as TranslationKey - } + labelKey={t(`ux_editor.modal_properties_textResourceBindings_${key}`)} + placeholderKey={t(`ux_editor.modal_properties_textResourceBindings_${key}_add`)} /> ))} - {keysToAdd.length > 0 && ( -
- ({ - label: t(`ux_editor.modal_properties_textResourceBindings_${key}`), - value: key, - }))} - onChange={(value) => handleAddKey(value)} - label={t('ux_editor.text_resource_bindings.add_label')} - /> -
- )}
); }; diff --git a/frontend/packages/ux-editor-v3/src/hooks/queries/useComponentSchemaQuery.ts b/frontend/packages/ux-editor-v3/src/hooks/queries/useComponentSchemaQuery.ts index 81501796a6e..9942d1ebf5d 100644 --- a/frontend/packages/ux-editor-v3/src/hooks/queries/useComponentSchemaQuery.ts +++ b/frontend/packages/ux-editor-v3/src/hooks/queries/useComponentSchemaQuery.ts @@ -13,7 +13,7 @@ export interface UseComponentSchemaQueryResult { // Currently use local mocks rather than fetching from CDN. This is because the CDN schemas are not ready to use, and // we also have made some modifications locally to the schemas. -// When the schemas are available on CDN, we can remove the mocks and use the querys instead. +// When the schemas are available on CDN, we can remove the mocks and use the queries instead. export const useComponentSchemaQuery = (component: string): UseQueryResult => { const queryClient = useQueryClient(); diff --git a/frontend/packages/ux-editor-v3/src/testing/componentSchemaMocks.ts b/frontend/packages/ux-editor-v3/src/testing/componentSchemaMocks.ts index ba5013341af..f7a6053c413 100644 --- a/frontend/packages/ux-editor-v3/src/testing/componentSchemaMocks.ts +++ b/frontend/packages/ux-editor-v3/src/testing/componentSchemaMocks.ts @@ -1,6 +1,6 @@ import AlertSchema from './schemas/json/component/Alert.schema.v1.json'; // TODO: Add schemas for the commented out components (https://github.com/Altinn/altinn-studio/issues/10868): -// import AccordionSchema from './schemas/json/component/Accordion.schema.v1.json'; +import AccordionSchema from './schemas/json/component/Accordion.schema.v1.json'; // import AccordionGroupSchema from './schemas/json/component/AccordionGroup.schema.v1.json'; import ActionButtonSchema from './schemas/json/component/ActionButton.schema.v1.json'; import AddressComponentSchema from './schemas/json/component/AddressComponent.schema.v1.json'; @@ -36,9 +36,10 @@ import SummarySchema from './schemas/json/component/Summary.schema.v1.json'; import TextAreaSchema from './schemas/json/component/TextArea.schema.v1.json'; export const componentSchemaMocks = { - Alert: AlertSchema, + Accordion: AccordionSchema, ActionButton: ActionButtonSchema, AddressComponent: AddressComponentSchema, + Alert: AlertSchema, AttachmentList: AttachmentListSchema, Button: ButtonSchema, ButtonGroup: ButtonGroupSchema, diff --git a/frontend/packages/ux-editor/src/components/Properties/Properties.tsx b/frontend/packages/ux-editor/src/components/Properties/Properties.tsx index d871b3e34f1..6531019b17d 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Properties.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Properties.tsx @@ -37,9 +37,7 @@ export const Properties = () => { toggleOpen('text')}> {t('right_menu.text')} - - {formId ? : t('right_menu.content_empty')} - + {formId ? : t('right_menu.content_empty')}
toggleOpen('content')}> diff --git a/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx b/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx index b403dee52d8..4a475f860bf 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx @@ -1,167 +1,188 @@ import React from 'react'; import { Text } from './Text'; -import { act, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; +import { screen } from '@testing-library/react'; import { textMock } from '../../../../../testing/mocks/i18nMock'; import { FormContext } from '../../containers/FormContext'; import { - component1IdMock, - component1Mock, - container1IdMock, - layoutMock, + component1IdMock, + component1Mock, + container1IdMock, + layoutMock, } from '../../testing/layoutMock'; import type { IAppDataState } from '../../features/appData/appDataReducers'; import type { ITextResourcesState } from '../../features/appData/textResources/textResourcesSlice'; -import {renderWithMockStore } from '../../testing/mocks'; +import { renderWithMockStore } from '../../testing/mocks'; import { appDataMock, textResourcesMock } from '../../testing/stateMocks'; import { formContextProviderMock } from '../../testing/formContextMocks'; -import {QueryKey} from "app-shared/types/QueryKey"; -import {queryClientMock} from "app-shared/mocks/queryClientMock"; -import {componentSchemaMocks} from "../../testing/componentSchemaMocks"; -import type {ITextResource, ITextResources} from "app-shared/types/global"; -import {DEFAULT_LANGUAGE} from "app-shared/constants"; - -const user = userEvent.setup(); +import { QueryKey } from 'app-shared/types/QueryKey'; +import { queryClientMock } from 'app-shared/mocks/queryClientMock'; +import { componentSchemaMocks } from '../../testing/componentSchemaMocks'; +import type { ITextResource, ITextResources } from 'app-shared/types/global'; +import { DEFAULT_LANGUAGE } from 'app-shared/constants'; // Test data: const org = 'org'; const app = 'app'; const labelTextId = 'labelTextId'; +const descriptionTextId = 'descriptionTextId'; const addButtonTextId = 'customAddButtonTextId'; -const labelTextValue = 'Label for group'; +const labelTextValue = 'Label for component'; +const descriptionTextValue = 'Description for component'; const addButtonTextValue = 'Custom text for add button for group'; const titleTextResource1: ITextResource = { - id: labelTextId, - value: labelTextValue, + id: labelTextId, + value: labelTextValue, }; const titleTextResource2: ITextResource = { - id: addButtonTextId, - value: addButtonTextValue, + id: addButtonTextId, + value: addButtonTextValue, +}; +const titleTextResource3: ITextResource = { + id: descriptionTextId, + value: descriptionTextValue, }; const textResources: ITextResources = { - [DEFAULT_LANGUAGE]: [titleTextResource1, titleTextResource2], + [DEFAULT_LANGUAGE]: [titleTextResource1, titleTextResource2, titleTextResource3], }; -const textResourceBindingsPropertiesForComponentType = (componentType: string) => Object.keys(componentSchemaMocks[componentType].properties.textResourceBindings.properties); +const textResourceBindingsPropertiesForComponentType = (componentType: string) => + Object.keys(componentSchemaMocks[componentType].properties.textResourceBindings.properties); describe('TextTab', () => { - afterEach(jest.clearAllMocks); - - describe('when editing a container', () => { - const props = { - formId: container1IdMock, - form: { ...layoutMock.containers[container1IdMock]}, - }; - - it('should render the component', async () => { - await render({ props }); - expect( - screen.getByRole('heading', {name: textMock('general.text')}), - ).toBeInTheDocument(); - }); - - it('should render all available textResourceBinding properties for the group component', async () => { - await render({ props }); - (textResourceBindingsPropertiesForComponentType(props.form.type)).forEach(trbProperty => { - // INVESTIGATE WHY IT DOES NOT WORK WITHOUT MOCKED TEXT - const textResourcePropertyLabel = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty})]`; - expect( - screen.getByText(textMock(textResourcePropertyLabel)), - ).toBeInTheDocument() - const textResourcePropertyPlaceHolder = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty}_add)]`; - expect( - screen.getByText(textMock(textResourcePropertyPlaceHolder)), - ).toBeInTheDocument() - } - ); - }); - - it('should render already defined textResourceBinding properties for the group component when exist', async () => { - await render({ props: {...props, form: { ...layoutMock.containers[container1IdMock], textResourceBindings: { 'title': labelTextId, 'add_button': addButtonTextId} }}}); - expect(screen.getByText(labelTextValue)).toBeInTheDocument(); - expect(screen.getByText(addButtonTextValue)).toBeInTheDocument(); - }); - - it('should render editable field in nb when a text is in editMode', async () => { - await render({ props: {...props, form: { ...layoutMock.containers[container1IdMock], textResourceBindings: { 'title': labelTextId, 'add_button': addButtonTextId} }}, editId: labelTextId}); - - expect(screen.getByText(textMock('ux_editor.edit_text_resource'))).toBeInTheDocument(); - const labelTextField = screen.getByRole('textbox', { name: textMock('language.nb') }); - expect(labelTextField).toBeInTheDocument(); - }); + afterEach(jest.clearAllMocks); + + describe('when editing a container', () => { + const props = { + formId: container1IdMock, + form: { ...layoutMock.containers[container1IdMock] }, + }; + + it('should render the component', async () => { + await render({ props }); + expect(screen.getByRole('heading', { name: textMock('general.text') })).toBeInTheDocument(); }); - describe('when editing a component', () => { - const props = { - formId: component1IdMock, - form: { ...component1Mock, dataModelBindings: {} }, - }; - - it('should render the component', async () => { - await render({ props }); - expect( - screen.getByRole('heading', {name: textMock('general.text')}), - ).toBeInTheDocument(); - }); - - it('should render all available textResourceBinding properties for the input component', async () => { - await render({ props }); - (textResourceBindingsPropertiesForComponentType(props.form.type)).forEach(trbProperty => { - // INVESTIGATE WHY IT DOES NOT WORK WITHOUT MOCKED TEXT - const textResourcePropertyLabel = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty})]`; - expect( - screen.getByText(textMock(textResourcePropertyLabel)), - ).toBeInTheDocument() - const textResourcePropertyPlaceHolder = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty}_add)]`; - expect( - screen.getByText(textMock(textResourcePropertyPlaceHolder)), - ).toBeInTheDocument() - } - ); - }); - - it('should render editable field in nb when a text is in editMode', async () => { - await render({ props: {...props, form: { ...layoutMock.components[component1IdMock], textResourceBindings: { 'title': labelTextId, 'add_button': addButtonTextId} }}, editId: labelTextId}); - - expect(screen.getByText(textMock('ux_editor.edit_text_resource'))).toBeInTheDocument(); - const labelTextField = screen.getByRole('textbox', { name: textMock('language.nb') }); - expect(labelTextField).toBeInTheDocument(); - }); - - it('should auto-save when updating a field', async () => { - await render({ props }); - - const idInput = screen.getByLabelText( - textMock('ux_editor.modal_properties_component_change_id'), - ); - await act(() => user.type(idInput, 'test')); - - expect(formContextProviderMock.handleUpdate).toHaveBeenCalledTimes(4); - expect(formContextProviderMock.debounceSave).toHaveBeenCalledTimes(4); - }); + it('should render all available textResourceBinding properties for the group component', async () => { + await render({ props }); + textResourceBindingsPropertiesForComponentType(props.form.type).forEach((trbProperty) => { + // INVESTIGATE WHY IT DOES NOT WORK WITHOUT MOCKED TEXT + const textResourcePropertyLabel = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty})]`; + expect(screen.getByText(textMock(textResourcePropertyLabel))).toBeInTheDocument(); + const textResourcePropertyPlaceHolder = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty}_add)]`; + expect(screen.getByText(textMock(textResourcePropertyPlaceHolder))).toBeInTheDocument(); + }); }); -}); -const render = async ({ props = {}, editId }: { props: Partial; editId?: string }) => { - queryClientMock.setQueryData([QueryKey.FormComponent, props.form.type], componentSchemaMocks[props.form.type]); - queryClientMock.setQueryData([QueryKey.TextResources, org, app], textResources); - const textResourcesState: ITextResourcesState = { - ...textResourcesMock, - currentEditId: editId, - }; - const appData: IAppDataState = { - ...appDataMock, - textResources: textResourcesState, + it('should render already defined textResourceBinding properties for the group component when exist', async () => { + await render({ + props: { + ...props, + form: { + ...layoutMock.containers[container1IdMock], + textResourceBindings: { title: labelTextId, add_button: addButtonTextId }, + }, + }, + }); + expect(screen.getByText(labelTextValue)).toBeInTheDocument(); + expect(screen.getByText(addButtonTextValue)).toBeInTheDocument(); + }); + + it('should render editable field in nb when a text is in editMode', async () => { + await render({ + props: { + ...props, + form: { + ...layoutMock.containers[container1IdMock], + textResourceBindings: { title: labelTextId, add_button: addButtonTextId }, + }, + }, + editId: labelTextId, + }); + + expect(screen.getByText(textMock('ux_editor.edit_text_resource'))).toBeInTheDocument(); + const labelTextField = screen.getByRole('textbox', { name: textMock('language.nb') }); + expect(labelTextField).toBeInTheDocument(); + }); + }); + + describe('when editing a component', () => { + const props = { + formId: component1IdMock, + form: { ...component1Mock, dataModelBindings: {} }, }; - return renderWithMockStore({ appData })( - - - , - ); + it('should render the component', async () => { + await render({ props }); + expect(screen.getByRole('heading', { name: textMock('general.text') })).toBeInTheDocument(); + }); + + it('should render all available textResourceBinding properties for the input component', async () => { + await render({ props }); + textResourceBindingsPropertiesForComponentType(props.form.type).forEach((trbProperty) => { + // INVESTIGATE WHY IT DOES NOT WORK WITHOUT MOCKED TEXT + const textResourcePropertyLabel = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty})]`; + expect(screen.getByText(textMock(textResourcePropertyLabel))).toBeInTheDocument(); + const textResourcePropertyPlaceHolder = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty}_add)]`; + expect(screen.getByText(textMock(textResourcePropertyPlaceHolder))).toBeInTheDocument(); + }); + }); + + it('should render already defined textResourceBinding properties for the input component when exist', async () => { + await render({ + props: { + ...props, + form: { + ...layoutMock.components[component1IdMock], + textResourceBindings: { title: labelTextId, description: descriptionTextId }, + }, + }, + }); + expect(screen.getByText(labelTextValue)).toBeInTheDocument(); + expect(screen.getByText(descriptionTextValue)).toBeInTheDocument(); + }); + + it('should render editable field in nb when a text is in editMode', async () => { + await render({ + props: { + ...props, + form: { + ...layoutMock.components[component1IdMock], + textResourceBindings: { title: labelTextId, description: descriptionTextId }, + }, + }, + editId: labelTextId, + }); + + expect(screen.getByText(textMock('ux_editor.edit_text_resource'))).toBeInTheDocument(); + const labelTextField = screen.getByRole('textbox', { name: textMock('language.nb') }); + expect(labelTextField).toBeInTheDocument(); + }); + }); +}); + +const render = async ({ props = {}, editId }: { props: Partial; editId?: string }) => { + queryClientMock.setQueryData( + [QueryKey.FormComponent, props.form.type], + componentSchemaMocks[props.form.type], + ); + queryClientMock.setQueryData([QueryKey.TextResources, org, app], textResources); + const textResourcesState: ITextResourcesState = { + ...textResourcesMock, + currentEditId: editId, + }; + const appData: IAppDataState = { + ...appDataMock, + textResources: textResourcesState, + }; + + return renderWithMockStore({ appData })( + + + , + ); }; diff --git a/frontend/packages/ux-editor/src/components/Properties/Text.tsx b/frontend/packages/ux-editor/src/components/Properties/Text.tsx index d547c22de79..a5280752118 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Text.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Text.tsx @@ -22,7 +22,7 @@ export const Text = () => { if (editId) return ; if (!schema?.properties) return null; - + return ( <> {!schema && } diff --git a/frontend/packages/ux-editor/src/components/config/EditFormContainer.tsx b/frontend/packages/ux-editor/src/components/config/EditFormContainer.tsx index 3fb0d82dee3..467bedd4c43 100644 --- a/frontend/packages/ux-editor/src/components/config/EditFormContainer.tsx +++ b/frontend/packages/ux-editor/src/components/config/EditFormContainer.tsx @@ -126,7 +126,7 @@ export const EditFormContainer = ({ id, }); }; - + return container.type === ComponentType.Group ? ( { const { t } = useTranslation(); - const [keysSet, setKeysSet] = useState(Object.keys( textResourceBindingKeys || component.textResourceBindings || {})); - + const [keysSet, setKeysSet] = useState( + Object.keys(textResourceBindingKeys || component.textResourceBindings || {}), + ); + const keysToAdd = useMemo( () => textResourceBindingKeys.filter((key) => !keysSet.includes(key)), [keysSet, textResourceBindingKeys], @@ -41,9 +43,7 @@ export const EditTextResourceBindings = ({ removeTextResourceBinding={() => handleRemoveKey(key)} textKey={key} labelKey={t(`ux_editor.modal_properties_textResourceBindings_${key}`)} - placeholderKey={ - t(`ux_editor.modal_properties_textResourceBindings_${key}_add`) - } + placeholderKey={t(`ux_editor.modal_properties_textResourceBindings_${key}_add`)} /> ))}
From 21302583de015d6ca997847fb0ab2d8b169bc931 Mon Sep 17 00:00:00 2001 From: Andrea Standeren Date: Fri, 2 Feb 2024 16:01:26 +0100 Subject: [PATCH 07/18] Remove texts from static component config --- frontend/language/src/nb.json | 4 +- .../components/Properties/Content.test.tsx | 15 ++ .../src/components/Properties/Content.tsx | 9 + .../components/Properties/Properties.test.tsx | 8 - .../src/components/Properties/Properties.tsx | 15 +- .../src/components/Properties/Text.test.tsx | 188 ------------------ .../src/components/Properties/Text.tsx | 48 ----- .../src/components/TextResource.module.css | 9 +- .../src/components/TextResource.tsx | 14 +- .../config/EditFormComponent.test.tsx | 1 + .../components/config/EditFormContainer.tsx | 17 ++ .../components/config/FormComponentConfig.tsx | 20 +- .../src/components/config/componentConfig.tsx | 4 +- .../editModal/EditTextResourceBinding.tsx | 4 +- .../editModal/EditTextResourceBindings.tsx | 54 +++-- .../hooks/queries/useComponentSchemaQuery.ts | 2 +- .../src/testing/componentSchemaMocks.ts | 5 +- .../components/Properties/Properties.test.tsx | 4 +- .../config/EditFormComponent.test.tsx | 13 -- .../src/components/config/componentConfig.tsx | 60 +----- .../Button/ButtonComponent.module.css | 5 - .../Button/ButtonComponent.test.tsx | 59 ------ .../Button/ButtonComponent.tsx | 33 --- .../componentSpecificContent/Button/index.ts | 1 - .../ComponentSpecificContent.tsx | 11 - .../Image/ImageComponent.tsx | 22 -- .../Panel/PanelComponent.tsx | 8 - .../EditTextResourceBindings.test.tsx | 66 ++---- 28 files changed, 152 insertions(+), 547 deletions(-) delete mode 100644 frontend/packages/ux-editor-v3/src/components/Properties/Text.test.tsx delete mode 100644 frontend/packages/ux-editor-v3/src/components/Properties/Text.tsx delete mode 100644 frontend/packages/ux-editor/src/components/config/componentSpecificContent/Button/ButtonComponent.module.css delete mode 100644 frontend/packages/ux-editor/src/components/config/componentSpecificContent/Button/ButtonComponent.test.tsx delete mode 100644 frontend/packages/ux-editor/src/components/config/componentSpecificContent/Button/ButtonComponent.tsx delete mode 100644 frontend/packages/ux-editor/src/components/config/componentSpecificContent/Button/index.ts diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index ec2fd86ba9b..81199067fb2 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -1636,8 +1636,8 @@ "ux_editor.modal_properties_textResourceBindings_shortName_add": "Legg til kortnavn", "ux_editor.modal_properties_textResourceBindings_tableTitle": "Tittel i tabell", "ux_editor.modal_properties_textResourceBindings_tableTitle_add": "Legg til tittel i tabell", - "ux_editor.modal_properties_textResourceBindings_tag": "Merkebeskrivelse:", - "ux_editor.modal_properties_textResourceBindings_tag_add": "Legg til merkebeskrivelse", + "ux_editor.modal_properties_textResourceBindings_tagTitle": "Merkebeskrivelse", + "ux_editor.modal_properties_textResourceBindings_tagTitle_add": "Legg til merkebeskrivelse", "ux_editor.modal_properties_textResourceBindings_title": "Ledetekst", "ux_editor.modal_properties_textResourceBindings_title_add": "Legg til ledetekst", "ux_editor.modal_properties_trigger_validation_label": "Skal feltet trigge en validering?", diff --git a/frontend/packages/ux-editor-v3/src/components/Properties/Content.test.tsx b/frontend/packages/ux-editor-v3/src/components/Properties/Content.test.tsx index 3122eb2fb69..aabed980cfd 100644 --- a/frontend/packages/ux-editor-v3/src/components/Properties/Content.test.tsx +++ b/frontend/packages/ux-editor-v3/src/components/Properties/Content.test.tsx @@ -19,9 +19,24 @@ import { useLayoutSchemaQuery } from '../../hooks/queries/useLayoutSchemaQuery'; const user = userEvent.setup(); +// Test data: +const textResourceEditTestId = 'text-resource-edit'; + +// Mocks: +jest.mock('../TextResourceEdit', () => ({ + TextResourceEdit: () =>
, +})); + describe('ContentTab', () => { afterEach(jest.clearAllMocks); + describe('when editing a text resource', () => { + it('should render the component', async () => { + await render({ props: {}, editId: 'test' }); + expect(screen.getByTestId(textResourceEditTestId)).toBeInTheDocument(); + }); + }); + describe('when editing a container', () => { const props = { formId: container1IdMock, diff --git a/frontend/packages/ux-editor-v3/src/components/Properties/Content.tsx b/frontend/packages/ux-editor-v3/src/components/Properties/Content.tsx index 1825b36eb15..8a287f5baab 100644 --- a/frontend/packages/ux-editor-v3/src/components/Properties/Content.tsx +++ b/frontend/packages/ux-editor-v3/src/components/Properties/Content.tsx @@ -1,11 +1,20 @@ import React from 'react'; +import { TextResourceEdit } from '../TextResourceEdit'; import { EditFormComponent } from '../config/EditFormComponent'; import { EditFormContainer } from '../config/EditFormContainer'; +import { getCurrentEditId } from '../../selectors/textResourceSelectors'; +import { useSelector } from 'react-redux'; import { useFormContext } from '../../containers/FormContext'; +import { useTranslation } from 'react-i18next'; import { isContainer } from '../../utils/formItemUtils'; export const Content = () => { const { formId, form, handleUpdate, debounceSave } = useFormContext(); + const editId = useSelector(getCurrentEditId); + const { t } = useTranslation(); + + if (editId) return ; + if (!formId || !form) return t('right_menu.content_empty'); return isContainer(form) ? ( ({ - Text: () =>
, -})); jest.mock('./Content', () => ({ Content: () =>
, })); @@ -115,11 +109,9 @@ describe('Properties', () => { it('Renders accordion', () => { const formIdMock = 'test-id'; render({ formId: formIdMock }); - expect(screen.getByText(textText)).toBeInTheDocument(); expect(screen.getByText(contentText)).toBeInTheDocument(); expect(screen.getByText(dynamicsText)).toBeInTheDocument(); expect(screen.getByText(calculationsText)).toBeInTheDocument(); - expect(screen.getByTestId(textTestId)).toBeInTheDocument(); expect(screen.getByTestId(contentTestId)).toBeInTheDocument(); expect(screen.getByTestId(expressionsTestId)).toBeInTheDocument(); expect(screen.getByTestId(calculationsTestId)).toBeInTheDocument(); diff --git a/frontend/packages/ux-editor-v3/src/components/Properties/Properties.tsx b/frontend/packages/ux-editor-v3/src/components/Properties/Properties.tsx index 6531019b17d..5879dff6ebd 100644 --- a/frontend/packages/ux-editor-v3/src/components/Properties/Properties.tsx +++ b/frontend/packages/ux-editor-v3/src/components/Properties/Properties.tsx @@ -1,7 +1,6 @@ import React, { useEffect } from 'react'; import { Calculations } from './Calculations'; import { Content } from './Content'; -import { Text } from './Text'; import { useTranslation } from 'react-i18next'; import { Accordion } from '@digdir/design-system-react'; import { useFormContext } from '../../containers/FormContext'; @@ -18,7 +17,7 @@ export const Properties = () => { useEffect(() => { if (formIdRef.current !== formId) { formIdRef.current = formId; - if (formId && openList.length === 0) setOpenList(['text']); + if (formId && openList.length === 0) setOpenList(['content']); } }, [formId, openList.length]); @@ -33,27 +32,19 @@ export const Properties = () => { return (
- - toggleOpen('text')}> - {t('right_menu.text')} - - {formId ? : t('right_menu.content_empty')} - toggleOpen('content')}> {t('right_menu.content')} - {formId ? : t('right_menu.content_empty')} + toggleOpen('dynamics')}> {t('right_menu.dynamics')} - - {formId ? : t('right_menu.content_empty')} - + {formId && } toggleOpen('calculations')}> diff --git a/frontend/packages/ux-editor-v3/src/components/Properties/Text.test.tsx b/frontend/packages/ux-editor-v3/src/components/Properties/Text.test.tsx deleted file mode 100644 index 4a475f860bf..00000000000 --- a/frontend/packages/ux-editor-v3/src/components/Properties/Text.test.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import React from 'react'; -import { Text } from './Text'; -import { screen } from '@testing-library/react'; -import { textMock } from '../../../../../testing/mocks/i18nMock'; -import { FormContext } from '../../containers/FormContext'; -import { - component1IdMock, - component1Mock, - container1IdMock, - layoutMock, -} from '../../testing/layoutMock'; -import type { IAppDataState } from '../../features/appData/appDataReducers'; -import type { ITextResourcesState } from '../../features/appData/textResources/textResourcesSlice'; -import { renderWithMockStore } from '../../testing/mocks'; -import { appDataMock, textResourcesMock } from '../../testing/stateMocks'; -import { formContextProviderMock } from '../../testing/formContextMocks'; -import { QueryKey } from 'app-shared/types/QueryKey'; -import { queryClientMock } from 'app-shared/mocks/queryClientMock'; -import { componentSchemaMocks } from '../../testing/componentSchemaMocks'; -import type { ITextResource, ITextResources } from 'app-shared/types/global'; -import { DEFAULT_LANGUAGE } from 'app-shared/constants'; - -// Test data: -const org = 'org'; -const app = 'app'; -const labelTextId = 'labelTextId'; -const descriptionTextId = 'descriptionTextId'; -const addButtonTextId = 'customAddButtonTextId'; -const labelTextValue = 'Label for component'; -const descriptionTextValue = 'Description for component'; -const addButtonTextValue = 'Custom text for add button for group'; -const titleTextResource1: ITextResource = { - id: labelTextId, - value: labelTextValue, -}; -const titleTextResource2: ITextResource = { - id: addButtonTextId, - value: addButtonTextValue, -}; -const titleTextResource3: ITextResource = { - id: descriptionTextId, - value: descriptionTextValue, -}; -const textResources: ITextResources = { - [DEFAULT_LANGUAGE]: [titleTextResource1, titleTextResource2, titleTextResource3], -}; - -const textResourceBindingsPropertiesForComponentType = (componentType: string) => - Object.keys(componentSchemaMocks[componentType].properties.textResourceBindings.properties); - -describe('TextTab', () => { - afterEach(jest.clearAllMocks); - - describe('when editing a container', () => { - const props = { - formId: container1IdMock, - form: { ...layoutMock.containers[container1IdMock] }, - }; - - it('should render the component', async () => { - await render({ props }); - expect(screen.getByRole('heading', { name: textMock('general.text') })).toBeInTheDocument(); - }); - - it('should render all available textResourceBinding properties for the group component', async () => { - await render({ props }); - textResourceBindingsPropertiesForComponentType(props.form.type).forEach((trbProperty) => { - // INVESTIGATE WHY IT DOES NOT WORK WITHOUT MOCKED TEXT - const textResourcePropertyLabel = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty})]`; - expect(screen.getByText(textMock(textResourcePropertyLabel))).toBeInTheDocument(); - const textResourcePropertyPlaceHolder = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty}_add)]`; - expect(screen.getByText(textMock(textResourcePropertyPlaceHolder))).toBeInTheDocument(); - }); - }); - - it('should render already defined textResourceBinding properties for the group component when exist', async () => { - await render({ - props: { - ...props, - form: { - ...layoutMock.containers[container1IdMock], - textResourceBindings: { title: labelTextId, add_button: addButtonTextId }, - }, - }, - }); - expect(screen.getByText(labelTextValue)).toBeInTheDocument(); - expect(screen.getByText(addButtonTextValue)).toBeInTheDocument(); - }); - - it('should render editable field in nb when a text is in editMode', async () => { - await render({ - props: { - ...props, - form: { - ...layoutMock.containers[container1IdMock], - textResourceBindings: { title: labelTextId, add_button: addButtonTextId }, - }, - }, - editId: labelTextId, - }); - - expect(screen.getByText(textMock('ux_editor.edit_text_resource'))).toBeInTheDocument(); - const labelTextField = screen.getByRole('textbox', { name: textMock('language.nb') }); - expect(labelTextField).toBeInTheDocument(); - }); - }); - - describe('when editing a component', () => { - const props = { - formId: component1IdMock, - form: { ...component1Mock, dataModelBindings: {} }, - }; - - it('should render the component', async () => { - await render({ props }); - expect(screen.getByRole('heading', { name: textMock('general.text') })).toBeInTheDocument(); - }); - - it('should render all available textResourceBinding properties for the input component', async () => { - await render({ props }); - textResourceBindingsPropertiesForComponentType(props.form.type).forEach((trbProperty) => { - // INVESTIGATE WHY IT DOES NOT WORK WITHOUT MOCKED TEXT - const textResourcePropertyLabel = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty})]`; - expect(screen.getByText(textMock(textResourcePropertyLabel))).toBeInTheDocument(); - const textResourcePropertyPlaceHolder = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty}_add)]`; - expect(screen.getByText(textMock(textResourcePropertyPlaceHolder))).toBeInTheDocument(); - }); - }); - - it('should render already defined textResourceBinding properties for the input component when exist', async () => { - await render({ - props: { - ...props, - form: { - ...layoutMock.components[component1IdMock], - textResourceBindings: { title: labelTextId, description: descriptionTextId }, - }, - }, - }); - expect(screen.getByText(labelTextValue)).toBeInTheDocument(); - expect(screen.getByText(descriptionTextValue)).toBeInTheDocument(); - }); - - it('should render editable field in nb when a text is in editMode', async () => { - await render({ - props: { - ...props, - form: { - ...layoutMock.components[component1IdMock], - textResourceBindings: { title: labelTextId, description: descriptionTextId }, - }, - }, - editId: labelTextId, - }); - - expect(screen.getByText(textMock('ux_editor.edit_text_resource'))).toBeInTheDocument(); - const labelTextField = screen.getByRole('textbox', { name: textMock('language.nb') }); - expect(labelTextField).toBeInTheDocument(); - }); - }); -}); - -const render = async ({ props = {}, editId }: { props: Partial; editId?: string }) => { - queryClientMock.setQueryData( - [QueryKey.FormComponent, props.form.type], - componentSchemaMocks[props.form.type], - ); - queryClientMock.setQueryData([QueryKey.TextResources, org, app], textResources); - const textResourcesState: ITextResourcesState = { - ...textResourcesMock, - currentEditId: editId, - }; - const appData: IAppDataState = { - ...appDataMock, - textResources: textResourcesState, - }; - - return renderWithMockStore({ appData })( - - - , - ); -}; diff --git a/frontend/packages/ux-editor-v3/src/components/Properties/Text.tsx b/frontend/packages/ux-editor-v3/src/components/Properties/Text.tsx deleted file mode 100644 index a5280752118..00000000000 --- a/frontend/packages/ux-editor-v3/src/components/Properties/Text.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import { useSelector } from 'react-redux'; -import { useFormContext } from '../../containers/FormContext'; -import { useTranslation } from 'react-i18next'; -import { Heading } from '@digdir/design-system-react'; -import { EditTextResourceBindings } from '../config/editModal/EditTextResourceBindings'; -import { useLayoutSchemaQuery } from '../../hooks/queries/useLayoutSchemaQuery'; -import { useComponentSchemaQuery } from '../../hooks/queries/useComponentSchemaQuery'; -import { selectedLayoutNameSelector } from '../../selectors/formLayoutSelectors'; -import { StudioSpinner } from '@studio/components'; -import { getCurrentEditId } from '../../selectors/textResourceSelectors'; -import { TextResourceEdit } from '../TextResourceEdit'; - -export const Text = () => { - const { formId, form, handleUpdate, debounceSave } = useFormContext(); - const { t } = useTranslation(); - - useLayoutSchemaQuery(); // Ensure we load the layout schemas so that component schemas can be loaded - const { data: schema } = useComponentSchemaQuery(form.type); - const selectedLayout = useSelector(selectedLayoutNameSelector); - const editId = useSelector(getCurrentEditId); - - if (editId) return ; - if (!schema?.properties) return null; - - return ( - <> - {!schema && } - {schema.properties.textResourceBindings?.properties && ( - <> - - {t('general.text')} - - { - handleUpdate(updatedComponent); - debounceSave(formId, updatedComponent); - }} - textResourceBindingKeys={Object.keys(schema.properties.textResourceBindings.properties)} - editFormId={formId} - layoutName={selectedLayout} - /> - - )} - - ); -}; diff --git a/frontend/packages/ux-editor-v3/src/components/TextResource.module.css b/frontend/packages/ux-editor-v3/src/components/TextResource.module.css index bcfcdb0a37c..110d7dc5e36 100644 --- a/frontend/packages/ux-editor-v3/src/components/TextResource.module.css +++ b/frontend/packages/ux-editor-v3/src/components/TextResource.module.css @@ -1,12 +1,9 @@ .root { --frame-offset: 4px; - border-radius: 1rem; - padding: 1rem; + border-radius: 2px; display: flex; flex-direction: column; - gap: 4px; - margin-top: 8px; - background-color: white; + gap: 1rem; } .root.isEditing { @@ -38,7 +35,7 @@ } .textResource { - align-items: center; + align-items: stretch; display: inline-flex; } diff --git a/frontend/packages/ux-editor-v3/src/components/TextResource.tsx b/frontend/packages/ux-editor-v3/src/components/TextResource.tsx index 4c92ebec24f..2cff0882d5d 100644 --- a/frontend/packages/ux-editor-v3/src/components/TextResource.tsx +++ b/frontend/packages/ux-editor-v3/src/components/TextResource.tsx @@ -24,6 +24,7 @@ import { prepend } from 'app-shared/utils/arrayUtils'; import cn from 'classnames'; import type { ITextResource } from 'app-shared/types/global'; import { useTextResourcesSelector } from '../hooks'; +import { FormField } from './FormField'; import { AltinnConfirmDialog } from 'app-shared/components/AltinnConfirmDialog'; import { useTranslation } from 'react-i18next'; import { shouldDisplayFeature } from 'app-shared/utils/featureToggleUtils'; @@ -59,6 +60,7 @@ export const TextResource = ({ handleRemoveTextResource, label, placeholder, + previewMode, textResourceId, generateIdOptions, }: TextResourceProps) => { @@ -107,6 +109,7 @@ export const TextResource = ({ ); - return renderTextResource(); + return previewMode ? ( + renderTextResource() + ) : ( + renderTextResource()} + /> + ); }; export interface TextResourceOptionProps { diff --git a/frontend/packages/ux-editor-v3/src/components/config/EditFormComponent.test.tsx b/frontend/packages/ux-editor-v3/src/components/config/EditFormComponent.test.tsx index c238c7800f3..969a11680a1 100644 --- a/frontend/packages/ux-editor-v3/src/components/config/EditFormComponent.test.tsx +++ b/frontend/packages/ux-editor-v3/src/components/config/EditFormComponent.test.tsx @@ -226,6 +226,7 @@ const waitForData = async () => { { getDatamodelMetadata }, )(() => useDatamodelMetadataQuery('test-org', 'test-app')).renderHookResult.result; await waitFor(() => expect(dataModelMetadataResult.current.isSuccess).toBe(true)); + await waitFor(() => expect(layoutSchemaResult.current[0].isSuccess).toBe(true)); }; const render = async ({ diff --git a/frontend/packages/ux-editor-v3/src/components/config/EditFormContainer.tsx b/frontend/packages/ux-editor-v3/src/components/config/EditFormContainer.tsx index 467bedd4c43..2b87df6ecdb 100644 --- a/frontend/packages/ux-editor-v3/src/components/config/EditFormContainer.tsx +++ b/frontend/packages/ux-editor-v3/src/components/config/EditFormContainer.tsx @@ -14,6 +14,7 @@ import { Paragraph, } from '@digdir/design-system-react'; import classes from './EditFormContainer.module.css'; +import { TextResource } from '../TextResource'; import { useDatamodelMetadataQuery } from '../../hooks/queries/useDatamodelMetadataQuery'; import { useText } from '../../hooks'; import { useSelectedFormLayout, useTextResourcesSelector } from '../../hooks'; @@ -86,6 +87,16 @@ export const EditFormContainer = ({ }); }; + const handleButtonTextChange = (id: string) => { + handleContainerUpdate({ + ...container, + textResourceBindings: { + ...container.textResourceBindings, + add_button: id, + }, + }); + }; + const handleTableHeadersChange = (ids: string[]) => { const updatedContainer = { ...container }; updatedContainer.tableHeaders = [...ids]; @@ -193,6 +204,12 @@ export const EditFormContainer = ({ /> )} /> + {items?.length > 0 && ( { + const selectedLayout = useSelector(selectedLayoutNameSelector); const t = useText(); if (!schema?.properties) return null; const { + textResourceBindings, dataModelBindings, required, readOnly, id, - textResourceBindings, type, options, optionsId, @@ -63,6 +67,20 @@ export const FormComponentConfig = ({ helpText={id.description} /> )} + {textResourceBindings?.properties && ( + <> + + {t('general.text')} + + + + )} {dataModelBindings?.properties && ( <> diff --git a/frontend/packages/ux-editor-v3/src/components/config/componentConfig.tsx b/frontend/packages/ux-editor-v3/src/components/config/componentConfig.tsx index b3c62acfa6a..95915a56e02 100644 --- a/frontend/packages/ux-editor-v3/src/components/config/componentConfig.tsx +++ b/frontend/packages/ux-editor-v3/src/components/config/componentConfig.tsx @@ -114,8 +114,8 @@ export const configComponents: IConfigComponents = { component={component} handleComponentChange={handleComponentChange} textKey={EditSettings.TagTitle} - labelKey='ux_editor.modal_properties_textResourceBindings_tag' - placeholderKey='ux_editor.modal_properties_textResourceBindings_tag_add' + labelKey='ux_editor.modal_properties_textResourceBindings_tagTitle' + placeholderKey='ux_editor.modal_properties_textResourceBindings_tagTitle_add' /> ), [EditSettings.Options]: EditOptions, diff --git a/frontend/packages/ux-editor-v3/src/components/config/editModal/EditTextResourceBinding.tsx b/frontend/packages/ux-editor-v3/src/components/config/editModal/EditTextResourceBinding.tsx index 522961cd8dc..fb71990e25b 100644 --- a/frontend/packages/ux-editor-v3/src/components/config/editModal/EditTextResourceBinding.tsx +++ b/frontend/packages/ux-editor-v3/src/components/config/editModal/EditTextResourceBinding.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { useSelector } from 'react-redux'; +import type { IGenericEditComponent } from '../componentConfig'; import { TextResource } from '../../TextResource'; import type { TranslationKey } from 'language/type'; import type { IAppState } from '../../../types/global'; import { useTranslation } from 'react-i18next'; -import type { EditTextResourceBindingsProps } from './EditTextResourceBindings'; -export interface EditTextResourceBindingProps extends EditTextResourceBindingsProps { +export interface EditTextResourceBindingProps extends IGenericEditComponent { textKey: string; labelKey: TranslationKey; descriptionKey?: TranslationKey; diff --git a/frontend/packages/ux-editor-v3/src/components/config/editModal/EditTextResourceBindings.tsx b/frontend/packages/ux-editor-v3/src/components/config/editModal/EditTextResourceBindings.tsx index b5622ad4a0f..5a204eb86cf 100644 --- a/frontend/packages/ux-editor-v3/src/components/config/editModal/EditTextResourceBindings.tsx +++ b/frontend/packages/ux-editor-v3/src/components/config/editModal/EditTextResourceBindings.tsx @@ -1,16 +1,15 @@ -import React, { useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; +import type { IGenericEditComponent } from '../componentConfig'; import { EditTextResourceBinding } from './EditTextResourceBinding'; import classes from './EditTextResourceBindings.module.css'; +import type { TranslationKey } from 'language/type'; import { useTranslation } from 'react-i18next'; -import type { FormContainer } from '../../../types/FormContainer'; -import type { FormComponent } from '../../../types/FormComponent'; - -export interface EditTextResourceBindingsProps { - editFormId?: string; - component: FormComponent | FormContainer; - handleComponentChange: (component: FormComponent | FormContainer) => void; - textResourceBindingKeys?: string[]; - layoutName?: string; +import { LegacySelect } from '@digdir/design-system-react'; + +export type TextResourceBindingKey = 'description' | 'title' | 'help' | 'body'; + +export interface EditTextResourceBindingsProps extends IGenericEditComponent { + textResourceBindingKeys: string[]; } export const EditTextResourceBindings = ({ @@ -20,32 +19,55 @@ export const EditTextResourceBindings = ({ }: EditTextResourceBindingsProps) => { const { t } = useTranslation(); - const [keysSet, setKeysSet] = useState( - Object.keys(textResourceBindingKeys || component.textResourceBindings || {}), - ); + const [keysSet, setKeysSet] = React.useState(Object.keys(component.textResourceBindings || {})); const keysToAdd = useMemo( () => textResourceBindingKeys.filter((key) => !keysSet.includes(key)), [keysSet, textResourceBindingKeys], ); + const handleAddKey = (key: string) => { + setKeysSet([...keysSet, key]); + handleComponentChange({ + ...component, + textResourceBindings: { + ...component.textResourceBindings, + [key]: '', + }, + }); + }; + const handleRemoveKey = (key: string) => { setKeysSet((prevKeysSet) => prevKeysSet.filter((k) => k !== key)); }; return (
- {keysToAdd.map((key: string) => ( + {keysSet.map((key: string) => ( handleRemoveKey(key)} textKey={key} - labelKey={t(`ux_editor.modal_properties_textResourceBindings_${key}`)} - placeholderKey={t(`ux_editor.modal_properties_textResourceBindings_${key}_add`)} + labelKey={`ux_editor.modal_properties_textResourceBindings_${key}` as TranslationKey} + placeholderKey={ + `ux_editor.modal_properties_textResourceBindings_${key}_add` as TranslationKey + } /> ))} + {keysToAdd.length > 0 && ( +
+ ({ + label: t(`ux_editor.modal_properties_textResourceBindings_${key}`), + value: key, + }))} + onChange={(value) => handleAddKey(value)} + label={t('ux_editor.text_resource_bindings.add_label')} + /> +
+ )}
); }; diff --git a/frontend/packages/ux-editor-v3/src/hooks/queries/useComponentSchemaQuery.ts b/frontend/packages/ux-editor-v3/src/hooks/queries/useComponentSchemaQuery.ts index 9942d1ebf5d..81501796a6e 100644 --- a/frontend/packages/ux-editor-v3/src/hooks/queries/useComponentSchemaQuery.ts +++ b/frontend/packages/ux-editor-v3/src/hooks/queries/useComponentSchemaQuery.ts @@ -13,7 +13,7 @@ export interface UseComponentSchemaQueryResult { // Currently use local mocks rather than fetching from CDN. This is because the CDN schemas are not ready to use, and // we also have made some modifications locally to the schemas. -// When the schemas are available on CDN, we can remove the mocks and use the queries instead. +// When the schemas are available on CDN, we can remove the mocks and use the querys instead. export const useComponentSchemaQuery = (component: string): UseQueryResult => { const queryClient = useQueryClient(); diff --git a/frontend/packages/ux-editor-v3/src/testing/componentSchemaMocks.ts b/frontend/packages/ux-editor-v3/src/testing/componentSchemaMocks.ts index f7a6053c413..ba5013341af 100644 --- a/frontend/packages/ux-editor-v3/src/testing/componentSchemaMocks.ts +++ b/frontend/packages/ux-editor-v3/src/testing/componentSchemaMocks.ts @@ -1,6 +1,6 @@ import AlertSchema from './schemas/json/component/Alert.schema.v1.json'; // TODO: Add schemas for the commented out components (https://github.com/Altinn/altinn-studio/issues/10868): -import AccordionSchema from './schemas/json/component/Accordion.schema.v1.json'; +// import AccordionSchema from './schemas/json/component/Accordion.schema.v1.json'; // import AccordionGroupSchema from './schemas/json/component/AccordionGroup.schema.v1.json'; import ActionButtonSchema from './schemas/json/component/ActionButton.schema.v1.json'; import AddressComponentSchema from './schemas/json/component/AddressComponent.schema.v1.json'; @@ -36,10 +36,9 @@ import SummarySchema from './schemas/json/component/Summary.schema.v1.json'; import TextAreaSchema from './schemas/json/component/TextArea.schema.v1.json'; export const componentSchemaMocks = { - Accordion: AccordionSchema, + Alert: AlertSchema, ActionButton: ActionButtonSchema, AddressComponent: AddressComponentSchema, - Alert: AlertSchema, AttachmentList: AttachmentListSchema, Button: ButtonSchema, ButtonGroup: ButtonGroupSchema, 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 5fb34b4d5c9..7e6d53f4739 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Properties.test.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Properties.test.tsx @@ -61,10 +61,10 @@ describe('Properties', () => { expect(button).toHaveAttribute('aria-expanded', 'false'); }); - it('Opens content when a component is selected', async () => { + it('Opens text when a component is selected', async () => { const { rerender } = render(); rerender(getComponent({ formId: 'test' })); - const button = screen.queryByRole('button', { name: contentText }); + const button = screen.queryByRole('button', { name: textText }); await waitFor(() => expect(button).toHaveAttribute('aria-expanded', 'true')); }); }); diff --git a/frontend/packages/ux-editor/src/components/config/EditFormComponent.test.tsx b/frontend/packages/ux-editor/src/components/config/EditFormComponent.test.tsx index c238c7800f3..bfa14bf9633 100644 --- a/frontend/packages/ux-editor/src/components/config/EditFormComponent.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/EditFormComponent.test.tsx @@ -28,10 +28,6 @@ const texts = { // Mocks: jest.mock('react-i18next', () => ({ useTranslation: () => mockUseTranslation(texts) })); -const buttonSpecificContentId = 'button-specific-content'; -jest.mock('./componentSpecificContent/Button/ButtonComponent', () => ({ - ButtonComponent: () =>
, -})); const imageSpecificContentId = 'image-specific-content'; jest.mock('./componentSpecificContent/Image/ImageComponent', () => ({ ImageComponent: () =>
, @@ -179,15 +175,6 @@ describe('EditFormComponent', () => { }); }); - test('should return button specific content when type button', async () => { - await render({ - componentProps: { - type: ComponentType.Button, - }, - }); - expect(await screen.findByTestId(buttonSpecificContentId)).toBeInTheDocument(); - }); - test('should render Image component when component type is Image', async () => { await render({ componentProps: { diff --git a/frontend/packages/ux-editor/src/components/config/componentConfig.tsx b/frontend/packages/ux-editor/src/components/config/componentConfig.tsx index b3c62acfa6a..bce1ee564b7 100644 --- a/frontend/packages/ux-editor/src/components/config/componentConfig.tsx +++ b/frontend/packages/ux-editor/src/components/config/componentConfig.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { ComponentType } from 'app-shared/types/ComponentType'; import { EditCodeList } from './editModal/EditCodeList'; import { EditDataModelBindings } from './editModal/EditDataModelBindings'; @@ -8,7 +7,6 @@ import { EditPreselectedIndex } from './editModal/EditPreselectedIndex'; import { EditReadOnly } from './editModal/EditReadOnly'; import { EditRequired } from './editModal/EditRequired'; import { EditAutoComplete } from './editModal/EditAutoComplete'; -import { EditTextResourceBinding } from './editModal/EditTextResourceBinding'; import type { FormComponent } from '../../types/FormComponent'; export interface IGenericEditComponent { @@ -19,10 +17,6 @@ export interface IGenericEditComponent } export enum EditSettings { - Title = 'title', - Description = 'description', - Help = 'help', - TagTitle = 'tagTitle', DataModelBindings = 'dataModelBindings', Size = 'size', ReadOnly = 'readonly', @@ -35,9 +29,6 @@ export enum EditSettings { export const editBoilerPlate = [ EditSettings.DataModelBindings, - EditSettings.Title, - EditSettings.Description, - EditSettings.Help, EditSettings.ReadOnly, EditSettings.Required, ]; @@ -51,12 +42,10 @@ interface IConfigComponents { } export const componentSpecificEditConfig: IComponentEditConfig = { - [ComponentType.Header]: [EditSettings.Title, EditSettings.Size], + [ComponentType.Header]: [EditSettings.Size], [ComponentType.Input]: [...editBoilerPlate, EditSettings.AutoComplete], [ComponentType.TextArea]: [...editBoilerPlate, EditSettings.AutoComplete], [ComponentType.Datepicker]: [...editBoilerPlate], - [ComponentType.Paragraph]: [EditSettings.Title], - [ComponentType.AttachmentList]: [EditSettings.Title], [ComponentType.Checkboxes]: [ ...editBoilerPlate, EditSettings.Options, @@ -73,62 +62,17 @@ export const componentSpecificEditConfig: IComponentEditConfig = { EditSettings.PreselectedIndex, EditSettings.AutoComplete, ], - [ComponentType.AddressComponent]: [EditSettings.Title, EditSettings.Help], - [ComponentType.FileUpload]: [EditSettings.Title, EditSettings.Description, EditSettings.Help], - [ComponentType.FileUploadWithTag]: [ - EditSettings.Title, - EditSettings.Description, - EditSettings.Help, - EditSettings.TagTitle, - EditSettings.CodeList, - ], - [ComponentType.Panel]: [EditSettings.Title], + [ComponentType.FileUploadWithTag]: [EditSettings.CodeList], [ComponentType.Map]: [...editBoilerPlate], }; export const configComponents: IConfigComponents = { [EditSettings.DataModelBindings]: EditDataModelBindings, [EditSettings.Size]: EditHeaderSize, - [EditSettings.Title]: ({ component, handleComponentChange }: IGenericEditComponent) => ( - - ), [EditSettings.ReadOnly]: EditReadOnly, [EditSettings.Required]: EditRequired, - [EditSettings.Description]: ({ component, handleComponentChange }: IGenericEditComponent) => ( - - ), - [EditSettings.TagTitle]: ({ component, handleComponentChange }: IGenericEditComponent) => ( - - ), [EditSettings.Options]: EditOptions, [EditSettings.CodeList]: EditCodeList, [EditSettings.PreselectedIndex]: EditPreselectedIndex, [EditSettings.AutoComplete]: EditAutoComplete, - [EditSettings.Help]: ({ component, handleComponentChange }: IGenericEditComponent) => ( - - ), }; diff --git a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Button/ButtonComponent.module.css b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Button/ButtonComponent.module.css deleted file mode 100644 index 63eaa171d31..00000000000 --- a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Button/ButtonComponent.module.css +++ /dev/null @@ -1,5 +0,0 @@ -.root > div { - display: flex; - flex-direction: column; - gap: var(--fieldset-gap); -} diff --git a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Button/ButtonComponent.test.tsx b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Button/ButtonComponent.test.tsx deleted file mode 100644 index b30eb756493..00000000000 --- a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Button/ButtonComponent.test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; -import { screen, waitFor } from '@testing-library/react'; -import { renderWithMockStore, renderHookWithMockStore } from '../../../../testing/mocks'; -import { useLayoutSchemaQuery } from '../../../../hooks/queries/useLayoutSchemaQuery'; -import { ButtonComponent } from './ButtonComponent'; -import { ComponentType } from 'app-shared/types/ComponentType'; -import type { FormButtonComponent } from '../../../../types/FormComponent'; -import type { IGenericEditComponent } from '../../componentConfig'; -import { textMock } from '../../../../../../../testing/mocks/i18nMock'; - -// Test data: -const component: FormButtonComponent = { - id: '1', - onClickAction: jest.fn(), - type: ComponentType.Button, - itemType: 'COMPONENT', - dataModelBindings: {}, -}; -const handleComponentChange = jest.fn(); -const defaultProps: IGenericEditComponent = { - component, - handleComponentChange, -}; - -describe('ButtonComponent', () => { - it('should render title text resource bindings for Button component', async () => { - await render(); - expect( - screen.getByText(textMock('ux_editor.modal_properties_textResourceBindings_title')), - ).toBeInTheDocument(); - }); - - it('should render next and back text resource bindings for NavigationButtons component', async () => { - await render({ - component: { - ...component, - type: ComponentType.NavigationButtons, - }, - }); - expect( - screen.getByText(textMock('ux_editor.modal_properties_textResourceBindings_next')), - ).toBeInTheDocument(); - expect( - screen.getByText(textMock('ux_editor.modal_properties_textResourceBindings_back')), - ).toBeInTheDocument(); - }); -}); - -const waitForData = async () => { - const layoutSchemaResult = renderHookWithMockStore()(() => useLayoutSchemaQuery()) - .renderHookResult.result; - await waitFor(() => expect(layoutSchemaResult.current[0].isSuccess).toBe(true)); -}; - -const render = async (props?: Partial) => { - await waitForData(); - - renderWithMockStore()(); -}; diff --git a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Button/ButtonComponent.tsx b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Button/ButtonComponent.tsx deleted file mode 100644 index 920ff8cd359..00000000000 --- a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Button/ButtonComponent.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import { Fieldset } from '@digdir/design-system-react'; -import classes from './ButtonComponent.module.css'; -import type { IGenericEditComponent } from '../../componentConfig'; -import { EditSettings } from '../../componentConfig'; -import { ComponentType } from 'app-shared/types/ComponentType'; -import { EditTextResourceBinding } from '../../editModal/EditTextResourceBinding'; -import { EditTextResourceBindings } from '../../editModal/EditTextResourceBindings'; -import { useTranslation } from 'react-i18next'; - -export const ButtonComponent = ({ component, handleComponentChange }: IGenericEditComponent) => { - const { t } = useTranslation(); - return ( -
- {component.type === ComponentType.Button && ( - - )} - {component.type === ComponentType.NavigationButtons && ( - - )} -
- ); -}; diff --git a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Button/index.ts b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Button/index.ts deleted file mode 100644 index 9ea38501c7f..00000000000 --- a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/Button/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ButtonComponent } from './ButtonComponent'; diff --git a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/ComponentSpecificContent.tsx b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/ComponentSpecificContent.tsx index 1b66aa81008..48296a18e81 100644 --- a/frontend/packages/ux-editor/src/components/config/componentSpecificContent/ComponentSpecificContent.tsx +++ b/frontend/packages/ux-editor/src/components/config/componentSpecificContent/ComponentSpecificContent.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { ImageComponent } from './Image'; import { PanelComponent } from './Panel'; -import { ButtonComponent } from './Button'; import { AddressComponent } from './Address'; import { FileUploadComponent } from './FileUpload'; import type { IGenericEditComponent } from '../componentConfig'; @@ -14,16 +13,6 @@ export function ComponentSpecificContent({ layoutName, }: IGenericEditComponent) { switch (component.type) { - case ComponentType.NavigationButtons: - case ComponentType.Button: - return ( - - ); - case ComponentType.AddressComponent: return ( { - handleComponentChange({ - ...component, - textResourceBindings: { - ...component.textResourceBindings, - altTextImg: altTextImg, - }, - }); - }; - const handleSourceChange = (src: any) => { const updatedComponent = { ...component }; updatedComponent.image.src = src; @@ -82,17 +71,6 @@ export const ImageComponent = ({ )} /> - -
- { const mockComponent: FormComponent = { id: 'test-id', textResourceBindings: { - test: 'test-text', + title: 'test-title-text-id', + description: 'test-desc-text-id', + help: 'test-help-text-id', }, type: ComponentType.Input, itemType: 'COMPONENT', @@ -33,8 +34,16 @@ describe('EditTextResourceBindings component', () => { const textResources: ITextResource[] = [ { - id: 'test-text', - value: 'This is a test', + id: 'test-title-text-id', + value: 'This is a test text', + }, + { + id: 'test-desc-text-id', + value: 'This is a test desc ', + }, + { + id: 'test-help-text-id', + value: 'This is a test help text', }, ]; @@ -42,11 +51,15 @@ describe('EditTextResourceBindings component', () => { const textResourceBindingKeys = ['title', 'description', 'help']; await renderEditTextResourceBindingsComponent({ textResourceBindingKeys }); const label = screen.getByText( - textMock(`ux_editor.modal_properties_textResourceBindings_test`), + textMock('[mockedText(ux_editor.modal_properties_textResourceBindings_title)]'), ); - const text = screen.getByText('This is a test'); expect(label).toBeInTheDocument(); - expect(text).toBeInTheDocument(); + const labelText = screen.getByText('This is a test text'); + expect(labelText).toBeInTheDocument(); + const descText = screen.getByText('This is a test desc'); + expect(descText).toBeInTheDocument(); + const helpText = screen.getByText('This is a test help text'); + expect(helpText).toBeInTheDocument(); }); test('that it renders no text resource bindings if none are added', async () => { @@ -61,43 +74,6 @@ describe('EditTextResourceBindings component', () => { expect(searchTextButton).not.toBeInTheDocument(); }); - test('that it renders the combobox for selecting text resource binding keys to add', async () => { - const textResourceBindingKeys = ['title', 'description', 'help']; - await renderEditTextResourceBindingsComponent({ textResourceBindingKeys }); - const selectTextResourcesCombobox = screen.getByRole('combobox', { - name: textMock('ux_editor.text_resource_bindings.add_label'), - }); - expect(selectTextResourcesCombobox).toBeInTheDocument(); - }); - - test('that the combobox for selecting text resource binding keys only contains keys that are not already added', async () => { - const textResourceBindingKeys = ['title', 'description', 'help']; - await renderEditTextResourceBindingsComponent({ textResourceBindingKeys }); - const selectTextResourcesCombobox = screen.getByRole('combobox', { - name: textMock('ux_editor.text_resource_bindings.add_label'), - }); - - await act(() => userEvent.click(selectTextResourcesCombobox)); // eslint-disable-line testing-library/no-unnecessary-act - let options = screen.getAllByRole('option'); - expect(options.length).toBe(3); - - await act(() => userEvent.click(options[0])); // eslint-disable-line testing-library/no-unnecessary-act - await act(() => userEvent.click(selectTextResourcesCombobox)); // eslint-disable-line testing-library/no-unnecessary-act - options = screen.getAllByRole('option'); - expect(options.length).toBe(2); - }); - - test('that it does not render the combobox for selecting text resource binding keys when all available keys are added', async () => { - const textResourceBindingKeys = ['test']; - await renderEditTextResourceBindingsComponent({ textResourceBindingKeys }); - const selectTextResourcesCombobox = screen.queryByRole('combobox', { - name: textMock('ux_editor.text_resource_bindings.add_label'), - }); - const addTextResourceButton = screen.queryByRole('button', { name: textMock('general.add') }); - expect(selectTextResourcesCombobox).not.toBeInTheDocument(); - expect(addTextResourceButton).not.toBeInTheDocument(); - }); - const waitForData = async () => { const layoutSchemaResult = renderHookWithMockStore()(() => useLayoutSchemaQuery()) .renderHookResult.result; From 8c07664f5f21930a6f0f606da63512a6a1c7f5be Mon Sep 17 00:00:00 2001 From: Nina Kylstad Date: Mon, 5 Feb 2024 16:03:07 +0100 Subject: [PATCH 08/18] add options section to texts accordion & update tests --- frontend/language/src/nb.json | 8 +- .../src/components/Properties/Text.module.css | 3 + .../src/components/Properties/Text.test.tsx | 97 +++++++++-- .../src/components/Properties/Text.tsx | 22 ++- .../config/FormComponentConfig.test.tsx | 47 +++-- .../components/config/FormComponentConfig.tsx | 8 - .../src/components/config/componentConfig.tsx | 15 +- .../config/editModal/EditCodeList.test.tsx | 13 +- .../config/editModal/EditCodeList.tsx | 23 ++- .../config/editModal/EditOptions.module.css | 7 + .../config/editModal/EditOptions.test.tsx | 14 +- .../config/editModal/EditOptions.tsx | 161 ++++++++++-------- .../EditTextResourceBindings.test.tsx | 2 +- .../editModal/EditTextResourceBindings.tsx | 7 +- .../ux-editor/src/testing/layoutMock.ts | 8 + 15 files changed, 283 insertions(+), 152 deletions(-) create mode 100644 frontend/packages/ux-editor/src/components/Properties/Text.module.css diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 81199067fb2..c5aeb0df9ae 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -1542,7 +1542,7 @@ "ux_editor.modal_new_option": "Legg til flere", "ux_editor.modal_options": "Valg", "ux_editor.modal_properties_add_check_box_options": "Hvordan vil du legge til avkrysningsbokser?", - "ux_editor.modal_properties_add_radio_button_options": "Hvordan vil du legge til radioknapper?", + "ux_editor.modal_properties_add_options": "Hvordan vil du legge til alternativer?", "ux_editor.modal_properties_button_helper": "Søk etter tekst til knappen", "ux_editor.modal_properties_button_type_ActionButton": "ActionButton", "ux_editor.modal_properties_button_type_InstantiationButton": "InstantiationButton", @@ -1672,6 +1672,12 @@ "ux_editor.pages_error_length": "Navnet kan ikke være lengre enn 30 tegn.", "ux_editor.pages_error_unique": "Navnet må være unikt.", "ux_editor.preview": "Forhåndsvisning", + "ux_editor.properties_panel.options.add_options": "Legg til alternativer", + "ux_editor.properties_panel.options.codelist_switch_to_custom": "Bytt til egendefinert kodeliste", + "ux_editor.properties_panel.options.codelist_switch_to_static": "Bytt til statisk kodeliste", + "ux_editor.properties_panel.options.use_code_list_helptext": "Skru på denne innstillingen for å bruke en kodeliste for å styre alternativene til denne komponenten. Skru av innstillingen for å legge til alternativene manuelt.", + "ux_editor.properties_panel.options.use_code_list_label": "Bruk kodeliste", + "ux_editor.properties_panel.texts.options_title": "Kodelister og alternativer", "ux_editor.radios_description_add": "Legg til beskrivelse", "ux_editor.radios_description_placeholder": "Ingen beskrivelse", "ux_editor.radios_error_DuplicateValues": "Alle verdier må være unike.", diff --git a/frontend/packages/ux-editor/src/components/Properties/Text.module.css b/frontend/packages/ux-editor/src/components/Properties/Text.module.css new file mode 100644 index 00000000000..cb072f38c6d --- /dev/null +++ b/frontend/packages/ux-editor/src/components/Properties/Text.module.css @@ -0,0 +1,3 @@ +.textResourceContainer { + padding-bottom: 20px; +} diff --git a/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx b/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx index 4a475f860bf..4f4f17ce0a8 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx @@ -65,11 +65,16 @@ describe('TextTab', () => { it('should render all available textResourceBinding properties for the group component', async () => { await render({ props }); textResourceBindingsPropertiesForComponentType(props.form.type).forEach((trbProperty) => { - // INVESTIGATE WHY IT DOES NOT WORK WITHOUT MOCKED TEXT - const textResourcePropertyLabel = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty})]`; - expect(screen.getByText(textMock(textResourcePropertyLabel))).toBeInTheDocument(); - const textResourcePropertyPlaceHolder = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty}_add)]`; - expect(screen.getByText(textMock(textResourcePropertyPlaceHolder))).toBeInTheDocument(); + expect( + screen.getByText( + textMock(`ux_editor.modal_properties_textResourceBindings_${trbProperty}`), + ), + ).toBeInTheDocument(); + expect( + screen.getByText( + textMock(`ux_editor.modal_properties_textResourceBindings_${trbProperty}_add`), + ), + ).toBeInTheDocument(); }); }); @@ -119,11 +124,16 @@ describe('TextTab', () => { it('should render all available textResourceBinding properties for the input component', async () => { await render({ props }); textResourceBindingsPropertiesForComponentType(props.form.type).forEach((trbProperty) => { - // INVESTIGATE WHY IT DOES NOT WORK WITHOUT MOCKED TEXT - const textResourcePropertyLabel = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty})]`; - expect(screen.getByText(textMock(textResourcePropertyLabel))).toBeInTheDocument(); - const textResourcePropertyPlaceHolder = `[mockedText(ux_editor.modal_properties_textResourceBindings_${trbProperty}_add)]`; - expect(screen.getByText(textMock(textResourcePropertyPlaceHolder))).toBeInTheDocument(); + expect( + screen.getByText( + textMock(`ux_editor.modal_properties_textResourceBindings_${trbProperty}`), + ), + ).toBeInTheDocument(); + expect( + screen.getByText( + textMock(`ux_editor.modal_properties_textResourceBindings_${trbProperty}_add`), + ), + ).toBeInTheDocument(); }); }); @@ -157,6 +167,73 @@ describe('TextTab', () => { const labelTextField = screen.getByRole('textbox', { name: textMock('language.nb') }); expect(labelTextField).toBeInTheDocument(); }); + + it('should not render options section if component schema does not have options/optionsId property', async () => { + await render({ + props: { + ...props, + form: { + ...layoutMock.components[component1IdMock], + }, + }, + }); + + expect( + screen.queryByRole('heading', { + name: textMock('ux_editor.properties_panel.texts.options_title'), + }), + ).not.toBeInTheDocument(); + }); + + it('should render options section if component schema has options property', async () => { + await render({ + props: { + ...props, + form: { + ...layoutMock.components.componentWithOptionsMock, + optionsId: 'optionsId', + }, + }, + }); + + expect( + screen.getByRole('heading', { + name: textMock('ux_editor.properties_panel.texts.options_title'), + }), + ).toBeInTheDocument(); + }); + + it('should render options section with codelist view if component has optionId defined', async () => { + await render({ + props: { + ...props, + form: { + ...layoutMock.components.componentWithOptionsMock, + optionsId: 'optionsId', + }, + }, + }); + + expect( + screen.getByText(textMock('ux_editor.modal_properties_custom_code_list_id')), + ).toBeInTheDocument(); + }); + + it('should render options section with manual view if component has options', async () => { + await render({ + props: { + ...props, + form: { + ...layoutMock.components.componentWithOptionsMock, + options: [{ label: labelTextId, value: 'value' }], + }, + }, + }); + + expect( + screen.getByText(textMock('ux_editor.properties_panel.options.add_options')), + ).toBeInTheDocument(); + }); }); }); diff --git a/frontend/packages/ux-editor/src/components/Properties/Text.tsx b/frontend/packages/ux-editor/src/components/Properties/Text.tsx index a5280752118..e7deb10468e 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Text.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Text.tsx @@ -10,6 +10,8 @@ import { selectedLayoutNameSelector } from '../../selectors/formLayoutSelectors' import { StudioSpinner } from '@studio/components'; import { getCurrentEditId } from '../../selectors/textResourceSelectors'; import { TextResourceEdit } from '../TextResourceEdit'; +import { EditOptions } from '../config/editModal/EditOptions'; +import classes from './Text.module.css'; export const Text = () => { const { formId, form, handleUpdate, debounceSave } = useFormContext(); @@ -27,8 +29,8 @@ export const Text = () => { <> {!schema && } {schema.properties.textResourceBindings?.properties && ( - <> - +
+ {t('general.text')} { editFormId={formId} layoutName={selectedLayout} /> +
+ )} + {(schema.properties.options || schema.properties.optionsId) && ( + <> + + {t('ux_editor.properties_panel.texts.options_title')} + + { + handleUpdate(updatedComponent); + debounceSave(formId, updatedComponent); + }} + editFormId={formId} + layoutName={selectedLayout} + /> )} 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 d6dc6aa6174..bd9bb85c248 100644 --- a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.test.tsx @@ -14,35 +14,30 @@ describe('FormComponentConfig', () => { expect( screen.getByText(textMock('ux_editor.modal_properties_component_change_id')), ).toBeInTheDocument(); - ['title', 'description', 'help'].forEach(async (key) => { + + await waitFor(() => { expect( - screen.getByText(textMock(`ux_editor.modal_properties_textResourceBindings_${key}`)), + screen.getByText(textMock('ux_editor.modal_properties_data_model_link')), ).toBeInTheDocument(); + }); - await waitFor(() => { - expect( - screen.getByText(textMock('ux_editor.modal_properties_data_model_link')), - ).toBeInTheDocument(); - }); - - [ - 'grid', - 'readOnly', - 'required', - 'hidden', - 'renderAsSummary', - 'variant', - 'autocomplete', - 'maxLength', - 'triggers', - 'labelSettings', - 'pageBreak', - 'formatting', - ].forEach(async (propertyKey) => { - expect( - await screen.findByText(textMock(`ux_editor.component_properties.${propertyKey}`)), - ).toBeInTheDocument(); - }); + [ + 'grid', + 'readOnly', + 'required', + 'hidden', + 'renderAsSummary', + 'variant', + 'autocomplete', + 'maxLength', + 'triggers', + 'labelSettings', + 'pageBreak', + 'formatting', + ].forEach(async (propertyKey) => { + expect( + await screen.findByText(textMock(`ux_editor.component_properties.${propertyKey}`)), + ).toBeInTheDocument(); }); }); diff --git a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx index ce3777c2d5a..bc994ea0390 100644 --- a/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx +++ b/frontend/packages/ux-editor/src/components/config/FormComponentConfig.tsx @@ -5,7 +5,6 @@ import type { FormComponent } from '../../types/FormComponent'; import { EditDataModelBindings } from './editModal/EditDataModelBindings'; import { EditBooleanValue } from './editModal/EditBooleanValue'; import { EditNumberValue } from './editModal/EditNumberValue'; -import { EditOptions } from './editModal/EditOptions'; import { EditStringValue } from './editModal/EditStringValue'; import { useText } from '../../hooks'; import { getComponentPropertyLabel } from '../../utils/language'; @@ -102,13 +101,6 @@ export const FormComponentConfig = ({ {'Andre innstillinger'}
)} - {options && optionsId && ( - - )} {hasCustomFileEndings && ( <> diff --git a/frontend/packages/ux-editor/src/components/config/componentConfig.tsx b/frontend/packages/ux-editor/src/components/config/componentConfig.tsx index bce1ee564b7..c5ad294e27b 100644 --- a/frontend/packages/ux-editor/src/components/config/componentConfig.tsx +++ b/frontend/packages/ux-editor/src/components/config/componentConfig.tsx @@ -2,7 +2,6 @@ import { ComponentType } from 'app-shared/types/ComponentType'; import { EditCodeList } from './editModal/EditCodeList'; import { EditDataModelBindings } from './editModal/EditDataModelBindings'; import { EditHeaderSize } from './editModal/EditHeaderSize'; -import { EditOptions } from './editModal/EditOptions'; import { EditPreselectedIndex } from './editModal/EditPreselectedIndex'; import { EditReadOnly } from './editModal/EditReadOnly'; import { EditRequired } from './editModal/EditRequired'; @@ -46,19 +45,10 @@ export const componentSpecificEditConfig: IComponentEditConfig = { [ComponentType.Input]: [...editBoilerPlate, EditSettings.AutoComplete], [ComponentType.TextArea]: [...editBoilerPlate, EditSettings.AutoComplete], [ComponentType.Datepicker]: [...editBoilerPlate], - [ComponentType.Checkboxes]: [ - ...editBoilerPlate, - EditSettings.Options, - EditSettings.PreselectedIndex, - ], - [ComponentType.RadioButtons]: [ - ...editBoilerPlate, - EditSettings.Options, - EditSettings.PreselectedIndex, - ], + [ComponentType.Checkboxes]: [...editBoilerPlate, EditSettings.PreselectedIndex], + [ComponentType.RadioButtons]: [...editBoilerPlate, EditSettings.PreselectedIndex], [ComponentType.Dropdown]: [ ...editBoilerPlate, - EditSettings.CodeList, EditSettings.PreselectedIndex, EditSettings.AutoComplete, ], @@ -71,7 +61,6 @@ export const configComponents: IConfigComponents = { [EditSettings.Size]: EditHeaderSize, [EditSettings.ReadOnly]: EditReadOnly, [EditSettings.Required]: EditRequired, - [EditSettings.Options]: EditOptions, [EditSettings.CodeList]: EditCodeList, [EditSettings.PreselectedIndex]: EditPreselectedIndex, [EditSettings.AutoComplete]: EditAutoComplete, diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList.test.tsx index 4c6b09eb2cb..dafd4675b28 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList.test.tsx @@ -9,6 +9,7 @@ import { } from '../../../testing/mocks'; import { useLayoutSchemaQuery } from '../../../hooks/queries/useLayoutSchemaQuery'; import userEvent from '@testing-library/user-event'; +import { textMock } from '../../../../../../testing/mocks/i18nMock'; describe('EditCodeList', () => { it('should render the component', async () => { @@ -19,7 +20,11 @@ describe('EditCodeList', () => { .mockImplementation(() => Promise.resolve(optionListIdsMock)), }, }); - expect(await screen.findByText('Bytt til egendefinert kodeliste')).toBeInTheDocument(); + expect( + await screen.findByText( + textMock('ux_editor.properties_panel.options.codelist_switch_to_custom'), + ), + ).toBeInTheDocument(); }); it('should render the component when optionListIds is undefined', async () => { @@ -31,7 +36,11 @@ describe('EditCodeList', () => { }, }); - expect(await screen.findByText('Bytt til egendefinert kodeliste')).toBeInTheDocument(); + expect( + await screen.findByText( + textMock('ux_editor.properties_panel.options.codelist_switch_to_custom'), + ), + ).toBeInTheDocument(); }); it('should call onChange when option list changes', async () => { diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList.tsx index 3a034f064bf..c291bfa3531 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import { LegacySelect, Textfield } from '@digdir/design-system-react'; +import React, { useEffect, useState } from 'react'; +import { Alert, LegacySelect, Textfield } from '@digdir/design-system-react'; import type { IGenericEditComponent } from '../componentConfig'; import { useOptionListIdsQuery } from '../../../hooks/queries/useOptionListIdsQuery'; import { useTranslation, Trans } from 'react-i18next'; @@ -22,6 +22,15 @@ export function EditCodeList({ component, handleComponentChange }: IGenericEditC }); }; + useEffect(() => { + if (!optionListIds) return; + if (optionListIds?.length === 0) { + setUseCustomCodeList(true); + } else { + setUseCustomCodeList(false); + } + }, [isPending, optionListIds]); + return (
{isPending ? ( @@ -31,7 +40,7 @@ export function EditCodeList({ component, handleComponentChange }: IGenericEditC {error instanceof Error ? error.message : t('ux_editor.modal_properties_error_message')} ) : optionListIds?.length === 0 ? ( - {t('ux_editor.modal_properties_no_options_found_message')} + {t('ux_editor.modal_properties_no_options_found_message')} ) : ( <>

@@ -40,8 +49,12 @@ export function EditCodeList({ component, handleComponentChange }: IGenericEditC onClick={() => setUseCustomCodeList(!useCustomCodeList)} size='small' > - {optionListIds?.length > 0 && useCustomCodeList && <>Bytt til statisk kodeliste} - {!useCustomCodeList && <>Bytt til egendefinert kodeliste} + {optionListIds?.length > 0 && useCustomCodeList && ( + <>{t('ux_editor.properties_panel.options.codelist_switch_to_static')} + )} + {!useCustomCodeList && ( + <>{t('ux_editor.properties_panel.options.codelist_switch_to_custom')} + )}

diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.module.css b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.module.css index 4935b9a81bd..f7b28d29607 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.module.css +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.module.css @@ -15,3 +15,10 @@ flex-direction: column; gap: 24px; } + +.codeListSwitchWrapper { + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; +} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.test.tsx index db0cc70e49f..747d2c0f2c9 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.test.tsx @@ -40,13 +40,15 @@ describe('EditOptions', () => { it('should render', async () => { await render(); expect( - screen.getByText(textMock('ux_editor.modal_properties_add_radio_button_options')), + screen.getByText(textMock('ux_editor.properties_panel.options.use_code_list_label')), ).toBeInTheDocument(); }); it('should show code list input by default when neither options nor optionId are set', async () => { await render(); - expect(screen.getByText(textMock('ux_editor.modal_add_options_codelist'))).toBeInTheDocument(); + expect( + screen.getByText(textMock('ux_editor.modal_properties_custom_code_list_id')), + ).toBeInTheDocument(); }); it('should show manual input when component has options defined', async () => { @@ -56,7 +58,9 @@ describe('EditOptions', () => { options: [{ label: 'option1', value: 'option1' }], }, }); - expect(screen.getByText(textMock('ux_editor.modal_add_options_manual'))).toBeInTheDocument(); + expect( + screen.getByText(textMock('ux_editor.properties_panel.options.add_options')), + ).toBeInTheDocument(); }); it('should show code list input when component has optionsId defined', async () => { @@ -66,6 +70,8 @@ describe('EditOptions', () => { optionsId: 'optionsId', }, }); - expect(screen.getByText(textMock('ux_editor.modal_add_options_manual'))).toBeInTheDocument(); + expect( + screen.getByText(textMock('ux_editor.modal_properties_custom_code_list_id')), + ).toBeInTheDocument(); }); }); diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx index 53d77899c08..a971e713623 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx @@ -1,10 +1,17 @@ import React, { useEffect, useRef, useState } from 'react'; import type { IOption } from '../../../types/global'; -import { Fieldset, Radio, Textfield } from '@digdir/design-system-react'; +import { + Fieldset, + Heading, + HelpText, + Paragraph, + Switch, + Textfield, +} from '@digdir/design-system-react'; import classes from './EditOptions.module.css'; import type { IGenericEditComponent } from '../componentConfig'; import { EditCodeList } from './EditCodeList'; -import { PlusIcon, XMarkIcon } from '@navikt/aksel-icons'; +import { PlusIcon, TrashIcon } from '@navikt/aksel-icons'; import { TextResource } from '../../TextResource'; import { useText, useComponentErrorMessage } from '../../../hooks'; import { addOptionToComponent, generateRandomOption } from '../../../utils/component'; @@ -56,19 +63,19 @@ export function EditOptions({ }, [editFormId, initialSelectedOptionType]); const handleOptionsTypeChange = (value: SelectedOptionsType) => { - setSelectedOptionsType(value); if (value === SelectedOptionsType.CodeList) { - delete component.options; + setSelectedOptionsType(SelectedOptionsType.Manual); + delete component.optionsId; handleComponentChange({ ...component, - optionsId: '', + options: [], }); - } - if (value === SelectedOptionsType.Manual) { - delete component.optionsId; + } else { + setSelectedOptionsType(SelectedOptionsType.CodeList); + delete component.options; handleComponentChange({ ...component, - options: [], + optionsId: '', }); } }; @@ -100,84 +107,88 @@ export function EditOptions({ }); }; - const handleAddOption = () => + const handleAddOption = () => { handleComponentChange(addOptionToComponent(component, generateRandomOption())); + }; return ( <> - - - {t('ux_editor.modal_add_options_codelist')} - - {t('ux_editor.modal_add_options_manual')} - +
+ handleOptionsTypeChange(selectedOptionsType)} + > + {t('ux_editor.properties_panel.options.use_code_list_label')} + + + {t('ux_editor.properties_panel.options.use_code_list_helptext')} + +
{selectedOptionsType === SelectedOptionsType.CodeList && ( )} {selectedOptionsType === SelectedOptionsType.Manual && ( - ( -
- {component.options?.map((option, index) => { - const updateValue = (e: any) => handleUpdateOptionValue(index, e); - const removeItem = () => handleRemoveOption(index); - const key = `${option.label}-${index}`; // Figure out a way to remove index from key. - const optionTitle = `${ - component.type === 'RadioButtons' - ? t('ux_editor.modal_radio_button_increment') - : t('ux_editor.modal_check_box_increment') - } ${index + 1}`; - return ( -
-
-
-
- -
- + + {t('ux_editor.properties_panel.options.add_options')} + + ( +
+ {component.options?.map((option, index) => { + const updateValue = (e: any) => handleUpdateOptionValue(index, e); + const removeItem = () => handleRemoveOption(index); + const key = `${option.label}-${index}`; // Figure out a way to remove index from key. + const optionTitle = `${ + component.type === 'RadioButtons' + ? t('ux_editor.modal_radio_button_increment') + : t('ux_editor.modal_check_box_increment') + } ${index + 1}`; + return ( +
+
+
+
+ +
+ +
-
-
-
-
- } - onClick={removeItem} - variant='tertiary' - size='small' - /> + +
+
+ } + onClick={removeItem} + variant='tertiary' + size='small' + /> +
-
- ); - })} -
- )} - /> + ); + })} +
+ )} + /> + )} {selectedOptionsType === SelectedOptionsType.Manual && ( diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.test.tsx index 9d63f97fdee..a911055e1e6 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.test.tsx @@ -51,7 +51,7 @@ describe('EditTextResourceBindings component', () => { const textResourceBindingKeys = ['title', 'description', 'help']; await renderEditTextResourceBindingsComponent({ textResourceBindingKeys }); const label = screen.getByText( - textMock('[mockedText(ux_editor.modal_properties_textResourceBindings_title)]'), + textMock('ux_editor.modal_properties_textResourceBindings_title'), ); expect(label).toBeInTheDocument(); const labelText = screen.getByText('This is a test text'); diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx index b5622ad4a0f..1dec9c2a418 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx @@ -1,7 +1,6 @@ import React, { useMemo, useState } from 'react'; import { EditTextResourceBinding } from './EditTextResourceBinding'; import classes from './EditTextResourceBindings.module.css'; -import { useTranslation } from 'react-i18next'; import type { FormContainer } from '../../../types/FormContainer'; import type { FormComponent } from '../../../types/FormComponent'; @@ -18,8 +17,6 @@ export const EditTextResourceBindings = ({ handleComponentChange, textResourceBindingKeys, }: EditTextResourceBindingsProps) => { - const { t } = useTranslation(); - const [keysSet, setKeysSet] = useState( Object.keys(textResourceBindingKeys || component.textResourceBindings || {}), ); @@ -42,8 +39,8 @@ export const EditTextResourceBindings = ({ handleComponentChange={handleComponentChange} removeTextResourceBinding={() => handleRemoveKey(key)} textKey={key} - labelKey={t(`ux_editor.modal_properties_textResourceBindings_${key}`)} - placeholderKey={t(`ux_editor.modal_properties_textResourceBindings_${key}_add`)} + labelKey={`ux_editor.modal_properties_textResourceBindings_${key}` as any} + placeholderKey={`ux_editor.modal_properties_textResourceBindings_${key}_add` as any} /> ))}
diff --git a/frontend/packages/ux-editor/src/testing/layoutMock.ts b/frontend/packages/ux-editor/src/testing/layoutMock.ts index de115de25ff..4712337df85 100644 --- a/frontend/packages/ux-editor/src/testing/layoutMock.ts +++ b/frontend/packages/ux-editor/src/testing/layoutMock.ts @@ -30,6 +30,13 @@ export const component2Mock: FormComponent = { itemType: 'COMPONENT', pageIndex: null, }; +export const componentWithOptionsMock: FormComponent = { + id: 'ComponentWithOptions', + type: ComponentType.Checkboxes, + itemType: 'COMPONENT', + pageIndex: null, + optionsId: '', +}; export const container1IdMock = 'Container-1'; export const customRootPropertiesMock: KeyValuePairs = { someCustomRootProp: 'someStringValue', @@ -43,6 +50,7 @@ export const layoutMock: IInternalLayout = { components: { [component1IdMock]: component1Mock, [component2IdMock]: component2Mock, + componentWithOptionsMock, }, containers: { [baseContainerIdMock]: { From 29a3fcefa2df064f7cf4a29a6f4958868c4bb4d9 Mon Sep 17 00:00:00 2001 From: Nina Kylstad Date: Mon, 5 Feb 2024 16:30:34 +0100 Subject: [PATCH 09/18] update layoutMock --- .../packages/ux-editor/src/testing/layoutMock.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/packages/ux-editor/src/testing/layoutMock.ts b/frontend/packages/ux-editor/src/testing/layoutMock.ts index 4712337df85..22e7126bc48 100644 --- a/frontend/packages/ux-editor/src/testing/layoutMock.ts +++ b/frontend/packages/ux-editor/src/testing/layoutMock.ts @@ -31,11 +31,12 @@ export const component2Mock: FormComponent = { pageIndex: null, }; export const componentWithOptionsMock: FormComponent = { - id: 'ComponentWithOptions', + id: 'ComponentWithOptionsMock', type: ComponentType.Checkboxes, itemType: 'COMPONENT', pageIndex: null, optionsId: '', + propertyPath: 'definitions/radioAndCheckboxComponents', }; export const container1IdMock = 'Container-1'; export const customRootPropertiesMock: KeyValuePairs = { @@ -50,7 +51,7 @@ export const layoutMock: IInternalLayout = { components: { [component1IdMock]: component1Mock, [component2IdMock]: component2Mock, - componentWithOptionsMock, + ComponentWithOptionsMock: componentWithOptionsMock, }, containers: { [baseContainerIdMock]: { @@ -69,7 +70,7 @@ export const layoutMock: IInternalLayout = { }, }, order: { - [baseContainerIdMock]: [container1IdMock], + [baseContainerIdMock]: [container1IdMock, 'ComponentWithOptionsMock'], [container1IdMock]: [component1IdMock, component2IdMock], }, customRootProperties: customRootPropertiesMock, @@ -93,6 +94,11 @@ export const layout1Mock: ExternalFormLayout = { id: component2IdMock, type: component2TypeMock, }, + { + id: 'ComponentWithOptionsMock', + type: ComponentType.Checkboxes, + optionsId: '', + }, ], ...customDataPropertiesMock, }, From 52b82945201de41cee5c7b95011db52ff6f6ad26 Mon Sep 17 00:00:00 2001 From: Nina Kylstad Date: Mon, 5 Feb 2024 16:36:01 +0100 Subject: [PATCH 10/18] add back text needed for v3 --- frontend/language/src/nb.json | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index c5aeb0df9ae..372828bd7a7 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -1543,6 +1543,7 @@ "ux_editor.modal_options": "Valg", "ux_editor.modal_properties_add_check_box_options": "Hvordan vil du legge til avkrysningsbokser?", "ux_editor.modal_properties_add_options": "Hvordan vil du legge til alternativer?", + "ux_editor.modal_properties_add_radio_button_options": "Hvordan vil du legge til radioknapper?", "ux_editor.modal_properties_button_helper": "Søk etter tekst til knappen", "ux_editor.modal_properties_button_type_ActionButton": "ActionButton", "ux_editor.modal_properties_button_type_InstantiationButton": "InstantiationButton", From 12324e28e6af82d82fe6059710cbc7a05250feae Mon Sep 17 00:00:00 2001 From: Nina Kylstad Date: Mon, 5 Feb 2024 17:33:22 +0100 Subject: [PATCH 11/18] cleanup and update tests --- .../ux-editor/src/components/Properties/Text.test.tsx | 6 +++--- .../packages/ux-editor/src/components/Properties/Text.tsx | 3 +++ .../ux-editor/src/components/config/componentConfig.tsx | 1 - .../src/components/config/editModal/EditOptions.tsx | 5 +++++ .../mutations/useUpdateFormComponentOrderMutation.test.ts | 1 + 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx b/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx index 4f4f17ce0a8..e6df9548e60 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Text.test.tsx @@ -190,7 +190,7 @@ describe('TextTab', () => { props: { ...props, form: { - ...layoutMock.components.componentWithOptionsMock, + ...layoutMock.components.ComponentWithOptionsMock, optionsId: 'optionsId', }, }, @@ -208,7 +208,7 @@ describe('TextTab', () => { props: { ...props, form: { - ...layoutMock.components.componentWithOptionsMock, + ...layoutMock.components.ComponentWithOptionsMock, optionsId: 'optionsId', }, }, @@ -224,7 +224,7 @@ describe('TextTab', () => { props: { ...props, form: { - ...layoutMock.components.componentWithOptionsMock, + ...layoutMock.components.ComponentWithOptionsMock, options: [{ label: labelTextId, value: 'value' }], }, }, diff --git a/frontend/packages/ux-editor/src/components/Properties/Text.tsx b/frontend/packages/ux-editor/src/components/Properties/Text.tsx index e7deb10468e..836e8883641 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Text.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Text.tsx @@ -58,6 +58,9 @@ export const Text = () => { }} editFormId={formId} layoutName={selectedLayout} + renderOptions={{ + onlyCodeListOptions: schema.properties.optionsId && !schema.properties.options, + }} /> )} diff --git a/frontend/packages/ux-editor/src/components/config/componentConfig.tsx b/frontend/packages/ux-editor/src/components/config/componentConfig.tsx index c5ad294e27b..1bcd263d460 100644 --- a/frontend/packages/ux-editor/src/components/config/componentConfig.tsx +++ b/frontend/packages/ux-editor/src/components/config/componentConfig.tsx @@ -52,7 +52,6 @@ export const componentSpecificEditConfig: IComponentEditConfig = { EditSettings.PreselectedIndex, EditSettings.AutoComplete, ], - [ComponentType.FileUploadWithTag]: [EditSettings.CodeList], [ComponentType.Map]: [...editBoilerPlate], }; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx index a971e713623..4a5ad76bf69 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx @@ -47,6 +47,7 @@ export function EditOptions({ editFormId, component, handleComponentChange, + renderOptions, }: ISelectionEditComponentProvidedProps) { const previousEditFormId = useRef(editFormId); const initialSelectedOptionType = getSelectedOptionsType(component.optionsId, component.options); @@ -111,6 +112,10 @@ export function EditOptions({ handleComponentChange(addOptionToComponent(component, generateRandomOption())); }; + if (renderOptions?.onlyCodeListOptions) { + return ; + } + return ( <>
diff --git a/frontend/packages/ux-editor/src/hooks/mutations/useUpdateFormComponentOrderMutation.test.ts b/frontend/packages/ux-editor/src/hooks/mutations/useUpdateFormComponentOrderMutation.test.ts index 928e9222801..2e20a7b0342 100644 --- a/frontend/packages/ux-editor/src/hooks/mutations/useUpdateFormComponentOrderMutation.test.ts +++ b/frontend/packages/ux-editor/src/hooks/mutations/useUpdateFormComponentOrderMutation.test.ts @@ -45,6 +45,7 @@ describe('useUpdateFormComponentOrderMutation', () => { data: expect.objectContaining({ layout: [ expect.objectContaining({ id: container1IdMock }), + expect.objectContaining({ id: 'ComponentWithOptionsMock' }), expect.objectContaining({ id: component2IdMock }), expect.objectContaining({ id: component1IdMock }), ], From d5beeaa15c7087fafd45eecc6f07f0fca21ab989 Mon Sep 17 00:00:00 2001 From: Nina Kylstad Date: Tue, 6 Feb 2024 08:17:39 +0100 Subject: [PATCH 12/18] refactor EditTextResourceBindings and small cleanup --- .../EditCodeList/EditCodeList.module.css | 3 ++ .../{ => EditCodeList}/EditCodeList.test.tsx | 6 +-- .../{ => EditCodeList}/EditCodeList.tsx | 8 ++-- .../config/editModal/EditCodeList/index.ts | 1 + .../editModal/EditTextResourceBinding.tsx | 4 +- .../EditTextResourceBindings.test.tsx | 43 +++++++++++++------ .../editModal/EditTextResourceBindings.tsx | 24 +++++------ 7 files changed, 54 insertions(+), 35 deletions(-) create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/EditCodeList.module.css rename frontend/packages/ux-editor/src/components/config/editModal/{ => EditCodeList}/EditCodeList.test.tsx (93%) rename frontend/packages/ux-editor/src/components/config/editModal/{ => EditCodeList}/EditCodeList.tsx (91%) create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/index.ts diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/EditCodeList.module.css b/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/EditCodeList.module.css new file mode 100644 index 00000000000..1a8fb99df9d --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/EditCodeList.module.css @@ -0,0 +1,3 @@ +.customOrStaticButton { + padding: 0; +} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/EditCodeList.test.tsx similarity index 93% rename from frontend/packages/ux-editor/src/components/config/editModal/EditCodeList.test.tsx rename to frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/EditCodeList.test.tsx index dafd4675b28..73b3c932a1f 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/EditCodeList.test.tsx @@ -6,10 +6,10 @@ import { renderWithMockStore, renderHookWithMockStore, optionListIdsMock, -} from '../../../testing/mocks'; -import { useLayoutSchemaQuery } from '../../../hooks/queries/useLayoutSchemaQuery'; +} from '../../../../testing/mocks'; +import { useLayoutSchemaQuery } from '../../../../hooks/queries/useLayoutSchemaQuery'; import userEvent from '@testing-library/user-event'; -import { textMock } from '../../../../../../testing/mocks/i18nMock'; +import { textMock } from '../../../../../../../testing/mocks/i18nMock'; describe('EditCodeList', () => { it('should render the component', async () => { diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/EditCodeList.tsx similarity index 91% rename from frontend/packages/ux-editor/src/components/config/editModal/EditCodeList.tsx rename to frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/EditCodeList.tsx index c291bfa3531..f2c5a4c4364 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/EditCodeList.tsx @@ -1,13 +1,14 @@ import React, { useEffect, useState } from 'react'; import { Alert, LegacySelect, Textfield } from '@digdir/design-system-react'; -import type { IGenericEditComponent } from '../componentConfig'; -import { useOptionListIdsQuery } from '../../../hooks/queries/useOptionListIdsQuery'; +import type { IGenericEditComponent } from '../../componentConfig'; +import { useOptionListIdsQuery } from '../../../../hooks/queries/useOptionListIdsQuery'; import { useTranslation, Trans } from 'react-i18next'; import { StudioButton, StudioSpinner } from '@studio/components'; import { ErrorMessage } from '@digdir/design-system-react'; import { altinnDocsUrl } from 'app-shared/ext-urls'; -import { FormField } from '../../FormField'; +import { FormField } from '../../../FormField'; import { useStudioUrlParams } from 'app-shared/hooks/useStudioUrlParams'; +import classes from './EditCodeList.module.css'; export function EditCodeList({ component, handleComponentChange }: IGenericEditComponent) { const { t } = useTranslation(); @@ -48,6 +49,7 @@ export function EditCodeList({ component, handleComponentChange }: IGenericEditC variant='tertiary' onClick={() => setUseCustomCodeList(!useCustomCodeList)} size='small' + className={classes.customOrStaticButton} > {optionListIds?.length > 0 && useCustomCodeList && ( <>{t('ux_editor.properties_panel.options.codelist_switch_to_static')} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/index.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/index.ts new file mode 100644 index 00000000000..951b645ee4a --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/index.ts @@ -0,0 +1 @@ +export { EditCodeList } from './EditCodeList'; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBinding.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBinding.tsx index 522961cd8dc..454d93e83ef 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBinding.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBinding.tsx @@ -4,9 +4,9 @@ import { TextResource } from '../../TextResource'; import type { TranslationKey } from 'language/type'; import type { IAppState } from '../../../types/global'; import { useTranslation } from 'react-i18next'; -import type { EditTextResourceBindingsProps } from './EditTextResourceBindings'; +import type { EditTextResourceBindingBase } from './EditTextResourceBindings'; -export interface EditTextResourceBindingProps extends EditTextResourceBindingsProps { +export interface EditTextResourceBindingProps extends EditTextResourceBindingBase { textKey: string; labelKey: TranslationKey; descriptionKey?: TranslationKey; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.test.tsx index a911055e1e6..e36725c31f3 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.test.tsx @@ -35,11 +35,11 @@ describe('EditTextResourceBindings component', () => { const textResources: ITextResource[] = [ { id: 'test-title-text-id', - value: 'This is a test text', + value: 'This is a test title', }, { id: 'test-desc-text-id', - value: 'This is a test desc ', + value: 'This is a test description ', }, { id: 'test-help-text-id', @@ -50,30 +50,47 @@ describe('EditTextResourceBindings component', () => { test('that it renders with expected text resource binding keys', async () => { const textResourceBindingKeys = ['title', 'description', 'help']; await renderEditTextResourceBindingsComponent({ textResourceBindingKeys }); - const label = screen.getByText( - textMock('ux_editor.modal_properties_textResourceBindings_title'), - ); - expect(label).toBeInTheDocument(); - const labelText = screen.getByText('This is a test text'); + + textResourceBindingKeys.forEach((key) => { + expect( + screen.getByText(textMock(`ux_editor.modal_properties_textResourceBindings_${key}`)), + ).toBeInTheDocument(); + }); + + const labelText = screen.getByText('This is a test title'); expect(labelText).toBeInTheDocument(); - const descText = screen.getByText('This is a test desc'); + const descText = screen.getByText('This is a test description'); expect(descText).toBeInTheDocument(); const helpText = screen.getByText('This is a test help text'); expect(helpText).toBeInTheDocument(); }); - test('that it renders no text resource bindings if none are added', async () => { + test('that it renders no text resource bindings if no keys are provided', async () => { await renderEditTextResourceBindingsComponent({ + textResourceBindingKeys: [], component: { ...mockComponent, textResourceBindings: {} }, }); - const titleLabel = screen.queryByText( - textMock(`ux_editor.modal_properties_textResourceBindings_title`), - ); - expect(titleLabel).not.toBeInTheDocument(); + expect( + screen.queryByText(textMock('ux_editor.modal_properties_textResourceBindings_title')), + ).not.toBeInTheDocument(); const searchTextButton = screen.queryByRole('button', { name: textMock('general.search') }); expect(searchTextButton).not.toBeInTheDocument(); }); + test('that it renders empty text resource bindings if component has no text resource bindings', async () => { + const textResourceBindingKeys = ['title', 'description', 'help']; + await renderEditTextResourceBindingsComponent({ + textResourceBindingKeys, + component: { ...mockComponent, textResourceBindings: {} }, + }); + + textResourceBindingKeys.forEach((key) => { + expect( + screen.getByText(textMock(`ux_editor.modal_properties_textResourceBindings_${key}_add`)), + ).toBeInTheDocument(); + }); + }); + const waitForData = async () => { const layoutSchemaResult = renderHookWithMockStore()(() => useLayoutSchemaQuery()) .renderHookResult.result; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx index 1dec9c2a418..7a6784d1950 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx @@ -1,38 +1,34 @@ -import React, { useMemo, useState } from 'react'; +import React from 'react'; import { EditTextResourceBinding } from './EditTextResourceBinding'; import classes from './EditTextResourceBindings.module.css'; import type { FormContainer } from '../../../types/FormContainer'; import type { FormComponent } from '../../../types/FormComponent'; -export interface EditTextResourceBindingsProps { +export interface EditTextResourceBindingBase { editFormId?: string; component: FormComponent | FormContainer; handleComponentChange: (component: FormComponent | FormContainer) => void; - textResourceBindingKeys?: string[]; layoutName?: string; } +export interface EditTextResourceBindingsProps extends EditTextResourceBindingBase { + textResourceBindingKeys: string[]; +} + export const EditTextResourceBindings = ({ component, handleComponentChange, textResourceBindingKeys, }: EditTextResourceBindingsProps) => { - const [keysSet, setKeysSet] = useState( - Object.keys(textResourceBindingKeys || component.textResourceBindings || {}), - ); - - const keysToAdd = useMemo( - () => textResourceBindingKeys.filter((key) => !keysSet.includes(key)), - [keysSet, textResourceBindingKeys], - ); - const handleRemoveKey = (key: string) => { - setKeysSet((prevKeysSet) => prevKeysSet.filter((k) => k !== key)); + const updatedComponent = { ...component }; + delete updatedComponent.textResourceBindings[key]; + handleComponentChange(updatedComponent); }; return (
- {keysToAdd.map((key: string) => ( + {textResourceBindingKeys.map((key: string) => ( Date: Tue, 6 Feb 2024 08:37:58 +0100 Subject: [PATCH 13/18] remove unneccessary code and add test --- .../components/Properties/Properties.test.tsx | 24 +++++++++++++------ .../editModal/EditTextResourceBindings.tsx | 7 ------ 2 files changed, 17 insertions(+), 14 deletions(-) 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 7e6d53f4739..fba7b3abe18 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Properties.test.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Properties.test.tsx @@ -45,6 +45,23 @@ jest.mock('./Calculations', () => ({ jest.mock('react-i18next', () => ({ useTranslation: () => mockUseTranslation(texts) })); describe('Properties', () => { + describe('Text', () => { + it('Toggles text when clicked', async () => { + render(); + const button = screen.queryByRole('button', { name: textText }); + await act(() => user.click(button)); + expect(button).toHaveAttribute('aria-expanded', 'true'); + await act(() => user.click(button)); + expect(button).toHaveAttribute('aria-expanded', 'false'); + }); + + it('Opens text when a component is selected', async () => { + const { rerender } = render(); + rerender(getComponent({ formId: 'test' })); + const button = screen.queryByRole('button', { name: textText }); + await waitFor(() => expect(button).toHaveAttribute('aria-expanded', 'true')); + }); + }); describe('Content', () => { it('Closes content on load', () => { render(); @@ -60,13 +77,6 @@ describe('Properties', () => { await act(() => user.click(button)); expect(button).toHaveAttribute('aria-expanded', 'false'); }); - - it('Opens text when a component is selected', async () => { - const { rerender } = render(); - rerender(getComponent({ formId: 'test' })); - const button = screen.queryByRole('button', { name: textText }); - await waitFor(() => expect(button).toHaveAttribute('aria-expanded', 'true')); - }); }); describe('Dynamics', () => { diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx index 7a6784d1950..7eb7314df82 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditTextResourceBindings.tsx @@ -20,12 +20,6 @@ export const EditTextResourceBindings = ({ handleComponentChange, textResourceBindingKeys, }: EditTextResourceBindingsProps) => { - const handleRemoveKey = (key: string) => { - const updatedComponent = { ...component }; - delete updatedComponent.textResourceBindings[key]; - handleComponentChange(updatedComponent); - }; - return (
{textResourceBindingKeys.map((key: string) => ( @@ -33,7 +27,6 @@ export const EditTextResourceBindings = ({ key={key} component={component} handleComponentChange={handleComponentChange} - removeTextResourceBinding={() => handleRemoveKey(key)} textKey={key} labelKey={`ux_editor.modal_properties_textResourceBindings_${key}` as any} placeholderKey={`ux_editor.modal_properties_textResourceBindings_${key}_add` as any} From c2bbc2c69da438cd2c21b520e65b4b2cf3714991 Mon Sep 17 00:00:00 2001 From: Nina Kylstad Date: Tue, 6 Feb 2024 15:31:07 +0100 Subject: [PATCH 14/18] more tests --- frontend/language/src/nb.json | 1 + .../config/editModal/EditOptions.test.tsx | 67 ++++++++++++++++++- .../config/editModal/EditOptions.tsx | 1 + 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 579b5dc7791..010478d3802 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -1676,6 +1676,7 @@ "ux_editor.properties_panel.options.add_options": "Legg til alternativer", "ux_editor.properties_panel.options.codelist_switch_to_custom": "Bytt til egendefinert kodeliste", "ux_editor.properties_panel.options.codelist_switch_to_static": "Bytt til statisk kodeliste", + "ux_editor.properties_panel.options.remove_option": "Slett alternativ", "ux_editor.properties_panel.options.use_code_list_helptext": "Skru på denne innstillingen for å bruke en kodeliste for å styre alternativene til denne komponenten. Skru av innstillingen for å legge til alternativene manuelt.", "ux_editor.properties_panel.options.use_code_list_label": "Bruk kodeliste", "ux_editor.properties_panel.texts.options_title": "Kodelister og alternativer", diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.test.tsx index 747d2c0f2c9..5665f0c8309 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { screen, waitFor } from '@testing-library/react'; +import { act, screen, waitFor } from '@testing-library/react'; import { EditOptions } from './EditOptions'; import { renderWithMockStore, renderHookWithMockStore } from '../../../testing/mocks'; @@ -56,11 +56,15 @@ describe('EditOptions', () => { component: { ...mockComponent, options: [{ label: 'option1', value: 'option1' }], + optionsId: undefined, }, }); expect( screen.getByText(textMock('ux_editor.properties_panel.options.add_options')), ).toBeInTheDocument(); + expect( + screen.getByText(textMock('ux_editor.modal_radio_button_increment') + ' 1'), + ).toBeInTheDocument(); }); it('should show code list input when component has optionsId defined', async () => { @@ -74,4 +78,65 @@ describe('EditOptions', () => { screen.getByText(textMock('ux_editor.modal_properties_custom_code_list_id')), ).toBeInTheDocument(); }); + + it('should switch to manual input when toggling codelist switch off', async () => { + const handleComponentChange = jest.fn(); + await render({ handleComponentChange }); + const switchElement = screen.getByRole('checkbox'); + await act(() => switchElement.click()); + expect(handleComponentChange).toHaveBeenCalledWith({ ...mockComponent, options: [] }); + }); + + it('should switch to codelist input when toggling codelist switch on', async () => { + const handleComponentChange = jest.fn(); + await render({ + handleComponentChange, + component: { + ...mockComponent, + options: [{ label: 'option1', value: 'option1' }], + optionsId: undefined, + }, + }); + const switchElement = screen.getByRole('checkbox'); + await act(() => switchElement.click()); + expect(handleComponentChange).toHaveBeenCalledWith({ ...mockComponent, optionsId: '' }); + }); + + it('should update component options when adding new option', async () => { + const handleComponentChange = jest.fn(); + await render({ + handleComponentChange, + component: { + ...mockComponent, + options: [{ label: 'option1', value: 'option1' }], + }, + }); + const addOptionButton = screen.getByRole('button', { + name: textMock('ux_editor.modal_new_option'), + }); + await act(() => addOptionButton.click()); + expect(handleComponentChange).toHaveBeenCalledWith({ + ...mockComponent, + options: expect.arrayContaining([ + { label: 'option1', value: 'option1' }, + expect.objectContaining({ label: '' }), + ]), + }); + }); + + it('should update component options when removing option', async () => { + const handleComponentChange = jest.fn(); + await render({ + handleComponentChange, + component: { + ...mockComponent, + options: [{ label: 'option1', value: 'option1' }], + }, + }); + const removeOptionButton = screen.getByRole('button', { + name: textMock('ux_editor.properties_panel.options.remove_option'), + }); + await act(() => removeOptionButton.click()); + expect(handleComponentChange).toHaveBeenCalledWith({ ...mockComponent, options: [] }); + }); }); diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx index 4a5ad76bf69..51cac72fb04 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx @@ -185,6 +185,7 @@ export function EditOptions({ onClick={removeItem} variant='tertiary' size='small' + title={t('ux_editor.properties_panel.options.remove_option')} />
From a0ed0d8ae5edc1fa9ab6bb5fb6bc3bf00749b06b Mon Sep 17 00:00:00 2001 From: Nina Kylstad Date: Wed, 7 Feb 2024 16:08:56 +0100 Subject: [PATCH 15/18] pr comments --- .../src/types/ComponentSpecificConfig.ts | 2 +- .../src/components/Properties/Text.tsx | 17 ++++++-- .../src/components/TextResource.module.css | 2 +- .../editModal/EditCodeList/EditCodeList.tsx | 8 +--- .../config/editModal/EditOptions.tsx | 40 +++++++++++-------- 5 files changed, 42 insertions(+), 27 deletions(-) diff --git a/frontend/packages/shared/src/types/ComponentSpecificConfig.ts b/frontend/packages/shared/src/types/ComponentSpecificConfig.ts index e9b92febf4b..8e577dcffd9 100644 --- a/frontend/packages/shared/src/types/ComponentSpecificConfig.ts +++ b/frontend/packages/shared/src/types/ComponentSpecificConfig.ts @@ -8,7 +8,7 @@ type Option = { value: T; }; -type OptionsComponentBase = { +export type OptionsComponentBase = { options?: Option[]; preselectedOptionIndex?: number; optionsId?: string; diff --git a/frontend/packages/ux-editor/src/components/Properties/Text.tsx b/frontend/packages/ux-editor/src/components/Properties/Text.tsx index 836e8883641..867ad155cde 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Text.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Text.tsx @@ -12,22 +12,29 @@ import { getCurrentEditId } from '../../selectors/textResourceSelectors'; import { TextResourceEdit } from '../TextResourceEdit'; import { EditOptions } from '../config/editModal/EditOptions'; import classes from './Text.module.css'; +import type { FormComponentBase } from '../../types/FormComponent'; +import type { ComponentType } from 'app-shared/types/ComponentType'; +import type { OptionsComponentBase } from 'app-shared/types/ComponentSpecificConfig'; export const Text = () => { const { formId, form, handleUpdate, debounceSave } = useFormContext(); const { t } = useTranslation(); - useLayoutSchemaQuery(); // Ensure we load the layout schemas so that component schemas can be loaded + useLayoutSchemaQuery(); // Component schema query is dependent on the data loaded by the layout schema query const { data: schema } = useComponentSchemaQuery(form.type); const selectedLayout = useSelector(selectedLayoutNameSelector); const editId = useSelector(getCurrentEditId); if (editId) return ; + + if (!schema) { + ; + } + if (!schema?.properties) return null; return ( <> - {!schema && } {schema.properties.textResourceBindings?.properties && (
@@ -51,7 +58,11 @@ export const Text = () => { {t('ux_editor.properties_panel.texts.options_title')} & OptionsComponentBase) + | (FormComponentBase & OptionsComponentBase) + } handleComponentChange={async (updatedComponent) => { handleUpdate(updatedComponent); debounceSave(formId, updatedComponent); diff --git a/frontend/packages/ux-editor/src/components/TextResource.module.css b/frontend/packages/ux-editor/src/components/TextResource.module.css index bcfcdb0a37c..2223863c517 100644 --- a/frontend/packages/ux-editor/src/components/TextResource.module.css +++ b/frontend/packages/ux-editor/src/components/TextResource.module.css @@ -6,7 +6,7 @@ flex-direction: column; gap: 4px; margin-top: 8px; - background-color: white; + background-color: var(--fds-semantic-surface-action-first-no_fill); } .root.isEditing { diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/EditCodeList.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/EditCodeList.tsx index f2c5a4c4364..120a100b75a 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/EditCodeList.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditCodeList/EditCodeList.tsx @@ -25,12 +25,8 @@ export function EditCodeList({ component, handleComponentChange }: IGenericEditC useEffect(() => { if (!optionListIds) return; - if (optionListIds?.length === 0) { - setUseCustomCodeList(true); - } else { - setUseCustomCodeList(false); - } - }, [isPending, optionListIds]); + setUseCustomCodeList(optionListIds?.length === 0); + }, [optionListIds]); return (
diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx index 51cac72fb04..7adfad2ad27 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx @@ -36,6 +36,17 @@ export enum SelectedOptionsType { Unknown = '', } +const optionsTypeMap = { + [SelectedOptionsType.CodeList]: { + propertyName: 'optionsId', + defaultValue: '', + }, + [SelectedOptionsType.Manual]: { + propertyName: 'options', + defaultValue: [], + }, +}; + const getSelectedOptionsType = (codeListId: string, options: IOption[]): SelectedOptionsType => { if (options?.length) { return SelectedOptionsType.Manual; @@ -63,22 +74,19 @@ export function EditOptions({ } }, [editFormId, initialSelectedOptionType]); - const handleOptionsTypeChange = (value: SelectedOptionsType) => { - if (value === SelectedOptionsType.CodeList) { - setSelectedOptionsType(SelectedOptionsType.Manual); - delete component.optionsId; - handleComponentChange({ - ...component, - options: [], - }); - } else { - setSelectedOptionsType(SelectedOptionsType.CodeList); - delete component.options; - handleComponentChange({ - ...component, - optionsId: '', - }); - } + const handleOptionsTypeChange = (oldOptionsType: SelectedOptionsType) => { + const newOptionsType = + oldOptionsType === SelectedOptionsType.CodeList + ? SelectedOptionsType.Manual + : SelectedOptionsType.CodeList; + + setSelectedOptionsType(newOptionsType); + delete component[optionsTypeMap[oldOptionsType].propertyName]; + + handleComponentChange({ + ...component, + [optionsTypeMap[newOptionsType].propertyName]: optionsTypeMap[newOptionsType].defaultValue, + }); }; const handleUpdateOptionLabel = (index: number) => (id: string) => { From 5a9ee37a5f061d1fc90e85c40e3817c7b9d159fd Mon Sep 17 00:00:00 2001 From: Nina Kylstad Date: Fri, 9 Feb 2024 12:11:07 +0100 Subject: [PATCH 16/18] updates from pr comments --- frontend/language/src/nb.json | 1 + .../ux-editor/src/components/Properties/Text.tsx | 10 +++++----- .../src/components/config/EditFormComponent.tsx | 2 -- .../src/hooks/queries/useComponentSchemaQuery.ts | 2 ++ 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 010478d3802..6bdaca2f52d 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -1679,6 +1679,7 @@ "ux_editor.properties_panel.options.remove_option": "Slett alternativ", "ux_editor.properties_panel.options.use_code_list_helptext": "Skru på denne innstillingen for å bruke en kodeliste for å styre alternativene til denne komponenten. Skru av innstillingen for å legge til alternativene manuelt.", "ux_editor.properties_panel.options.use_code_list_label": "Bruk kodeliste", + "ux_editor.properties_panel.texts.no_properties": "Det er ingen tekster å konfigurere for denne komponenten.", "ux_editor.properties_panel.texts.options_title": "Kodelister og alternativer", "ux_editor.radios_description_add": "Legg til beskrivelse", "ux_editor.radios_description_placeholder": "Ingen beskrivelse", diff --git a/frontend/packages/ux-editor/src/components/Properties/Text.tsx b/frontend/packages/ux-editor/src/components/Properties/Text.tsx index 867ad155cde..ff77332807a 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Text.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Text.tsx @@ -2,9 +2,8 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { useFormContext } from '../../containers/FormContext'; import { useTranslation } from 'react-i18next'; -import { Heading } from '@digdir/design-system-react'; +import { Heading, Paragraph } from '@digdir/design-system-react'; import { EditTextResourceBindings } from '../config/editModal/EditTextResourceBindings'; -import { useLayoutSchemaQuery } from '../../hooks/queries/useLayoutSchemaQuery'; import { useComponentSchemaQuery } from '../../hooks/queries/useComponentSchemaQuery'; import { selectedLayoutNameSelector } from '../../selectors/formLayoutSelectors'; import { StudioSpinner } from '@studio/components'; @@ -20,7 +19,6 @@ export const Text = () => { const { formId, form, handleUpdate, debounceSave } = useFormContext(); const { t } = useTranslation(); - useLayoutSchemaQuery(); // Component schema query is dependent on the data loaded by the layout schema query const { data: schema } = useComponentSchemaQuery(form.type); const selectedLayout = useSelector(selectedLayoutNameSelector); const editId = useSelector(getCurrentEditId); @@ -28,10 +26,12 @@ export const Text = () => { if (editId) return ; if (!schema) { - ; + return ; } - if (!schema?.properties) return null; + if (!schema?.properties) { + return {t('ux_editor.properties_panel.texts.no_properties')}; + } return ( <> diff --git a/frontend/packages/ux-editor/src/components/config/EditFormComponent.tsx b/frontend/packages/ux-editor/src/components/config/EditFormComponent.tsx index 8dd32ed7128..de90d4fc043 100644 --- a/frontend/packages/ux-editor/src/components/config/EditFormComponent.tsx +++ b/frontend/packages/ux-editor/src/components/config/EditFormComponent.tsx @@ -11,7 +11,6 @@ import { useComponentSchemaQuery } from '../../hooks/queries/useComponentSchemaQ import { StudioSpinner } from '@studio/components'; import { FormComponentConfig } from './FormComponentConfig'; import { EditComponentId } from './editModal/EditComponentId'; -import { useLayoutSchemaQuery } from '../../hooks/queries/useLayoutSchemaQuery'; import { useSelector } from 'react-redux'; import { getComponentTitleByComponentType } from '../../utils/language'; import { useTranslation } from 'react-i18next'; @@ -41,7 +40,6 @@ export const EditFormComponent = ({ shouldDisplayFeature('componentConfigBeta'), ); - useLayoutSchemaQuery(); // Ensure we load the layout schemas so that component schemas can be loaded const { data: schema, isPending } = useComponentSchemaQuery(component.type); const renderFromComponentSpecificDefinition = (configDef: EditSettings[]) => { diff --git a/frontend/packages/ux-editor/src/hooks/queries/useComponentSchemaQuery.ts b/frontend/packages/ux-editor/src/hooks/queries/useComponentSchemaQuery.ts index 9942d1ebf5d..c750703cbfc 100644 --- a/frontend/packages/ux-editor/src/hooks/queries/useComponentSchemaQuery.ts +++ b/frontend/packages/ux-editor/src/hooks/queries/useComponentSchemaQuery.ts @@ -4,6 +4,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import { addSchemas, dereferenceSchema } from 'app-shared/utils/formValidationUtils'; import { componentSchemaMocks } from '../../testing/componentSchemaMocks'; import { QueryKey } from 'app-shared/types/QueryKey'; +import { useLayoutSchemaQuery } from './useLayoutSchemaQuery'; export interface UseComponentSchemaQueryResult { queryResults: { @@ -16,6 +17,7 @@ export interface UseComponentSchemaQueryResult { // When the schemas are available on CDN, we can remove the mocks and use the queries instead. export const useComponentSchemaQuery = (component: string): UseQueryResult => { const queryClient = useQueryClient(); + useLayoutSchemaQuery(); // Ensure that the layout schema is fetched before the component schema return useQuery({ queryKey: [QueryKey.FormComponent, component], From 82349f0fa8378f5d88b04488d4526425cec683de Mon Sep 17 00:00:00 2001 From: Nina Kylstad Date: Fri, 9 Feb 2024 12:27:33 +0100 Subject: [PATCH 17/18] cleanup after merge --- .../shared/src/types/ComponentSpecificConfig.ts | 2 +- .../ux-editor/src/components/Properties/Text.tsx | 8 +++++--- .../src/components/config/componentConfig.tsx | 1 - .../components/config/editModal/EditOptions.test.tsx | 12 +++++++----- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/frontend/packages/shared/src/types/ComponentSpecificConfig.ts b/frontend/packages/shared/src/types/ComponentSpecificConfig.ts index f5993c4ccfd..69315bc8a84 100644 --- a/frontend/packages/shared/src/types/ComponentSpecificConfig.ts +++ b/frontend/packages/shared/src/types/ComponentSpecificConfig.ts @@ -14,7 +14,7 @@ type Option = { helpText?: string; }; -export type SelectionComponent = { +type SelectionComponent = { optionsId?: string; mapping?: KeyValuePairs; queryParameters?: KeyValuePairs; diff --git a/frontend/packages/ux-editor/src/components/Properties/Text.tsx b/frontend/packages/ux-editor/src/components/Properties/Text.tsx index ff77332807a..d79b3d5a895 100644 --- a/frontend/packages/ux-editor/src/components/Properties/Text.tsx +++ b/frontend/packages/ux-editor/src/components/Properties/Text.tsx @@ -13,7 +13,7 @@ import { EditOptions } from '../config/editModal/EditOptions'; import classes from './Text.module.css'; import type { FormComponentBase } from '../../types/FormComponent'; import type { ComponentType } from 'app-shared/types/ComponentType'; -import type { OptionsComponentBase } from 'app-shared/types/ComponentSpecificConfig'; +import type { ComponentSpecificConfig } from 'app-shared/types/ComponentSpecificConfig'; export const Text = () => { const { formId, form, handleUpdate, debounceSave } = useFormContext(); @@ -60,8 +60,10 @@ export const Text = () => { & OptionsComponentBase) - | (FormComponentBase & OptionsComponentBase) + | (FormComponentBase & + ComponentSpecificConfig) + | (FormComponentBase & + ComponentSpecificConfig) } handleComponentChange={async (updatedComponent) => { handleUpdate(updatedComponent); diff --git a/frontend/packages/ux-editor/src/components/config/componentConfig.tsx b/frontend/packages/ux-editor/src/components/config/componentConfig.tsx index 07486cb5b05..aa0fe1b67c8 100644 --- a/frontend/packages/ux-editor/src/components/config/componentConfig.tsx +++ b/frontend/packages/ux-editor/src/components/config/componentConfig.tsx @@ -6,7 +6,6 @@ import { EditPreselectedIndex } from './editModal/EditPreselectedIndex'; import { EditReadOnly } from './editModal/EditReadOnly'; import { EditRequired } from './editModal/EditRequired'; import { EditAutoComplete } from './editModal/EditAutoComplete'; -import { EditTextResourceBinding } from './editModal/EditTextResourceBinding'; import type { FormItem } from '../../types/FormItem'; export interface IGenericEditComponent { diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.test.tsx index d40804fe3e1..9f27fcf3020 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.test.tsx @@ -33,7 +33,9 @@ describe('EditOptions', () => { it('should show code list input by default when neither options nor optionId are set', async () => { renderEditOptions(); - expect(screen.getByText(textMock('ux_editor.modal_properties_custom_code_list_id'))).toBeInTheDocument(); + expect( + screen.getByText(textMock('ux_editor.modal_properties_custom_code_list_id')), + ).toBeInTheDocument(); }); it('should show manual input when component has options defined', async () => { @@ -66,7 +68,7 @@ describe('EditOptions', () => { it('should switch to manual input when toggling codelist switch off', async () => { const handleComponentChange = jest.fn(); - await render({ handleComponentChange }); + renderEditOptions({ handleComponentChange }); const switchElement = screen.getByRole('checkbox'); await act(() => switchElement.click()); expect(handleComponentChange).toHaveBeenCalledWith({ ...mockComponent, options: [] }); @@ -74,7 +76,7 @@ describe('EditOptions', () => { it('should switch to codelist input when toggling codelist switch on', async () => { const handleComponentChange = jest.fn(); - await render({ + renderEditOptions({ handleComponentChange, component: { ...mockComponent, @@ -89,7 +91,7 @@ describe('EditOptions', () => { it('should update component options when adding new option', async () => { const handleComponentChange = jest.fn(); - await render({ + renderEditOptions({ handleComponentChange, component: { ...mockComponent, @@ -111,7 +113,7 @@ describe('EditOptions', () => { it('should update component options when removing option', async () => { const handleComponentChange = jest.fn(); - await render({ + renderEditOptions({ handleComponentChange, component: { ...mockComponent, From 53eb35ea42157b5630e8578357daf0ab2fa28f40 Mon Sep 17 00:00:00 2001 From: Nina Kylstad Date: Mon, 12 Feb 2024 20:01:39 +0100 Subject: [PATCH 18/18] ran prettier --- .../ux-editor/src/components/config/editModal/EditOptions.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx index 6d39fb44875..0f1be15fe13 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions.tsx @@ -59,7 +59,6 @@ export function EditOptions({ handleComponentChange, renderOptions, }: ISelectionEditComponentProvidedProps) { - const previousEditFormId = useRef(editFormId); const initialSelectedOptionType = getSelectedOptionsType(component.optionsId, component.options); const [selectedOptionsType, setSelectedOptionsType] = useState(initialSelectedOptionType);