From a789754705578b4334648d8b0a63a3bd70593316 Mon Sep 17 00:00:00 2001 From: Konrad-Simso Date: Tue, 29 Oct 2024 15:32:30 +0100 Subject: [PATCH 1/9] Added manual edit functionality for codelists. But it's not done. Still needs the following: - Tests for onClose in CodeListTableEditor.tsx - Check typing and tests overall. --- .../components/RepoList/RepoList.tsx | 2 +- frontend/language/src/nb.json | 12 +- .../types/CodeListItem.ts | 4 +- frontend/packages/shared/src/api/mutations.ts | 7 +- frontend/packages/shared/src/api/paths.js | 3 +- frontend/packages/shared/src/api/queries.ts | 3 +- frontend/packages/shared/src/mocks/mocks.ts | 9 + .../packages/shared/src/mocks/queriesMock.ts | 7 +- .../CodeListTableEditor.module.css | 8 + .../EditCodeList/CodeListTableEditor.test.tsx | 97 +++++++++++ .../EditCodeList/CodeListTableEditor.tsx | 76 ++++++++ .../EditCodeList/EditCodeList.test.tsx | 163 ++++++++---------- .../EditOptions/EditCodeList/EditCodeList.tsx | 28 +-- .../hooks/useCodeListEditorTexts.ts | 22 +++ .../utils/conversionUtils.test.ts | 42 +++++ .../EditCodeList/utils/conversionUtils.ts | 38 ++++ .../{ => utils}/findFileNameError.test.ts | 0 .../{ => utils}/findFileNameError.ts | 0 .../EditOptions/EditOptions.test.tsx | 42 +++-- .../useUpdateOptionListMutation.test.ts | 24 +++ .../mutations/useUpdateOptionListMutation.ts | 28 +++ .../src/hooks/queries/useOptionListsQuery.ts | 6 +- 22 files changed, 488 insertions(+), 133 deletions(-) create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.module.css create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/hooks/useCodeListEditorTexts.ts create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.test.ts create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.ts rename frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/{ => utils}/findFileNameError.test.ts (100%) rename frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/{ => utils}/findFileNameError.ts (100%) create mode 100644 frontend/packages/ux-editor/src/hooks/mutations/useUpdateOptionListMutation.test.ts create mode 100644 frontend/packages/ux-editor/src/hooks/mutations/useUpdateOptionListMutation.ts diff --git a/frontend/dashboard/components/RepoList/RepoList.tsx b/frontend/dashboard/components/RepoList/RepoList.tsx index e083a1e5f94..a1ef29772b7 100644 --- a/frontend/dashboard/components/RepoList/RepoList.tsx +++ b/frontend/dashboard/components/RepoList/RepoList.tsx @@ -69,7 +69,7 @@ export const RepoList = ({ }, { accessor: 'description', - heading: t('dashboard.description'), + heading: t('general.description'), sortable: true, }, { diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 760a9ae779f..14e1c144a34 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -108,7 +108,6 @@ "dashboard.created_by": "Opprettet av", "dashboard.creating_your_service": "Oppretter appen din", "dashboard.data_models": "Datamodeller", - "dashboard.description": "Beskrivelse", "dashboard.edit_app": "Endre {{appName}} i Studio", "dashboard.error_getting_organization_data.message": "Det oppsto en feil da vi skulle hente de organisasjonene som trengs for å kjøre appen.", "dashboard.error_getting_organization_data.title": "Kunne ikke laste inn organisasjoner", @@ -260,6 +259,7 @@ "general.date_time_format": "{{date}} kl. {{time}}", "general.delete": "Slett", "general.delete_item": "Slett {{item}}", + "general.description": "Beskrivelse", "general.edit": "Endre", "general.empty_string": "Tom tekst", "general.error_message": "Det har oppstått en feil. Hvis problemet fortsetter, ta kontakt med oss.", @@ -275,6 +275,7 @@ "general.loading": "Laster...", "general.next": "Neste", "general.no_options": "Ingen alternativer tilgjengelige", + "general.option": "Alternativ", "general.options": "Alternativer", "general.page": "Side", "general.page_error_message": "Vi vet ikke helt hva, men ta kontakt med oss, så graver vi i det sammen.", @@ -1456,8 +1457,15 @@ "ux_editor.modal_header_type_helper": "Velg titteltype", "ux_editor.modal_new_option": "Legg til alternativ", "ux_editor.modal_properties_add_radio_button_options": "Hvordan vil du legge til radioknapper?", + "ux_editor.modal_properties_code_list_delete_item": "Slett alternativ {{number}}", + "ux_editor.modal_properties_code_list_empty": "Kodelisten er tom.", "ux_editor.modal_properties_code_list_helper": "Velg kodeliste", "ux_editor.modal_properties_code_list_id": "Kodeliste-ID", + "ux_editor.modal_properties_code_list_item_description": "Beskrivelse for alternativ {{number}}", + "ux_editor.modal_properties_code_list_item_helpText": "Hjelpetekst for alternativ {{number}}", + "ux_editor.modal_properties_code_list_item_label": "Ledetekst for alternativ {{number}}", + "ux_editor.modal_properties_code_list_item_value": "Verdi for alternativ {{number}}", + "ux_editor.modal_properties_code_list_open_editor": "Åpne redigeringsverktøy", "ux_editor.modal_properties_code_list_read_more": "<0 href=\"{{optionsDocs}}\" >Les mer om kodelister", "ux_editor.modal_properties_code_list_read_more_dynamic": "<0 href=\"{{optionsDocs}}\" >Les mer om dynamiske kodelister", "ux_editor.modal_properties_code_list_read_more_static": "<0 href=\"{{optionsDocs}}\" >Les mer om statiske kodelister", @@ -1612,7 +1620,7 @@ "ux_editor.options.codelist_create_info.step4": "Skriv inn kodelisten i tekstfeltet midt på siden. Kodelisten må være i JSON-format.", "ux_editor.options.codelist_create_info.step5": "Velg \"Commit endringer\".", "ux_editor.options.codelist_create_info.step6": "Du er nå ferdig i Gitea for denne gang. Gå tilbake til Altinn Studio-fanen, eller klikk på Altinn-logoen øverst til venstre i Gitea for å komme tilbake til Altinn Studio.", - "ux_editor.options.codelist_only": "Denne komponenten støtter kun oppsett med kodelister.", + "ux_editor.options.codelist_only": "Denne komponenten støtter kun oppsett med predefinerte kodelister.", "ux_editor.options.codelist_referenceId.description": "Her kan du legge til en referanse-ID til en dynamisk kodeliste som er satt opp i koden.", "ux_editor.options.codelist_referenceId.description_details": "Du bruker dynamiske kodelister for å tilpasse alternativer for brukerne. Det kan for eksempel være tilpasninger ut fra geografisk plassering, eller valg brukeren gjør tidligere i skjemaet.", "ux_editor.options.codelist_upload_info.heading": "Steg for å laste opp kodelister manuelt", diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/types/CodeListItem.ts b/frontend/libs/studio-components/src/components/StudioCodelistEditor/types/CodeListItem.ts index 5d61ddff9bd..9aea25454b6 100644 --- a/frontend/libs/studio-components/src/components/StudioCodelistEditor/types/CodeListItem.ts +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/types/CodeListItem.ts @@ -1,6 +1,6 @@ -export type CodeListItem = { +export type CodeListItem = { description?: string; helpText?: string; label: string; - value: string; + value: T; }; diff --git a/frontend/packages/shared/src/api/mutations.ts b/frontend/packages/shared/src/api/mutations.ts index b89409bb779..405f0b46f3a 100644 --- a/frontend/packages/shared/src/api/mutations.ts +++ b/frontend/packages/shared/src/api/mutations.ts @@ -41,7 +41,8 @@ import { altinn2DelegationsMigrationPath, imagePath, addImagePath, - optionListPath, + optionListUploadPath, + optionListUpdatePath, } from 'app-shared/api/paths'; import type { AddLanguagePayload } from 'app-shared/types/api/AddLanguagePayload'; import type { AddRepoParams } from 'app-shared/types/api'; @@ -65,6 +66,7 @@ import type { PipelineDeployment } from 'app-shared/types/api/PipelineDeployment import type { AddLayoutSetResponse } from 'app-shared/types/api/AddLayoutSetResponse'; import type { DataTypesChange } from 'app-shared/types/api/DataTypesChange'; import type { FormLayoutRequest } from 'app-shared/types/api/FormLayoutRequest'; +import type { Option } from 'app-shared/types/Option'; const headers = { Accept: 'application/json', @@ -114,7 +116,8 @@ export const updateAppPolicy = (org: string, app: string, payload: Policy) => pu export const updateAppMetadata = (org: string, app: string, payload: ApplicationMetadata) => put(appMetadataPath(org, app), payload); export const updateAppConfig = (org: string, app: string, payload: AppConfig) => post(serviceConfigPath(org, app), payload); export const uploadDataModel = (org: string, app: string, form: FormData) => post(dataModelsUploadPath(org, app), form, { headers: { 'Content-Type': 'multipart/form-data' } }); -export const uploadOptionList = (org: string, app: string, payload: FormData) => post(optionListPath(org, app), payload, { headers: { 'Content-Type': 'multipart/form-data' } }); +export const uploadOptionList = (org: string, app: string, payload: FormData) => post(optionListUploadPath(org, app), payload, { headers: { 'Content-Type': 'multipart/form-data' } }); +export const updateOptionList = (org: string, app: string, optionsListId: string, payload: Option[]) => put(optionListUpdatePath(org, app, optionsListId), payload); export const upsertTextResources = (org: string, app: string, language: string, payload: ITextResourcesObjectFormat) => put(textResourcesPath(org, app, language), payload); // Resourceadm diff --git a/frontend/packages/shared/src/api/paths.js b/frontend/packages/shared/src/api/paths.js index fb6e9c76aa9..0f3c86db231 100644 --- a/frontend/packages/shared/src/api/paths.js +++ b/frontend/packages/shared/src/api/paths.js @@ -29,8 +29,9 @@ export const dataModelAddXsdFromRepoPath = (org, app, filePath) => `${basePath}/ export const ruleHandlerPath = (org, app, layoutSetName) => `${basePath}/${org}/${app}/app-development/rule-handler?${s({ layoutSetName })}`; // Get, Post export const widgetSettingsPath = (org, app) => `${basePath}/${org}/${app}/app-development/widget-settings`; // Get export const optionListsPath = (org, app) => `${basePath}/${org}/${app}/options/option-lists`; // Get -export const optionListPath = (org, app) => `${basePath}/${org}/${app}/options/upload/`; // Post export const optionListIdsPath = (org, app) => `${basePath}/${org}/${app}/app-development/option-list-ids`; // Get +export const optionListUpdatePath = (org, app, optionsListId) => `${basePath}/${org}/${app}/options/${optionsListId}`; // Put +export const optionListUploadPath = (org, app) => `${basePath}/${org}/${app}/options/upload`; // Post export const ruleConfigPath = (org, app, layoutSetName) => `${basePath}/${org}/${app}/app-development/rule-config?${s({ layoutSetName })}`; // Get, Post export const appMetadataModelIdsPath = (org, app, onlyUnReferenced) => `${basePath}/${org}/${app}/app-development/model-ids?${s({ onlyUnReferenced })}`; // Get export const dataModelMetadataPath = (org, app, layoutSetName, dataModelName) => `${basePath}/${org}/${app}/app-development/model-metadata?${s({ layoutSetName })}&${s({ dataModelName })}`; // Get diff --git a/frontend/packages/shared/src/api/queries.ts b/frontend/packages/shared/src/api/queries.ts index 0e5f047156b..fe0f927d6cd 100644 --- a/frontend/packages/shared/src/api/queries.ts +++ b/frontend/packages/shared/src/api/queries.ts @@ -82,6 +82,7 @@ import type { FormLayoutsResponseV3 } from 'app-shared/types/api/FormLayoutsResp import type { Policy } from 'app-shared/types/Policy'; import type { RepoDiffResponse } from 'app-shared/types/api/RepoDiffResponse'; import type { ExternalImageUrlValidationResponse } from 'app-shared/types/api/ExternalImageUrlValidationResponse'; +import type { Option } from 'app-shared/types/Option'; export const getAppMetadataModelIds = (org: string, app: string, onlyUnReferenced: boolean) => get(appMetadataModelIdsPath(org, app, onlyUnReferenced)); export const getAppReleases = (owner: string, app: string) => get(releasesPath(owner, app, 'Descending')); @@ -102,7 +103,7 @@ export const getImageFileNames = (owner: string, app: string) => get(g export const getInstanceIdForPreview = (owner: string, app: string) => get(instanceIdForPreviewPath(owner, app)); export const getLayoutNames = (owner: string, app: string) => get(layoutNamesPath(owner, app)); export const getLayoutSets = (owner: string, app: string) => get(layoutSetsPath(owner, app)); -export const getOptionLists = (owner: string, app: string) => get(optionListsPath(owner, app)); +export const getOptionLists = (owner: string, app: string) => get>(optionListsPath(owner, app)); export const getOptionListIds = (owner: string, app: string) => get(optionListIdsPath(owner, app)); export const getOrgList = () => get(orgListUrl()); export const getOrganizations = () => get(orgsListPath()); diff --git a/frontend/packages/shared/src/mocks/mocks.ts b/frontend/packages/shared/src/mocks/mocks.ts index 380f5250760..77a8734e154 100644 --- a/frontend/packages/shared/src/mocks/mocks.ts +++ b/frontend/packages/shared/src/mocks/mocks.ts @@ -26,6 +26,7 @@ import type { Organization } from 'app-shared/types/Organization'; import type { KubernetesDeployment } from 'app-shared/types/api/KubernetesDeployment'; import type { DeploymentsResponse } from 'app-shared/types/api/DeploymentsResponse'; import type { AppRelease } from 'app-shared/types/AppRelease'; +import { Option } from 'app-shared/types/Option'; export const build: Build = { id: '', @@ -244,3 +245,11 @@ export const searchRepositoryResponse: SearchRepositoryResponse = { totalCount: 0, totalPages: 0, }; + +export const optionListResponse: Map = new Map([ + [ + 'key1', + [{ value: 'test', label: 'test label', description: 'description', helpText: 'help text' }], + ], + ['key2', [{ value: 'test2', label: 'test2 label' }]], +]); diff --git a/frontend/packages/shared/src/mocks/queriesMock.ts b/frontend/packages/shared/src/mocks/queriesMock.ts index 03f83e9c251..02c030c92d3 100644 --- a/frontend/packages/shared/src/mocks/queriesMock.ts +++ b/frontend/packages/shared/src/mocks/queriesMock.ts @@ -52,6 +52,7 @@ import { createRepoCommitPayload, dataModelMetadataResponse, layoutSets, + optionListResponse, orgList, policy, repoStatus, @@ -68,6 +69,7 @@ import type { FormLayoutsResponseV3 } from 'app-shared/types/api/FormLayoutsResp import type { DeploymentsResponse } from 'app-shared/types/api/DeploymentsResponse'; import type { RepoDiffResponse } from 'app-shared/types/api/RepoDiffResponse'; import type { ExternalImageUrlValidationResponse } from 'app-shared/types/api/ExternalImageUrlValidationResponse'; +import type { Option } from 'app-shared/types/Option'; export const queriesMock: ServicesContextProps = { // Queries @@ -102,7 +104,9 @@ export const queriesMock: ServicesContextProps = { getLayoutNames: jest.fn().mockImplementation(() => Promise.resolve([])), getLayoutSets: jest.fn().mockImplementation(() => Promise.resolve(layoutSets)), getOptionListIds: jest.fn().mockImplementation(() => Promise.resolve([])), - getOptionLists: jest.fn().mockImplementation(() => Promise.resolve([])), + getOptionLists: jest + .fn() + .mockImplementation(() => Promise.resolve>(optionListResponse)), getOrgList: jest.fn().mockImplementation(() => Promise.resolve(orgList)), getOrganizations: jest.fn().mockImplementation(() => Promise.resolve([])), getRepoMetadata: jest.fn().mockImplementation(() => Promise.resolve(repository)), @@ -211,6 +215,7 @@ export const queriesMock: ServicesContextProps = { updateAppPolicy: jest.fn().mockImplementation(() => Promise.resolve()), updateAppMetadata: jest.fn().mockImplementation(() => Promise.resolve()), updateAppConfig: jest.fn().mockImplementation(() => Promise.resolve()), + updateOptionList: jest.fn().mockImplementation(() => Promise.resolve()), uploadDataModel: jest.fn().mockImplementation(() => Promise.resolve({})), uploadOptionList: jest.fn().mockImplementation(() => Promise.resolve()), upsertTextResources: jest diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.module.css b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.module.css new file mode 100644 index 00000000000..9ca2c3cfb7e --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.module.css @@ -0,0 +1,8 @@ +.manualTabModal[open] { + max-width: unset; + width: min(80vw, 64rem); +} + +.modalTrigger { + margin-top: var(--fds-spacing-2); +} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx new file mode 100644 index 00000000000..42a1f258102 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx @@ -0,0 +1,97 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { ComponentType } from 'app-shared/types/ComponentType'; +import type { FormComponent } from '../../../../../types/FormComponent'; +import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; +import { + type ServicesContextProps, + ServicesContextProvider, +} from 'app-shared/contexts/ServicesContext'; +import { CodeListTableEditor } from './CodeListTableEditor'; +import { textMock } from '@studio/testing/mocks/i18nMock'; +import { PreviewContext, type PreviewContextProps } from 'app-development/contexts/PreviewContext'; +import { queriesMock } from 'app-shared/mocks/queriesMock'; +import userEvent, { UserEvent } from '@testing-library/user-event'; + +// Test data: +const mockComponent: FormComponent = { + id: 'c24d0812-0c34-4582-8f31-ff4ce9795e96', + type: ComponentType.Dropdown, + itemType: 'COMPONENT', + dataModelBindings: { simpleBinding: 'some-path' }, + optionsId: 'test', +}; + +const defaultPreviewContextProps: PreviewContextProps = { + shouldReloadPreview: false, + doReloadPreview: jest.fn(), + previewHasLoaded: jest.fn(), +}; + +const queryClientMock = createQueryClientMock(); +describe('CodeListTableEditor', () => { + afterEach(() => { + queryClientMock.clear(); + }); + + it('should render the component', async () => { + await renderCodeListTableEditor(); + + expect( + await screen.findByRole('button', { + name: textMock('ux_editor.modal_properties_code_list_open_editor'), + }), + ).toBeInTheDocument(); + }); + + it('should open Dialog', async () => { + const user = userEvent.setup(); + await renderCodeListTableEditor(); + await userFindOpenButtonAndClick(user); + + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + + it('should close Dialog', async () => { + const user = userEvent.setup(); + const doReloadPreview = jest.fn(); + await renderCodeListTableEditor({ previewContextProps: { doReloadPreview } }); + await userFindOpenButtonAndClick(user); + + await user.click(screen.getByRole('button', { name: 'close modal' })); // Todo: Replace "close modal" with defaultDialogProps.closeButtonTitle when https://github.com/digdir/designsystemet/issues/2195 is fixed + + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + expect(doReloadPreview).toHaveBeenCalledTimes(1); // Todo: assertion fails, needs to take a closer look why handleClose does not get called. + }); +}); + +const userFindOpenButtonAndClick = async (user: UserEvent) => { + const btnOpen = await screen.findByRole('button', { + name: textMock('ux_editor.modal_properties_code_list_open_editor'), + }); + await user.click(btnOpen); +}; + +const renderCodeListTableEditor = async ({ + queries = {}, + previewContextProps = {}, + componentProps = {}, +} = {}) => { + const allQueries: ServicesContextProps = { + ...queries, + ...queriesMock, + }; + + return render( + + + + + , + ); +}; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx new file mode 100644 index 00000000000..4ea207df0c6 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx @@ -0,0 +1,76 @@ +import React, { useEffect, useState } from 'react'; +import type { IGenericEditComponent } from '@altinn/ux-editor/components/config/componentConfig'; +import type { SelectionComponentType } from '@altinn/ux-editor/types/FormComponent'; +import type { CodeListItem } from '@studio/components'; +import type { Option } from 'app-shared/types/Option'; +import { useTranslation } from 'react-i18next'; +import { StudioCodeListEditor, StudioModal } from '@studio/components'; +import { TableIcon } from '@studio/icons'; +import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; +import { useUpdateOptionListMutation } from '../../../../../hooks/mutations/useUpdateOptionListMutation'; +import { useOptionListsQuery } from '../../../../../hooks/queries/useOptionListsQuery'; +import { + convertCodeListItemListToOptionsList, + convertOptionsListToCodeListItemList, +} from './utils/conversionUtils'; +import { useCodeListEditorTexts } from './hooks/useCodeListEditorTexts'; +import { usePreviewContext } from 'app-development/contexts/PreviewContext'; +import classes from './CodeListTableEditor.module.css'; + +export type CodeListTableEditorProps = Pick< + IGenericEditComponent, + 'component' +>; + +export function CodeListTableEditor({ component }: CodeListTableEditorProps): React.ReactNode { + const { t } = useTranslation(); + const { org, app } = useStudioEnvironmentParams(); + const { doReloadPreview } = usePreviewContext(); + const { data: optionsListMap, isFetching } = useOptionListsQuery(org, app); + const { mutate: uploadOptionList } = useUpdateOptionListMutation(org, app, { + hideDefaultError: true, + }); + const [codeListItemList, setCodeListItemList] = useState([]); + const editorTexts = useCodeListEditorTexts(); + + useEffect(() => { + if (isFetching) return; + handleOptionsChange(optionsListMap[component.optionsId]); + }, [optionsListMap, component.optionsId, isFetching]); + + const handleOptionsChange = (options: Option[]) => { + const convertedList = convertOptionsListToCodeListItemList(options); + setCodeListItemList(convertedList); + }; + + const handleClose = () => { + const optionsListLocal = convertCodeListItemListToOptionsList(codeListItemList); + uploadOptionList({ optionListId: component.optionsId, optionsList: optionsListLocal }); + doReloadPreview(); + }; + + if (component.optionsId === undefined || isFetching) return; + return ( + + } + > + {t('ux_editor.modal_properties_code_list_open_editor')} + + + + + + ); +} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.test.tsx index de183d34f44..968074bf007 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.test.tsx @@ -1,13 +1,20 @@ import React from 'react'; import { EditCodeList } from './EditCodeList'; -import { screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { ComponentType } from 'app-shared/types/ComponentType'; -import { renderWithProviders, optionListIdsMock } from '../../../../../testing/mocks'; -import userEvent from '@testing-library/user-event'; +import { optionListIdsMock } from '../../../../../testing/mocks'; +import userEvent, { UserEvent } from '@testing-library/user-event'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import type { FormComponent } from '../../../../../types/FormComponent'; - +import { + type ServicesContextProps, + ServicesContextProvider, +} from 'app-shared/contexts/ServicesContext'; +import { queriesMock } from 'app-shared/mocks/queriesMock'; +import { PreviewContext, type PreviewContextProps } from 'app-development/contexts/PreviewContext'; + +// Test data: const mockComponent: FormComponent = { id: 'c24d0812-0c34-4582-8f31-ff4ce9795e96', type: ComponentType.Dropdown, @@ -18,6 +25,12 @@ const mockComponent: FormComponent = { dataModelBindings: { simpleBinding: 'some-path' }, }; +const defaultPreviewContextProps: PreviewContextProps = { + shouldReloadPreview: false, + doReloadPreview: jest.fn(), + previewHasLoaded: jest.fn(), +}; + const queryClientMock = createQueryClientMock(); describe('EditCodeList', () => { @@ -26,13 +39,7 @@ describe('EditCodeList', () => { }); it('should render the component', async () => { - await render({ - queries: { - getOptionListIds: jest - .fn() - .mockImplementation(() => Promise.resolve(optionListIdsMock)), - }, - }); + renderEditCodeList(); expect( await screen.findByText(textMock('ux_editor.modal_properties_code_list_helper')), ).toBeInTheDocument(); @@ -41,13 +48,8 @@ describe('EditCodeList', () => { it('should call onChange when option list changes', async () => { const handleComponentChangeMock = jest.fn(); const user = userEvent.setup(); - await render({ + renderEditCodeList({ handleComponentChange: handleComponentChangeMock, - queries: { - getOptionListIds: jest - .fn() - .mockImplementation(() => Promise.resolve(optionListIdsMock)), - }, }); await waitFor(() => screen.findByRole('combobox')); @@ -59,16 +61,11 @@ describe('EditCodeList', () => { it('should remove options property (if it exists) when optionsId property changes', async () => { const handleComponentChangeMock = jest.fn(); const user = userEvent.setup(); - await render({ + renderEditCodeList({ handleComponentChange: handleComponentChangeMock, componentProps: { options: [{ label: 'option1', value: 'option1' }], }, - queries: { - getOptionListIds: jest - .fn() - .mockImplementation(() => Promise.resolve(optionListIdsMock)), - }, }); await waitFor(() => screen.findByRole('combobox')); @@ -84,22 +81,17 @@ describe('EditCodeList', () => { }); it('should render the selected option list item upon component initialization', async () => { - await render({ + renderEditCodeList({ componentProps: { optionsId: 'test-2', }, - queries: { - getOptionListIds: jest - .fn() - .mockImplementation(() => Promise.resolve(optionListIdsMock)), - }, }); expect(await screen.findByRole('combobox')).toHaveValue('test-2'); }); it('should render returned error message if option list endpoint returns an error', async () => { - await render({ + renderEditCodeList({ queries: { getOptionListIds: jest.fn().mockImplementation(() => Promise.reject(new Error('Error'))), }, @@ -109,7 +101,7 @@ describe('EditCodeList', () => { }); it('should render standard error message if option list endpoint throws an error without specified error message', async () => { - await render({ + renderEditCodeList({ queries: { getOptionListIds: jest.fn().mockImplementation(() => Promise.reject()), }, @@ -123,24 +115,10 @@ describe('EditCodeList', () => { it('should render success toast if file upload is successful', async () => { const user = userEvent.setup(); const file = new File(['hello'], 'hello.json', { type: 'text/json' }); - await render({ - queries: { - getOptionListIds: jest - .fn() - .mockImplementation(() => Promise.resolve(optionListIdsMock)), - }, - }); - const btn = screen.getByRole('button', { - name: textMock('ux_editor.modal_properties_code_list_upload'), - }); - await user.click(btn); - - const fileInput = screen.getByLabelText( - textMock('ux_editor.modal_properties_code_list_upload'), - ); - - await user.upload(fileInput, file); + renderEditCodeList(); + await userFindUploadButtonAndClick(user); + await userFindFileAndUpload(user, file); expect(await screen.findByRole('alert')).toHaveTextContent( textMock('ux_editor.modal_properties_code_list_upload_success'), @@ -152,24 +130,10 @@ describe('EditCodeList', () => { const file = new File([optionListIdsMock[0]], optionListIdsMock[0] + '.json', { type: 'text/json', }); - await render({ - queries: { - getOptionListIds: jest - .fn() - .mockImplementation(() => Promise.resolve(optionListIdsMock)), - }, - }); - - const btn = screen.getByRole('button', { - name: textMock('ux_editor.modal_properties_code_list_upload'), - }); - await user.click(btn); - - const fileInput = screen.getByLabelText( - textMock('ux_editor.modal_properties_code_list_upload'), - ); - await user.upload(fileInput, file); + renderEditCodeList(); + await userFindUploadButtonAndClick(user); + await userFindFileAndUpload(user, file); expect(await screen.findByRole('alert')).toHaveTextContent( textMock('ux_editor.modal_properties_code_list_upload_duplicate_error'), @@ -182,23 +146,10 @@ describe('EditCodeList', () => { const file = new File([optionListIdsMock[0]], invalidFileName, { type: 'text/json', }); - await render({ - queries: { - getOptionListIds: jest - .fn() - .mockImplementation(() => Promise.resolve(optionListIdsMock)), - }, - }); - - const btn = screen.getByRole('button', { - name: textMock('ux_editor.modal_properties_code_list_upload'), - }); - await user.click(btn); - const fileInput = screen.getByLabelText( - textMock('ux_editor.modal_properties_code_list_upload'), - ); - await user.upload(fileInput, file); + renderEditCodeList(); + await userFindUploadButtonAndClick(user); + await userFindFileAndUpload(user, file); expect(await screen.findByRole('alert')).toHaveTextContent( textMock('ux_editor.model_properties_code_list_filename_error'), @@ -206,22 +157,44 @@ describe('EditCodeList', () => { }); }); -const render = async ({ +const userFindUploadButtonAndClick = async (user: UserEvent) => { + const btn = screen.getByRole('button', { + name: textMock('ux_editor.modal_properties_code_list_upload'), + }); + await user.click(btn); +}; + +const userFindFileAndUpload = async (user: UserEvent, file: File) => { + const fileInput = screen.getByLabelText(textMock('ux_editor.modal_properties_code_list_upload')); + + await user.upload(fileInput, file); +}; + +const renderEditCodeList = ({ handleComponentChange = jest.fn(), - queries = {}, + queries = { + getOptionListIds: jest + .fn() + .mockImplementation(() => Promise.resolve(optionListIdsMock)), + }, componentProps = {}, } = {}) => { - renderWithProviders( - , - { - queries, - queryClient: queryClientMock, - }, + const allQueries: ServicesContextProps = { + ...queriesMock, + ...queries, + }; + + return render( + + + + + , ); }; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.tsx index 1ddff90dfdb..294dba979a7 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.tsx @@ -10,12 +10,13 @@ import { FormField } from '../../../../FormField'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; import type { SelectionComponentType } from '../../../../../types/FormComponent'; import { removeExtension } from 'app-shared/utils/filenameUtils'; -import { findFileNameError } from './findFileNameError'; -import type { FileNameError } from './findFileNameError'; +import { findFileNameError } from './utils/findFileNameError'; +import type { FileNameError } from './utils/findFileNameError'; import type { AxiosError } from 'axios'; +import type { ApiError } from 'app-shared/types/api/ApiError'; import { toast } from 'react-toastify'; import classes from './EditCodeList.module.css'; -import type { ApiError } from 'app-shared/types/api/ApiError'; +import { CodeListTableEditor } from './CodeListTableEditor'; export function EditCodeList({ component, @@ -25,7 +26,7 @@ export function EditCodeList({ const { org, app } = useStudioEnvironmentParams(); const { data: optionListIds } = useOptionListIdsQuery(org, app); const { mutate: uploadOptionList } = useAddOptionListMutation(org, app, { - hideDefaultError: (error: AxiosError) => !error.response.data.errorCode, + hideDefaultError: true, }); const handleOptionsIdChange = (optionsId: string) => { @@ -39,6 +40,15 @@ export function EditCodeList({ }); }; + const onSubmit = (file: File) => { + const fileNameError = findFileNameError(optionListIds, file.name); + if (fileNameError) { + handleInvalidFileName(fileNameError); + } else { + handleUpload(file); + } + }; + const handleUpload = (file: File) => { uploadOptionList(file, { onSuccess: () => { @@ -53,15 +63,6 @@ export function EditCodeList({ }); }; - const onSubmit = (file: File) => { - const fileNameError = findFileNameError(optionListIds, file.name); - if (fileNameError) { - handleInvalidFileName(fileNameError); - } else { - handleUpload(file); - } - }; - const handleInvalidFileName = (fileNameError: FileNameError) => { switch (fileNameError) { case 'invalidFileName': @@ -74,6 +75,7 @@ export function EditCodeList({ return ( <> + { + const { t } = useTranslation(); + + return { + add: t('ux_editor.modal_new_option'), + codeList: t('ux_editor.modal_add_options_codelist'), + delete: t('general.delete'), + deleteItem: (number) => t('ux_editor.modal_properties_code_list_delete_item', { number }), + description: t('general.description'), + emptyCodeList: t('ux_editor.modal_properties_code_list_empty'), + helpText: t('ux_editor.options_text_help_text'), + itemDescription: (number) => + t('ux_editor.modal_properties_code_list_item_description', { number }), + itemHelpText: (number) => t('ux_editor.modal_properties_code_list_item_helpText', { number }), + itemLabel: (number) => t('ux_editor.modal_properties_code_list_item_label', { number }), + itemValue: (number) => t('ux_editor.modal_properties_code_list_item_value', { number }), + label: t('ux_editor.options_text_label'), + value: t('general.value'), + }; +}; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.test.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.test.ts new file mode 100644 index 00000000000..0cd027d1b00 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.test.ts @@ -0,0 +1,42 @@ +import type { Option } from 'app-shared/types/Option'; +import type { CodeListItem } from '@studio/components'; +import { convertOptionsListToCodeListItemList } from './conversionUtils'; +import { convertCodeListItemListToOptionsList } from './conversionUtils'; + +// Test data: +const optionsList: Option[] = [ + { value: 'test', label: 'test label', description: 'description', helpText: 'help text' }, +]; +const codeListItemList: CodeListItem[] = [ + { value: 'test', label: 'test label', description: 'description', helpText: 'help text' }, +]; + +describe('conversionUtils', () => { + describe('convertOptionsListToCodeListItem', () => { + it('should return empty list if input is undefined', () => { + expect(convertOptionsListToCodeListItemList(undefined)).toEqual([]); + }); + + it('should return empty list if input is an empty list', () => { + expect(convertOptionsListToCodeListItemList([])).toEqual([]); + }); + + it('should return converted list', () => { + expect(convertOptionsListToCodeListItemList(optionsList)).toEqual(codeListItemList); + }); + }); + + describe('convertCodeListItemListToOptionsList', () => { + it('should return empty list if input is undefined', () => { + expect(convertCodeListItemListToOptionsList(undefined)).toEqual([]); + }); + + it('should return empty list if input is an empty list', () => { + expect(convertCodeListItemListToOptionsList([])).toEqual([]); + }); + + it('should return converted list', () => { + expect(convertCodeListItemListToOptionsList(codeListItemList)).toEqual(optionsList); + }); + }); +}); diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.ts new file mode 100644 index 00000000000..bceebcd8a21 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.ts @@ -0,0 +1,38 @@ +import type { Option } from 'app-shared/types/Option'; +import type { CodeListItem } from '@studio/components'; + +export const convertOptionsListToCodeListItemList = (optionList: Option[]): CodeListItem[] => { + if (optionList === undefined) return []; + + const tempList: CodeListItem[] = []; + if (optionList.length === 0) return tempList; + + optionList.forEach((option: Option) => { + tempList.push({ + label: option.label, + value: option.value, + description: option.description ?? '', + helpText: option.helpText ?? '', + }); + }); + return tempList; +}; + +export const convertCodeListItemListToOptionsList = ( + codeListItemList: CodeListItem[], +): Option[] => { + if (codeListItemList === undefined) return []; + + const tempList: Option[] = []; + if (codeListItemList.length === 0) return tempList; + + codeListItemList.forEach((codeListItem: CodeListItem) => { + tempList.push({ + label: codeListItem.label, + value: codeListItem.value, + description: codeListItem.description ?? '', + helpText: codeListItem.helpText ?? '', + }); + }); + return tempList; +}; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/findFileNameError.test.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/findFileNameError.test.ts similarity index 100% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/findFileNameError.test.ts rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/findFileNameError.test.ts diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/findFileNameError.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/findFileNameError.ts similarity index 100% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/findFileNameError.ts rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/findFileNameError.ts diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.test.tsx index d4f4f5d6860..8b711c99818 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.test.tsx @@ -1,15 +1,17 @@ import React from 'react'; -import { screen, waitFor } from '@testing-library/react'; - +import { render, screen, waitFor } from '@testing-library/react'; import { EditOptions } from './EditOptions'; -import { renderWithProviders } from '../../../../testing/mocks'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { ComponentType } from 'app-shared/types/ComponentType'; import type { FormComponent } from '../../../../types/FormComponent'; import type { FormItem } from '../../../../types/FormItem'; -import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; +import { ServicesContextProps, ServicesContextProvider } from 'app-shared/contexts/ServicesContext'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import userEvent from '@testing-library/user-event'; +import { queriesMock } from 'app-shared/mocks/queriesMock'; +import { MemoryRouter } from 'react-router-dom'; +import { SettingsModalContextProvider } from 'app-development/contexts/SettingsModalContext'; +import { PreviewContext, type PreviewContextProps } from 'app-development/contexts/PreviewContext'; const mockComponent: FormComponent = { id: 'c24d0812-0c34-4582-8f31-ff4ce9795e96', @@ -22,6 +24,12 @@ const mockComponent: FormComponent = { dataModelBindings: { simpleBinding: '' }, }; +const defaultPreviewContextProps: PreviewContextProps = { + shouldReloadPreview: false, + doReloadPreview: jest.fn(), + previewHasLoaded: jest.fn(), +}; + const queryClientMock = createQueryClientMock(); const renderEditOptions = async ({ @@ -41,16 +49,22 @@ const renderEditOptions = async , - { - queries, - queryClient: queryClientMock, - }, + const allQueries = { + ...queriesMock, + ...queries, + }; + + render( + + + + , + + , ); }; diff --git a/frontend/packages/ux-editor/src/hooks/mutations/useUpdateOptionListMutation.test.ts b/frontend/packages/ux-editor/src/hooks/mutations/useUpdateOptionListMutation.test.ts new file mode 100644 index 00000000000..ccc323ac6e7 --- /dev/null +++ b/frontend/packages/ux-editor/src/hooks/mutations/useUpdateOptionListMutation.test.ts @@ -0,0 +1,24 @@ +import { app, org } from '@studio/testing/testids'; +import { queriesMock } from 'app-shared/mocks/queriesMock'; +import { renderHookWithProviders } from '../../testing/mocks'; +import { + UpdateOptionListMutationArgs, + useUpdateOptionListMutation, +} from './useUpdateOptionListMutation'; +import type { Option } from 'app-shared/types/Option'; + +// Test data: +const optionListId = 'test'; +const optionsList: Option[] = [{ value: 'test', label: 'test' }]; +const args: UpdateOptionListMutationArgs = { optionListId: optionListId, optionsList: optionsList }; + +describe('useUpdateOptionListMutation', () => { + test('Calls useUpdateOptionList with correct parameters', async () => { + const renderUpdateOptionListMutationResult = renderHookWithProviders(() => + useUpdateOptionListMutation(org, app), + ).result; + await renderUpdateOptionListMutationResult.current.mutateAsync(args); + expect(queriesMock.updateOptionList).toHaveBeenCalledTimes(1); + expect(queriesMock.updateOptionList).toHaveBeenCalledWith(org, app, optionListId, optionsList); + }); +}); diff --git a/frontend/packages/ux-editor/src/hooks/mutations/useUpdateOptionListMutation.ts b/frontend/packages/ux-editor/src/hooks/mutations/useUpdateOptionListMutation.ts new file mode 100644 index 00000000000..031a7ef9b9b --- /dev/null +++ b/frontend/packages/ux-editor/src/hooks/mutations/useUpdateOptionListMutation.ts @@ -0,0 +1,28 @@ +import type { MutationMeta } from '@tanstack/react-query'; +import { QueryKey } from 'app-shared/types/QueryKey'; +import type { Option } from 'app-shared/types/Option'; +import { useQueryClient, useMutation } from '@tanstack/react-query'; +import { useServicesContext } from 'app-shared/contexts/ServicesContext'; + +export interface UpdateOptionListMutationArgs { + optionListId: string; + optionsList: Option[]; +} + +export const useUpdateOptionListMutation = (org: string, app: string, meta?: MutationMeta) => { + const queryClient = useQueryClient(); + const { updateOptionList } = useServicesContext(); + + return useMutation({ + mutationFn: ({ optionListId, optionsList }: UpdateOptionListMutationArgs) => { + return updateOptionList(org, app, optionListId, optionsList); + }, + onSuccess: async () => { + await Promise.all([ + queryClient.invalidateQueries({ queryKey: [QueryKey.OptionListIds, org, app] }), + queryClient.invalidateQueries({ queryKey: [QueryKey.OptionLists, org, app] }), + ]); + }, + meta, + }); +}; diff --git a/frontend/packages/ux-editor/src/hooks/queries/useOptionListsQuery.ts b/frontend/packages/ux-editor/src/hooks/queries/useOptionListsQuery.ts index b3bec60fc1d..473c41213b4 100644 --- a/frontend/packages/ux-editor/src/hooks/queries/useOptionListsQuery.ts +++ b/frontend/packages/ux-editor/src/hooks/queries/useOptionListsQuery.ts @@ -2,8 +2,12 @@ import { useServicesContext } from 'app-shared/contexts/ServicesContext'; import { QueryKey } from 'app-shared/types/QueryKey'; import type { UseQueryResult } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query'; +import type { Option } from 'app-shared/types/Option'; -export const useOptionListsQuery = (org: string, app: string): UseQueryResult => { +export const useOptionListsQuery = ( + org: string, + app: string, +): UseQueryResult> => { const { getOptionLists } = useServicesContext(); return useQuery({ From 6db7a5e046ebd291b200d9914bf11cb9eb753c49 Mon Sep 17 00:00:00 2001 From: Konrad-Simso Date: Tue, 29 Oct 2024 16:31:35 +0100 Subject: [PATCH 2/9] Update tests and move handleClose from onClose to onBeforeClose --- .../EditCodeList/CodeListTableEditor.test.tsx | 12 +++++++++++- .../EditOptions/EditCodeList/CodeListTableEditor.tsx | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx index 42a1f258102..f1ead2809cf 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx @@ -53,6 +53,16 @@ describe('CodeListTableEditor', () => { }); it('should close Dialog', async () => { + const user = userEvent.setup(); + await renderCodeListTableEditor(); + await userFindOpenButtonAndClick(user); + + await user.click(screen.getByRole('button', { name: 'close modal' })); // Todo: Replace "close modal" with defaultDialogProps.closeButtonTitle when https://github.com/digdir/designsystemet/issues/2195 is fixed + + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + + it('should call handClose when closing Dialog', async () => { const user = userEvent.setup(); const doReloadPreview = jest.fn(); await renderCodeListTableEditor({ previewContextProps: { doReloadPreview } }); @@ -61,7 +71,7 @@ describe('CodeListTableEditor', () => { await user.click(screen.getByRole('button', { name: 'close modal' })); // Todo: Replace "close modal" with defaultDialogProps.closeButtonTitle when https://github.com/digdir/designsystemet/issues/2195 is fixed expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - expect(doReloadPreview).toHaveBeenCalledTimes(1); // Todo: assertion fails, needs to take a closer look why handleClose does not get called. + expect(doReloadPreview).toHaveBeenCalledTimes(1); }); }); diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx index 4ea207df0c6..53b2fe31fa1 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx @@ -63,7 +63,7 @@ export function CodeListTableEditor({ component }: CodeListTableEditorProps): Re className={classes.manualTabModal} closeButtonTitle={t('general.close')} heading={t('ux_editor.modal_add_options_codelist')} - onClose={handleClose} + onBeforeClose={handleClose} > Date: Tue, 29 Oct 2024 17:51:15 +0100 Subject: [PATCH 3/9] Remove unused imports and update typing. Type changes for StudioCodeListEditorRow.tsx should likely be reverted. To be determined during review. --- .../StudioCodeListEditorRow/StudioCodeListEditorRow.tsx | 2 +- .../config/editModal/EditOptions/EditOptions.test.tsx | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/StudioCodeListEditorRow.tsx b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/StudioCodeListEditorRow.tsx index ac886af7bec..9fe35941d29 100644 --- a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/StudioCodeListEditorRow.tsx +++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditorRow/StudioCodeListEditorRow.tsx @@ -56,7 +56,7 @@ export function StudioCodeListEditorRow({ = { From c950bab72297478486cf7e199b27e0651c689df0 Mon Sep 17 00:00:00 2001 From: Konrad-Simso Date: Tue, 29 Oct 2024 17:59:34 +0100 Subject: [PATCH 4/9] Update types, linting --- frontend/packages/shared/src/mocks/mocks.ts | 2 +- .../EditOptions/EditCodeList/CodeListTableEditor.test.tsx | 2 +- .../EditOptions/EditCodeList/EditCodeList.test.tsx | 2 +- .../EditOptions/EditCodeList/utils/conversionUtils.test.ts | 6 ++++-- .../config/editModal/EditOptions/EditOptions.test.tsx | 5 ++++- .../src/hooks/mutations/useUpdateOptionListMutation.test.ts | 2 +- 6 files changed, 12 insertions(+), 7 deletions(-) diff --git a/frontend/packages/shared/src/mocks/mocks.ts b/frontend/packages/shared/src/mocks/mocks.ts index 77a8734e154..1cc332ab505 100644 --- a/frontend/packages/shared/src/mocks/mocks.ts +++ b/frontend/packages/shared/src/mocks/mocks.ts @@ -26,7 +26,7 @@ import type { Organization } from 'app-shared/types/Organization'; import type { KubernetesDeployment } from 'app-shared/types/api/KubernetesDeployment'; import type { DeploymentsResponse } from 'app-shared/types/api/DeploymentsResponse'; import type { AppRelease } from 'app-shared/types/AppRelease'; -import { Option } from 'app-shared/types/Option'; +import type { Option } from 'app-shared/types/Option'; export const build: Build = { id: '', diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx index f1ead2809cf..fec7146a995 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx @@ -11,7 +11,7 @@ import { CodeListTableEditor } from './CodeListTableEditor'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { PreviewContext, type PreviewContextProps } from 'app-development/contexts/PreviewContext'; import { queriesMock } from 'app-shared/mocks/queriesMock'; -import userEvent, { UserEvent } from '@testing-library/user-event'; +import userEvent, { type UserEvent } from '@testing-library/user-event'; // Test data: const mockComponent: FormComponent = { diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.test.tsx index 968074bf007..4cefdd00a04 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.test.tsx @@ -3,7 +3,7 @@ import { EditCodeList } from './EditCodeList'; import { render, screen, waitFor } from '@testing-library/react'; import { ComponentType } from 'app-shared/types/ComponentType'; import { optionListIdsMock } from '../../../../../testing/mocks'; -import userEvent, { UserEvent } from '@testing-library/user-event'; +import userEvent, { type UserEvent } from '@testing-library/user-event'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import type { FormComponent } from '../../../../../types/FormComponent'; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.test.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.test.ts index 0cd027d1b00..7a083841370 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.test.ts +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.test.ts @@ -1,7 +1,9 @@ import type { Option } from 'app-shared/types/Option'; import type { CodeListItem } from '@studio/components'; -import { convertOptionsListToCodeListItemList } from './conversionUtils'; -import { convertCodeListItemListToOptionsList } from './conversionUtils'; +import { + convertOptionsListToCodeListItemList, + convertCodeListItemListToOptionsList, +} from './conversionUtils'; // Test data: const optionsList: Option[] = [ diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.test.tsx index 0bfa7a0deea..65c00264a2b 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.test.tsx @@ -5,7 +5,10 @@ import { textMock } from '@studio/testing/mocks/i18nMock'; import { ComponentType } from 'app-shared/types/ComponentType'; import type { FormComponent } from '../../../../types/FormComponent'; import type { FormItem } from '../../../../types/FormItem'; -import { ServicesContextProps, ServicesContextProvider } from 'app-shared/contexts/ServicesContext'; +import { + type ServicesContextProps, + ServicesContextProvider, +} from 'app-shared/contexts/ServicesContext'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import userEvent from '@testing-library/user-event'; import { queriesMock } from 'app-shared/mocks/queriesMock'; diff --git a/frontend/packages/ux-editor/src/hooks/mutations/useUpdateOptionListMutation.test.ts b/frontend/packages/ux-editor/src/hooks/mutations/useUpdateOptionListMutation.test.ts index ccc323ac6e7..218db118158 100644 --- a/frontend/packages/ux-editor/src/hooks/mutations/useUpdateOptionListMutation.test.ts +++ b/frontend/packages/ux-editor/src/hooks/mutations/useUpdateOptionListMutation.test.ts @@ -2,7 +2,7 @@ import { app, org } from '@studio/testing/testids'; import { queriesMock } from 'app-shared/mocks/queriesMock'; import { renderHookWithProviders } from '../../testing/mocks'; import { - UpdateOptionListMutationArgs, + type UpdateOptionListMutationArgs, useUpdateOptionListMutation, } from './useUpdateOptionListMutation'; import type { Option } from 'app-shared/types/Option'; From da9ef74496c6127006c6cba64a5ac648eae77856 Mon Sep 17 00:00:00 2001 From: Konrad-Simso Date: Wed, 30 Oct 2024 10:17:55 +0100 Subject: [PATCH 5/9] Update tests and remove unused export. --- .../EditOptions/EditCodeList/CodeListTableEditor.test.tsx | 8 ++++---- .../EditOptions/EditCodeList/CodeListTableEditor.tsx | 5 +---- .../EditCodeList/utils/conversionUtils.test.ts | 4 ++-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx index fec7146a995..5de87f462c7 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx @@ -47,7 +47,7 @@ describe('CodeListTableEditor', () => { it('should open Dialog', async () => { const user = userEvent.setup(); await renderCodeListTableEditor(); - await userFindOpenButtonAndClick(user); + await openModal(user); expect(screen.getByRole('dialog')).toBeInTheDocument(); }); @@ -55,7 +55,7 @@ describe('CodeListTableEditor', () => { it('should close Dialog', async () => { const user = userEvent.setup(); await renderCodeListTableEditor(); - await userFindOpenButtonAndClick(user); + await openModal(user); await user.click(screen.getByRole('button', { name: 'close modal' })); // Todo: Replace "close modal" with defaultDialogProps.closeButtonTitle when https://github.com/digdir/designsystemet/issues/2195 is fixed @@ -66,7 +66,7 @@ describe('CodeListTableEditor', () => { const user = userEvent.setup(); const doReloadPreview = jest.fn(); await renderCodeListTableEditor({ previewContextProps: { doReloadPreview } }); - await userFindOpenButtonAndClick(user); + await openModal(user); await user.click(screen.getByRole('button', { name: 'close modal' })); // Todo: Replace "close modal" with defaultDialogProps.closeButtonTitle when https://github.com/digdir/designsystemet/issues/2195 is fixed @@ -75,7 +75,7 @@ describe('CodeListTableEditor', () => { }); }); -const userFindOpenButtonAndClick = async (user: UserEvent) => { +const openModal = async (user: UserEvent) => { const btnOpen = await screen.findByRole('button', { name: textMock('ux_editor.modal_properties_code_list_open_editor'), }); diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx index 53b2fe31fa1..ce2d27b4c9f 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx @@ -17,10 +17,7 @@ import { useCodeListEditorTexts } from './hooks/useCodeListEditorTexts'; import { usePreviewContext } from 'app-development/contexts/PreviewContext'; import classes from './CodeListTableEditor.module.css'; -export type CodeListTableEditorProps = Pick< - IGenericEditComponent, - 'component' ->; +type CodeListTableEditorProps = Pick, 'component'>; export function CodeListTableEditor({ component }: CodeListTableEditorProps): React.ReactNode { const { t } = useTranslation(); diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.test.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.test.ts index 7a083841370..d09ee78f384 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.test.ts +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.test.ts @@ -24,7 +24,7 @@ describe('conversionUtils', () => { }); it('should return converted list', () => { - expect(convertOptionsListToCodeListItemList(optionsList)).toEqual(codeListItemList); + expect(convertOptionsListToCodeListItemList(optionsList)).toStrictEqual(codeListItemList); }); }); @@ -38,7 +38,7 @@ describe('conversionUtils', () => { }); it('should return converted list', () => { - expect(convertCodeListItemListToOptionsList(codeListItemList)).toEqual(optionsList); + expect(convertCodeListItemListToOptionsList(codeListItemList)).toStrictEqual(optionsList); }); }); }); From b0b3c3eb7857f3d8e3a963116f7b1411b604cb6a Mon Sep 17 00:00:00 2001 From: Konrad-Simso Date: Wed, 30 Oct 2024 10:57:51 +0100 Subject: [PATCH 6/9] Add featureFlag for CodeListTableEditor. --- frontend/packages/shared/src/utils/featureToggleUtils.ts | 3 ++- .../config/editModal/EditOptions/EditCodeList/EditCodeList.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/packages/shared/src/utils/featureToggleUtils.ts b/frontend/packages/shared/src/utils/featureToggleUtils.ts index 8f3dc9010f2..d91a04ba1bd 100644 --- a/frontend/packages/shared/src/utils/featureToggleUtils.ts +++ b/frontend/packages/shared/src/utils/featureToggleUtils.ts @@ -11,7 +11,8 @@ export type SupportedFeatureFlags = | 'multipleDataModelsPerTask' | 'exportForm' | 'subform' - | 'summary2'; + | 'summary2' + | 'codeListEditor'; /* * Please add all the features that you want to be toggle on by default here. diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.tsx index 294dba979a7..7575017d4b0 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.tsx @@ -17,6 +17,7 @@ import type { ApiError } from 'app-shared/types/api/ApiError'; import { toast } from 'react-toastify'; import classes from './EditCodeList.module.css'; import { CodeListTableEditor } from './CodeListTableEditor'; +import { shouldDisplayFeature } from 'app-shared/utils/featureToggleUtils'; export function EditCodeList({ component, @@ -75,7 +76,7 @@ export function EditCodeList({ return ( <> - + {shouldDisplayFeature('codeListEditor') && } Date: Wed, 30 Oct 2024 12:17:01 +0100 Subject: [PATCH 7/9] Updated CodeListTableEditor.tsx --- .../editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx index ce2d27b4c9f..12a3d0f3ad8 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx @@ -46,7 +46,7 @@ export function CodeListTableEditor({ component }: CodeListTableEditorProps): Re doReloadPreview(); }; - if (component.optionsId === undefined || isFetching) return; + if (component.optionsId === undefined || component.optionsId === '' || isFetching) return; return ( Date: Wed, 30 Oct 2024 14:18:12 +0100 Subject: [PATCH 8/9] Added functionality to call handleClose when clicking outside Modal to close it. --- .../editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx index 12a3d0f3ad8..7a287975c32 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx @@ -61,6 +61,7 @@ export function CodeListTableEditor({ component }: CodeListTableEditorProps): Re closeButtonTitle={t('general.close')} heading={t('ux_editor.modal_add_options_codelist')} onBeforeClose={handleClose} + onInteractOutside={handleClose} > Date: Mon, 4 Nov 2024 13:57:27 +0100 Subject: [PATCH 9/9] Updates from review --- frontend/language/src/nb.json | 2 +- frontend/packages/shared/src/api/mutations.ts | 2 +- frontend/packages/shared/src/mocks/mocks.ts | 9 -- .../packages/shared/src/mocks/queriesMock.ts | 3 +- frontend/packages/shared/src/types/Option.ts | 10 +- ...r.module.css => CodeListEditor.module.css} | 0 ...ditor.test.tsx => CodeListEditor.test.tsx} | 81 ++++++------- .../EditCodeList/CodeListEditor.tsx | 113 ++++++++++++++++++ .../EditCodeList/CodeListTableEditor.tsx | 74 ------------ .../EditCodeList/EditCodeList.test.tsx | 78 ++++-------- .../EditOptions/EditCodeList/EditCodeList.tsx | 6 +- .../hooks/useCodeListEditorTexts.ts | 2 +- .../utils/conversionUtils.test.ts | 44 ------- .../EditCodeList/utils/conversionUtils.ts | 38 ------ .../EditOptions/EditOptions.test.tsx | 49 +++----- .../mutations/useUpdateOptionListMutation.ts | 8 +- .../src/hooks/queries/useOptionListsQuery.ts | 10 +- .../packages/ux-editor/src/testing/mocks.tsx | 26 +++- 18 files changed, 228 insertions(+), 327 deletions(-) rename frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/{CodeListTableEditor.module.css => CodeListEditor.module.css} (100%) rename frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/{CodeListTableEditor.test.tsx => CodeListEditor.test.tsx} (59%) create mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListEditor.tsx delete mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx delete mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.test.ts delete mode 100644 frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.ts diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index 14e1c144a34..59eaf73615a 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -1462,7 +1462,7 @@ "ux_editor.modal_properties_code_list_helper": "Velg kodeliste", "ux_editor.modal_properties_code_list_id": "Kodeliste-ID", "ux_editor.modal_properties_code_list_item_description": "Beskrivelse for alternativ {{number}}", - "ux_editor.modal_properties_code_list_item_helpText": "Hjelpetekst for alternativ {{number}}", + "ux_editor.modal_properties_code_list_item_help_text": "Hjelpetekst for alternativ {{number}}", "ux_editor.modal_properties_code_list_item_label": "Ledetekst for alternativ {{number}}", "ux_editor.modal_properties_code_list_item_value": "Verdi for alternativ {{number}}", "ux_editor.modal_properties_code_list_open_editor": "Åpne redigeringsverktøy", diff --git a/frontend/packages/shared/src/api/mutations.ts b/frontend/packages/shared/src/api/mutations.ts index 405f0b46f3a..48e721ebf0d 100644 --- a/frontend/packages/shared/src/api/mutations.ts +++ b/frontend/packages/shared/src/api/mutations.ts @@ -117,7 +117,7 @@ export const updateAppMetadata = (org: string, app: string, payload: Application export const updateAppConfig = (org: string, app: string, payload: AppConfig) => post(serviceConfigPath(org, app), payload); export const uploadDataModel = (org: string, app: string, form: FormData) => post(dataModelsUploadPath(org, app), form, { headers: { 'Content-Type': 'multipart/form-data' } }); export const uploadOptionList = (org: string, app: string, payload: FormData) => post(optionListUploadPath(org, app), payload, { headers: { 'Content-Type': 'multipart/form-data' } }); -export const updateOptionList = (org: string, app: string, optionsListId: string, payload: Option[]) => put(optionListUpdatePath(org, app, optionsListId), payload); +export const updateOptionList = (org: string, app: string, optionsListId: string, payload: Option[]) => put(optionListUpdatePath(org, app, optionsListId), payload); export const upsertTextResources = (org: string, app: string, language: string, payload: ITextResourcesObjectFormat) => put(textResourcesPath(org, app, language), payload); // Resourceadm diff --git a/frontend/packages/shared/src/mocks/mocks.ts b/frontend/packages/shared/src/mocks/mocks.ts index 1cc332ab505..380f5250760 100644 --- a/frontend/packages/shared/src/mocks/mocks.ts +++ b/frontend/packages/shared/src/mocks/mocks.ts @@ -26,7 +26,6 @@ import type { Organization } from 'app-shared/types/Organization'; import type { KubernetesDeployment } from 'app-shared/types/api/KubernetesDeployment'; import type { DeploymentsResponse } from 'app-shared/types/api/DeploymentsResponse'; import type { AppRelease } from 'app-shared/types/AppRelease'; -import type { Option } from 'app-shared/types/Option'; export const build: Build = { id: '', @@ -245,11 +244,3 @@ export const searchRepositoryResponse: SearchRepositoryResponse = { totalCount: 0, totalPages: 0, }; - -export const optionListResponse: Map = new Map([ - [ - 'key1', - [{ value: 'test', label: 'test label', description: 'description', helpText: 'help text' }], - ], - ['key2', [{ value: 'test2', label: 'test2 label' }]], -]); diff --git a/frontend/packages/shared/src/mocks/queriesMock.ts b/frontend/packages/shared/src/mocks/queriesMock.ts index 02c030c92d3..049baf027bb 100644 --- a/frontend/packages/shared/src/mocks/queriesMock.ts +++ b/frontend/packages/shared/src/mocks/queriesMock.ts @@ -52,7 +52,6 @@ import { createRepoCommitPayload, dataModelMetadataResponse, layoutSets, - optionListResponse, orgList, policy, repoStatus, @@ -106,7 +105,7 @@ export const queriesMock: ServicesContextProps = { getOptionListIds: jest.fn().mockImplementation(() => Promise.resolve([])), getOptionLists: jest .fn() - .mockImplementation(() => Promise.resolve>(optionListResponse)), + .mockImplementation(() => Promise.resolve>(new Map())), getOrgList: jest.fn().mockImplementation(() => Promise.resolve(orgList)), getOrganizations: jest.fn().mockImplementation(() => Promise.resolve([])), getRepoMetadata: jest.fn().mockImplementation(() => Promise.resolve(repository)), diff --git a/frontend/packages/shared/src/types/Option.ts b/frontend/packages/shared/src/types/Option.ts index 73cb228f2aa..2c9dc559281 100644 --- a/frontend/packages/shared/src/types/Option.ts +++ b/frontend/packages/shared/src/types/Option.ts @@ -1,6 +1,4 @@ -export type Option = { - label: string; - value: T; - description?: string; - helpText?: string; -}; +import type { CodeListItem } from '@studio/components'; + +export type Option = + CodeListItem; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.module.css b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListEditor.module.css similarity index 100% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.module.css rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListEditor.module.css diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListEditor.test.tsx similarity index 59% rename from frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx rename to frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListEditor.test.tsx index 5de87f462c7..dd79334623f 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListEditor.test.tsx @@ -1,34 +1,34 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { screen, waitForElementToBeRemoved } from '@testing-library/react'; import { ComponentType } from 'app-shared/types/ComponentType'; import type { FormComponent } from '../../../../../types/FormComponent'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; -import { - type ServicesContextProps, - ServicesContextProvider, -} from 'app-shared/contexts/ServicesContext'; -import { CodeListTableEditor } from './CodeListTableEditor'; +import { CodeListEditor } from './CodeListEditor'; import { textMock } from '@studio/testing/mocks/i18nMock'; -import { PreviewContext, type PreviewContextProps } from 'app-development/contexts/PreviewContext'; -import { queriesMock } from 'app-shared/mocks/queriesMock'; import userEvent, { type UserEvent } from '@testing-library/user-event'; +import { componentMocks } from '@altinn/ux-editor/testing/componentMocks'; +import type { Option } from 'app-shared/types/Option'; +import { renderWithProviders } from '@altinn/ux-editor/testing/mocks'; // Test data: -const mockComponent: FormComponent = { - id: 'c24d0812-0c34-4582-8f31-ff4ce9795e96', - type: ComponentType.Dropdown, - itemType: 'COMPONENT', - dataModelBindings: { simpleBinding: 'some-path' }, - optionsId: 'test', +const mockComponent: FormComponent = componentMocks[ComponentType.Dropdown]; +mockComponent.optionsId = 'test'; + +const optionsList = new Map([ + [ + 'text', + [{ value: 'test', label: 'label text', description: 'description', helpText: 'help text' }], + ], + ['number', [{ value: 2, label: 'label number' }]], + ['boolean', [{ value: true, label: 'label boolean' }]], +]); +const queriesMock = { + getOptionLists: jest + .fn() + .mockImplementation(() => Promise.resolve>(optionsList)), }; - -const defaultPreviewContextProps: PreviewContextProps = { - shouldReloadPreview: false, - doReloadPreview: jest.fn(), - previewHasLoaded: jest.fn(), -}; - const queryClientMock = createQueryClientMock(); + describe('CodeListTableEditor', () => { afterEach(() => { queryClientMock.clear(); @@ -38,7 +38,7 @@ describe('CodeListTableEditor', () => { await renderCodeListTableEditor(); expect( - await screen.findByRole('button', { + screen.getByRole('button', { name: textMock('ux_editor.modal_properties_code_list_open_editor'), }), ).toBeInTheDocument(); @@ -76,32 +76,25 @@ describe('CodeListTableEditor', () => { }); const openModal = async (user: UserEvent) => { - const btnOpen = await screen.findByRole('button', { + const btnOpen = screen.getByRole('button', { name: textMock('ux_editor.modal_properties_code_list_open_editor'), }); await user.click(btnOpen); }; -const renderCodeListTableEditor = async ({ - queries = {}, - previewContextProps = {}, - componentProps = {}, -} = {}) => { - const allQueries: ServicesContextProps = { - ...queries, - ...queriesMock, - }; - - return render( - - - - - , +const renderCodeListTableEditor = async ({ previewContextProps = {} } = {}) => { + const view = renderWithProviders( + , + { + queries: queriesMock, + queryClient: queryClientMock, + previewContextProps: previewContextProps, + }, ); + await waitForElementToBeRemoved(screen.queryByTestId('studio-spinner-test-id')); + return view; }; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListEditor.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListEditor.tsx new file mode 100644 index 00000000000..8050daacdb1 --- /dev/null +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListEditor.tsx @@ -0,0 +1,113 @@ +import React, { createRef, useEffect, useState } from 'react'; +import type { IGenericEditComponent } from '@altinn/ux-editor/components/config/componentConfig'; +import type { SelectionComponentType } from '@altinn/ux-editor/types/FormComponent'; +import type { Option } from 'app-shared/types/Option'; +import type { ApiError } from 'app-shared/types/api/ApiError'; +import type { AxiosError } from 'axios'; +import { useTranslation } from 'react-i18next'; +import { + StudioCodeListEditor, + StudioModal, + StudioSpinner, + type CodeList, +} from '@studio/components'; +import { TableIcon } from '@studio/icons'; +import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; +import { useUpdateOptionListMutation } from '../../../../../hooks/mutations/useUpdateOptionListMutation'; +import { useOptionListsQuery } from '../../../../../hooks/queries/useOptionListsQuery'; +import { useCodeListEditorTexts } from './hooks/useCodeListEditorTexts'; +import { usePreviewContext } from 'app-development/contexts/PreviewContext'; +import { ErrorMessage } from '@digdir/designsystemet-react'; +import classes from './CodeListEditor.module.css'; + +type CodeListEditorProps = Pick, 'component'>; + +export function CodeListEditor({ component }: CodeListEditorProps): React.ReactNode { + const { t } = useTranslation(); + const { org, app } = useStudioEnvironmentParams(); + const { data: optionsListMap, status, error } = useOptionListsQuery(org, app); + const [codeList, setCodeList] = useState([]); + + useEffect(() => { + if (status === 'pending') return; + handleOptionsChange(optionsListMap[component.optionsId]); + }, [optionsListMap, component.optionsId, status]); + + const handleOptionsChange = (options?: Option[]) => { + if (options === undefined) return; + setCodeList(options); + }; + + if (component.optionsId === undefined) return ; + switch (status) { + case 'pending': + return ; // Extract title to nb.json + case 'error': + return ( + + {error instanceof Error ? error.message : t('ux_editor.modal_properties_error_message')} + + ); + case 'success': { + return ( + + ); + } + } +} + +type CodeListEditorModalProps = { + codeList: CodeList; + handleOptionsChange: (options?: Option[]) => void; +} & Pick, 'component'>; + +function CodeListEditorModal({ + codeList, + component, + handleOptionsChange, +}: CodeListEditorModalProps): React.ReactNode { + const { t } = useTranslation(); + const { org, app } = useStudioEnvironmentParams(); + const { doReloadPreview } = usePreviewContext(); + const { mutate: uploadOptionList } = useUpdateOptionListMutation(org, app, { + hideDefaultError: (apiError: AxiosError) => !apiError.response.data.errorCode, + }); + const editorTexts = useCodeListEditorTexts(); + const modalRef = createRef(); + + const handleClose = () => { + uploadOptionList({ optionListId: component.optionsId, optionsList: codeList }); + doReloadPreview(); + modalRef.current?.close(); + }; + + return ( + + } + > + {t('ux_editor.modal_properties_code_list_open_editor')} + + + + + + ); +} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx deleted file mode 100644 index 7a287975c32..00000000000 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/CodeListTableEditor.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import type { IGenericEditComponent } from '@altinn/ux-editor/components/config/componentConfig'; -import type { SelectionComponentType } from '@altinn/ux-editor/types/FormComponent'; -import type { CodeListItem } from '@studio/components'; -import type { Option } from 'app-shared/types/Option'; -import { useTranslation } from 'react-i18next'; -import { StudioCodeListEditor, StudioModal } from '@studio/components'; -import { TableIcon } from '@studio/icons'; -import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; -import { useUpdateOptionListMutation } from '../../../../../hooks/mutations/useUpdateOptionListMutation'; -import { useOptionListsQuery } from '../../../../../hooks/queries/useOptionListsQuery'; -import { - convertCodeListItemListToOptionsList, - convertOptionsListToCodeListItemList, -} from './utils/conversionUtils'; -import { useCodeListEditorTexts } from './hooks/useCodeListEditorTexts'; -import { usePreviewContext } from 'app-development/contexts/PreviewContext'; -import classes from './CodeListTableEditor.module.css'; - -type CodeListTableEditorProps = Pick, 'component'>; - -export function CodeListTableEditor({ component }: CodeListTableEditorProps): React.ReactNode { - const { t } = useTranslation(); - const { org, app } = useStudioEnvironmentParams(); - const { doReloadPreview } = usePreviewContext(); - const { data: optionsListMap, isFetching } = useOptionListsQuery(org, app); - const { mutate: uploadOptionList } = useUpdateOptionListMutation(org, app, { - hideDefaultError: true, - }); - const [codeListItemList, setCodeListItemList] = useState([]); - const editorTexts = useCodeListEditorTexts(); - - useEffect(() => { - if (isFetching) return; - handleOptionsChange(optionsListMap[component.optionsId]); - }, [optionsListMap, component.optionsId, isFetching]); - - const handleOptionsChange = (options: Option[]) => { - const convertedList = convertOptionsListToCodeListItemList(options); - setCodeListItemList(convertedList); - }; - - const handleClose = () => { - const optionsListLocal = convertCodeListItemListToOptionsList(codeListItemList); - uploadOptionList({ optionListId: component.optionsId, optionsList: optionsListLocal }); - doReloadPreview(); - }; - - if (component.optionsId === undefined || component.optionsId === '' || isFetching) return; - return ( - - } - > - {t('ux_editor.modal_properties_code_list_open_editor')} - - - - - - ); -} diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.test.tsx index 4cefdd00a04..bbfee20279b 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.test.tsx @@ -1,36 +1,22 @@ import React from 'react'; import { EditCodeList } from './EditCodeList'; -import { render, screen, waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import { ComponentType } from 'app-shared/types/ComponentType'; -import { optionListIdsMock } from '../../../../../testing/mocks'; +import { optionListIdsMock, renderWithProviders } from '../../../../../testing/mocks'; import userEvent, { type UserEvent } from '@testing-library/user-event'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import type { FormComponent } from '../../../../../types/FormComponent'; -import { - type ServicesContextProps, - ServicesContextProvider, -} from 'app-shared/contexts/ServicesContext'; -import { queriesMock } from 'app-shared/mocks/queriesMock'; -import { PreviewContext, type PreviewContextProps } from 'app-development/contexts/PreviewContext'; +import { componentMocks } from '@altinn/ux-editor/testing/componentMocks'; // Test data: -const mockComponent: FormComponent = { - id: 'c24d0812-0c34-4582-8f31-ff4ce9795e96', - type: ComponentType.Dropdown, - textResourceBindings: { - title: 'ServiceName', - }, - itemType: 'COMPONENT', - dataModelBindings: { simpleBinding: 'some-path' }, +const mockComponent: FormComponent = componentMocks[ComponentType.Dropdown]; +const handleComponentChangeMock = jest.fn(); +const queriesMock = { + getOptionListIds: jest + .fn() + .mockImplementation(() => Promise.resolve(optionListIdsMock)), }; - -const defaultPreviewContextProps: PreviewContextProps = { - shouldReloadPreview: false, - doReloadPreview: jest.fn(), - previewHasLoaded: jest.fn(), -}; - const queryClientMock = createQueryClientMock(); describe('EditCodeList', () => { @@ -46,11 +32,8 @@ describe('EditCodeList', () => { }); it('should call onChange when option list changes', async () => { - const handleComponentChangeMock = jest.fn(); const user = userEvent.setup(); - renderEditCodeList({ - handleComponentChange: handleComponentChangeMock, - }); + renderEditCodeList(); await waitFor(() => screen.findByRole('combobox')); @@ -59,10 +42,8 @@ describe('EditCodeList', () => { }); it('should remove options property (if it exists) when optionsId property changes', async () => { - const handleComponentChangeMock = jest.fn(); const user = userEvent.setup(); renderEditCodeList({ - handleComponentChange: handleComponentChangeMock, componentProps: { options: [{ label: 'option1', value: 'option1' }], }, @@ -170,31 +151,18 @@ const userFindFileAndUpload = async (user: UserEvent, file: File) => { await user.upload(fileInput, file); }; -const renderEditCodeList = ({ - handleComponentChange = jest.fn(), - queries = { - getOptionListIds: jest - .fn() - .mockImplementation(() => Promise.resolve(optionListIdsMock)), - }, - componentProps = {}, -} = {}) => { - const allQueries: ServicesContextProps = { - ...queriesMock, - ...queries, - }; - - return render( - - - - - , +const renderEditCodeList = ({ queries = queriesMock, componentProps = {} } = {}) => { + return renderWithProviders( + , + { + queries: queries, + queryClient: queryClientMock, + }, ); }; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.tsx index 7575017d4b0..86e3762dc32 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/EditCodeList.tsx @@ -16,7 +16,7 @@ import type { AxiosError } from 'axios'; import type { ApiError } from 'app-shared/types/api/ApiError'; import { toast } from 'react-toastify'; import classes from './EditCodeList.module.css'; -import { CodeListTableEditor } from './CodeListTableEditor'; +import { CodeListEditor } from './CodeListEditor'; import { shouldDisplayFeature } from 'app-shared/utils/featureToggleUtils'; export function EditCodeList({ @@ -27,7 +27,7 @@ export function EditCodeList({ const { org, app } = useStudioEnvironmentParams(); const { data: optionListIds } = useOptionListIdsQuery(org, app); const { mutate: uploadOptionList } = useAddOptionListMutation(org, app, { - hideDefaultError: true, + hideDefaultError: (error: AxiosError) => !error.response.data.errorCode, }); const handleOptionsIdChange = (optionsId: string) => { @@ -76,7 +76,7 @@ export function EditCodeList({ return ( <> - {shouldDisplayFeature('codeListEditor') && } + {shouldDisplayFeature('codeListEditor') && } { helpText: t('ux_editor.options_text_help_text'), itemDescription: (number) => t('ux_editor.modal_properties_code_list_item_description', { number }), - itemHelpText: (number) => t('ux_editor.modal_properties_code_list_item_helpText', { number }), + itemHelpText: (number) => t('ux_editor.modal_properties_code_list_item_help_text', { number }), itemLabel: (number) => t('ux_editor.modal_properties_code_list_item_label', { number }), itemValue: (number) => t('ux_editor.modal_properties_code_list_item_value', { number }), label: t('ux_editor.options_text_label'), diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.test.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.test.ts deleted file mode 100644 index d09ee78f384..00000000000 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { Option } from 'app-shared/types/Option'; -import type { CodeListItem } from '@studio/components'; -import { - convertOptionsListToCodeListItemList, - convertCodeListItemListToOptionsList, -} from './conversionUtils'; - -// Test data: -const optionsList: Option[] = [ - { value: 'test', label: 'test label', description: 'description', helpText: 'help text' }, -]; -const codeListItemList: CodeListItem[] = [ - { value: 'test', label: 'test label', description: 'description', helpText: 'help text' }, -]; - -describe('conversionUtils', () => { - describe('convertOptionsListToCodeListItem', () => { - it('should return empty list if input is undefined', () => { - expect(convertOptionsListToCodeListItemList(undefined)).toEqual([]); - }); - - it('should return empty list if input is an empty list', () => { - expect(convertOptionsListToCodeListItemList([])).toEqual([]); - }); - - it('should return converted list', () => { - expect(convertOptionsListToCodeListItemList(optionsList)).toStrictEqual(codeListItemList); - }); - }); - - describe('convertCodeListItemListToOptionsList', () => { - it('should return empty list if input is undefined', () => { - expect(convertCodeListItemListToOptionsList(undefined)).toEqual([]); - }); - - it('should return empty list if input is an empty list', () => { - expect(convertCodeListItemListToOptionsList([])).toEqual([]); - }); - - it('should return converted list', () => { - expect(convertCodeListItemListToOptionsList(codeListItemList)).toStrictEqual(optionsList); - }); - }); -}); diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.ts deleted file mode 100644 index bceebcd8a21..00000000000 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditCodeList/utils/conversionUtils.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { Option } from 'app-shared/types/Option'; -import type { CodeListItem } from '@studio/components'; - -export const convertOptionsListToCodeListItemList = (optionList: Option[]): CodeListItem[] => { - if (optionList === undefined) return []; - - const tempList: CodeListItem[] = []; - if (optionList.length === 0) return tempList; - - optionList.forEach((option: Option) => { - tempList.push({ - label: option.label, - value: option.value, - description: option.description ?? '', - helpText: option.helpText ?? '', - }); - }); - return tempList; -}; - -export const convertCodeListItemListToOptionsList = ( - codeListItemList: CodeListItem[], -): Option[] => { - if (codeListItemList === undefined) return []; - - const tempList: Option[] = []; - if (codeListItemList.length === 0) return tempList; - - codeListItemList.forEach((codeListItem: CodeListItem) => { - tempList.push({ - label: codeListItem.label, - value: codeListItem.value, - description: codeListItem.description ?? '', - helpText: codeListItem.helpText ?? '', - }); - }); - return tempList; -}; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.test.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.test.tsx index 65c00264a2b..0b4c9606068 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.test.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/EditOptions.test.tsx @@ -1,18 +1,14 @@ import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import { EditOptions } from './EditOptions'; import { textMock } from '@studio/testing/mocks/i18nMock'; import { ComponentType } from 'app-shared/types/ComponentType'; import type { FormComponent } from '../../../../types/FormComponent'; import type { FormItem } from '../../../../types/FormItem'; -import { - type ServicesContextProps, - ServicesContextProvider, -} from 'app-shared/contexts/ServicesContext'; +import type { ServicesContextProps } from 'app-shared/contexts/ServicesContext'; import { createQueryClientMock } from 'app-shared/mocks/queryClientMock'; import userEvent from '@testing-library/user-event'; -import { queriesMock } from 'app-shared/mocks/queriesMock'; -import { PreviewContext, type PreviewContextProps } from 'app-development/contexts/PreviewContext'; +import { renderWithProviders } from '@altinn/ux-editor/testing/mocks'; const mockComponent: FormComponent = { id: 'c24d0812-0c34-4582-8f31-ff4ce9795e96', @@ -25,12 +21,6 @@ const mockComponent: FormComponent = { dataModelBindings: { simpleBinding: '' }, }; -const defaultPreviewContextProps: PreviewContextProps = { - shouldReloadPreview: false, - doReloadPreview: jest.fn(), - previewHasLoaded: jest.fn(), -}; - const queryClientMock = createQueryClientMock(); const renderEditOptions = async ({ @@ -46,26 +36,19 @@ const renderEditOptions = async { - const component = { - ...mockComponent, - ...componentProps, - }; - const allQueries = { - ...queriesMock, - ...queries, - }; - - render( - - - - , - - , + return renderWithProviders( + , + { + queries: queries, + queryClient: queryClientMock, + }, ); }; diff --git a/frontend/packages/ux-editor/src/hooks/mutations/useUpdateOptionListMutation.ts b/frontend/packages/ux-editor/src/hooks/mutations/useUpdateOptionListMutation.ts index 031a7ef9b9b..4bd37894dda 100644 --- a/frontend/packages/ux-editor/src/hooks/mutations/useUpdateOptionListMutation.ts +++ b/frontend/packages/ux-editor/src/hooks/mutations/useUpdateOptionListMutation.ts @@ -17,11 +17,9 @@ export const useUpdateOptionListMutation = (org: string, app: string, meta?: Mut mutationFn: ({ optionListId, optionsList }: UpdateOptionListMutationArgs) => { return updateOptionList(org, app, optionListId, optionsList); }, - onSuccess: async () => { - await Promise.all([ - queryClient.invalidateQueries({ queryKey: [QueryKey.OptionListIds, org, app] }), - queryClient.invalidateQueries({ queryKey: [QueryKey.OptionLists, org, app] }), - ]); + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QueryKey.OptionListIds, org, app] }); + queryClient.invalidateQueries({ queryKey: [QueryKey.OptionLists, org, app] }); }, meta, }); diff --git a/frontend/packages/ux-editor/src/hooks/queries/useOptionListsQuery.ts b/frontend/packages/ux-editor/src/hooks/queries/useOptionListsQuery.ts index 473c41213b4..e0992110b80 100644 --- a/frontend/packages/ux-editor/src/hooks/queries/useOptionListsQuery.ts +++ b/frontend/packages/ux-editor/src/hooks/queries/useOptionListsQuery.ts @@ -9,16 +9,8 @@ export const useOptionListsQuery = ( app: string, ): UseQueryResult> => { const { getOptionLists } = useServicesContext(); - return useQuery({ queryKey: [QueryKey.OptionLists, org, app], - queryFn: () => - getOptionLists(org, app).then((result) => { - const optionLists = {}; - Object.keys(result).forEach((optionListId) => { - optionLists[optionListId] = result[optionListId]; - }); - return optionLists; - }), + queryFn: () => getOptionLists(org, app).then((result) => result || new Map()), }); }; diff --git a/frontend/packages/ux-editor/src/testing/mocks.tsx b/frontend/packages/ux-editor/src/testing/mocks.tsx index fa28801059f..412ab2caf20 100644 --- a/frontend/packages/ux-editor/src/testing/mocks.tsx +++ b/frontend/packages/ux-editor/src/testing/mocks.tsx @@ -14,6 +14,7 @@ import type { AppContextProps } from '../AppContext'; import { AppContext } from '../AppContext'; import { appContextMock } from './appContextMock'; import { queryClientMock } from 'app-shared/mocks/queryClientMock'; +import { PreviewContext, type PreviewContextProps } from 'app-development/contexts/PreviewContext'; export const formLayoutSettingsMock: ILayoutSettings = { pages: { @@ -25,23 +26,35 @@ export const textLanguagesMock = ['nb', 'nn', 'en']; export const optionListIdsMock: string[] = ['test-1', 'test-2']; +const defaultPreviewContextProps: PreviewContextProps = { + shouldReloadPreview: false, + doReloadPreview: jest.fn(), + previewHasLoaded: jest.fn(), +}; + type WrapperArgs = { queries: Partial; queryClient: QueryClient; appContextProps: Partial; + previewContextProps?: Partial; }; const wrapper = ({ queries = {}, queryClient = queryClientMock, appContextProps = {}, + previewContextProps = {}, }: WrapperArgs) => { const renderComponent = (component: ReactNode) => ( - {component} + + {component} + @@ -54,11 +67,17 @@ export interface ExtendedRenderOptions extends Omit { queries?: Partial; queryClient?: QueryClient; appContextProps?: Partial; + previewContextProps?: Partial; } export const renderHookWithProviders = ( hook: () => any, - { queries = {}, queryClient = queryClientMock, appContextProps = {} }: ExtendedRenderOptions = {}, + { + queries = {}, + queryClient = queryClientMock, + appContextProps = {}, + previewContextProps = {}, + }: ExtendedRenderOptions = {}, ) => { return renderHook(hook, { wrapper: ({ children }) => @@ -66,6 +85,7 @@ export const renderHookWithProviders = ( queries, queryClient, appContextProps, + previewContextProps, })(children), }); }; @@ -76,6 +96,7 @@ export const renderWithProviders = ( queries = {}, queryClient = queryClientMock, appContextProps = {}, + previewContextProps = {}, ...renderOptions }: Partial = {}, ) => { @@ -86,6 +107,7 @@ export const renderWithProviders = ( queries, queryClient, appContextProps, + previewContextProps, })(children), ...renderOptions, }),