diff --git a/frontend/libs/studio-components/src/components/StudioTextResourceInput/StudioTextResourceInput.tsx b/frontend/libs/studio-components/src/components/StudioTextResourceInput/StudioTextResourceInput.tsx index f7d9788c35e..254fceca28c 100644 --- a/frontend/libs/studio-components/src/components/StudioTextResourceInput/StudioTextResourceInput.tsx +++ b/frontend/libs/studio-components/src/components/StudioTextResourceInput/StudioTextResourceInput.tsx @@ -27,7 +27,7 @@ type TextResourceInputPropsBase = { currentIdClass?: string; inputClass?: string; onChangeCurrentId: (id: string | null) => void; - onChangeTextResource: (textResource: TextResource) => void; + onChangeTextResource?: (textResource: TextResource) => void; textResources: TextResource[]; texts: TextResourceInputTexts; toggleClass?: string; @@ -62,7 +62,7 @@ export const StudioTextResourceInput = forwardRef { const newList = changeTextResourceInList(textResources, newTextResource); setTextResources(newList); - onChangeTextResource(newTextResource); + onChangeTextResource?.(newTextResource); }; const rootClass = cn(givenClass, classes.container); diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.test.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.test.tsx index 8fe2954d7e3..b9697ae9bf5 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.test.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.test.tsx @@ -13,6 +13,7 @@ import { } from '../../../../test-data/codeListDataList'; import { ArrayUtils } from '@studio/pure-functions'; import { label1ResourceNb, textResources } from '../../../../test-data/textResources'; +import type { TextResource } from '../../../../types/TextResource'; import type { TextResourceWithLanguage } from '../../../../types/TextResourceWithLanguage'; const onDeleteCodeList = jest.fn(); @@ -182,6 +183,41 @@ describe('CodeListPage', () => { expect(onUpdateTextResource).toHaveBeenCalledTimes(newLabel.length); expect(onUpdateTextResource).toHaveBeenLastCalledWith(expectedObject); }); + + it('Renders with text resources in the input fields of the create dialog when given', async () => { + const user = userEvent.setup(); + + renderCodeListPage({ textResources }); + const dialog = await openCreateDialog(user); + await addCodeListItem(user, dialog); + await openSearchModeForFirstLabel(user, dialog); + await openFirstLabelCombobox(user, dialog); + + expect(getTextResourceOption(label1ResourceNb)).toBeInTheDocument(); + }); + + it('Calls onUpdateTextResource with the new text resource and the default language when a text resource is changed in the create dialog', async () => { + const user = userEvent.setup(); + const onUpdateTextResource = jest.fn(); + const newLabel = 'Ny ledetekst'; + + renderCodeListPage({ textResources, onUpdateTextResource }); + const dialog = await openCreateDialog(user); + await addCodeListItem(user, dialog); + await openSearchModeForFirstLabel(user, dialog); + await openFirstLabelCombobox(user, dialog); + await user.click(getTextResourceOption(label1ResourceNb)); + await openEditModeForFirstLabel(user, dialog); + await user.type(getFirstLabelField(dialog), newLabel); + + const expectedLanguage = 'nb'; + const expectedObject: TextResourceWithLanguage = { + language: expectedLanguage, + textResource: { ...label1ResourceNb, value: newLabel }, + }; + expect(onUpdateTextResource).toHaveBeenCalledTimes(newLabel.length); + expect(onUpdateTextResource).toHaveBeenLastCalledWith(expectedObject); + }); }); const uploadCodeList = async (user: UserEvent, fileName: string): Promise => { @@ -198,8 +234,12 @@ const openAndGetFirstLabelField = async ( ): Promise => { await user.click(getCodeListHeading(codeListTitle)); const accordion = getCodeListAccordion(codeListTitle); + return getFirstLabelField(accordion); +}; + +const getFirstLabelField = (area: HTMLElement): HTMLElement => { const labelFieldLabel = textMock('code_list_editor.text_resource.label.value', { number: 1 }); - return within(accordion).getByRole('textbox', { name: labelFieldLabel }); + return within(area).getByRole('textbox', { name: labelFieldLabel }); }; const getCodeListAccordion = (codeListTitle: string): HTMLElement => @@ -215,3 +255,37 @@ const queryCodeListHeading = (codeListTitle: string): HTMLElement => const renderCodeListPage = (props: Partial = {}): RenderResult => render(); + +const openCreateDialog = async (user: UserEvent): Promise => { + const createButtonLabel = textMock('app_content_library.code_lists.create_new_code_list'); + await user.click(screen.getByRole('button', { name: createButtonLabel })); + return screen.getByRole('dialog'); +}; + +const addCodeListItem = async (user: UserEvent, area: HTMLElement): Promise => { + const addButtonLabel = textMock('code_list_editor.add_option'); + await user.click(within(area).getByRole('button', { name: addButtonLabel })); +}; + +const openSearchModeForFirstLabel = async (user: UserEvent, area: HTMLElement): Promise => { + const radioLabel = textMock('code_list_editor.text_resource.label.search_mode', { number: 1 }); + const radio = within(area).getByRole('radio', { name: radioLabel }); + await user.click(radio); +}; + +const openEditModeForFirstLabel = async (user: UserEvent, area: HTMLElement): Promise => { + const radioLabel = textMock('code_list_editor.text_resource.label.edit_mode', { number: 1 }); + const radio = await within(area).findByRole('radio', { name: radioLabel }); + await user.click(radio); +}; + +const openFirstLabelCombobox = async (user: UserEvent, area: HTMLElement): Promise => { + const comboboxLabel = textMock('code_list_editor.text_resource.label.select', { number: 1 }); + const combobox = within(area).getByRole('combobox', { name: comboboxLabel }); + await user.click(combobox); +}; + +const getTextResourceOption = (textResource: TextResource): HTMLElement => + screen.getByRole('option', { name: retrieveOptionName(textResource) }); + +const retrieveOptionName = ({ value, id }: TextResource): string => `${value} ${id}`; diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.tsx index 651b63c497b..c89dc35262b 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListPage.tsx @@ -1,6 +1,6 @@ -import React, { useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; +import type { CodeList, TextResource } from '@studio/components'; import { StudioHeading } from '@studio/components'; -import type { CodeList } from '@studio/components'; import { useTranslation } from 'react-i18next'; import { CodeListsActionsBar } from './CodeListsActionsBar'; import { CodeLists } from './CodeLists'; @@ -8,7 +8,11 @@ import { CodeListsCounterMessage } from './CodeListsCounterMessage'; import classes from './CodeListPage.module.css'; import { ArrayUtils, FileNameUtils } from '@studio/pure-functions'; import type { CodeListReference } from './types/CodeListReference'; -import { filterCodeLists } from './utils'; +import { + filterCodeLists, + getTextResourcesForLanguage, + createTextResourceWithLanguage, +} from './utils'; import type { TextResourceWithLanguage } from '../../../../types/TextResourceWithLanguage'; import type { TextResources } from '../../../../types/TextResources'; @@ -53,6 +57,19 @@ export function CodeListPage({ [codeListsData, searchString], ); + const textResourcesForLanguage = useMemo( + () => getTextResourcesForLanguage(language, textResources), + [textResources], + ); + + const handleChangeTextResource = useCallback( + (textResource: TextResource) => { + const updatedTextResource = createTextResourceWithLanguage(language, textResource); + onUpdateTextResource?.(updatedTextResource); + }, + [onUpdateTextResource], + ); + const codeListTitles = ArrayUtils.mapByKey(codeListsData, 'title'); const handleUploadCodeList = (uploadedCodeList: File) => { @@ -70,22 +87,26 @@ export function CodeListPage({ {t('app_content_library.code_lists.page_name')} ); } + +const language: string = 'nb'; // Todo: Let the user choose the language: https://github.com/Altinn/altinn-studio/issues/14572 diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.tsx index 553883fbc08..05ad580a165 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/CodeLists.tsx @@ -7,19 +7,18 @@ import { Trans, useTranslation } from 'react-i18next'; import type { CodeListIdSource, CodeListReference } from '../types/CodeListReference'; import classes from './CodeLists.module.css'; import { getCodeListSourcesById, getCodeListUsageCount } from '../utils'; -import type { TextResourceWithLanguage } from '../../../../../types/TextResourceWithLanguage'; -import type { TextResources } from '../../../../../types/TextResources'; +import type { TextResource } from '@studio/components'; export type CodeListsProps = { codeListsData: CodeListData[]; + onChangeTextResource?: (textResource: TextResource) => void; onDeleteCodeList: (codeListId: string) => void; onUpdateCodeListId: (codeListId: string, newCodeListId: string) => void; onUpdateCodeList: (updatedCodeList: CodeListWithMetadata) => void; - onUpdateTextResource?: (textResource: TextResourceWithLanguage) => void; codeListInEditMode: string | undefined; codeListNames: string[]; codeListsUsages: CodeListReference[]; - textResources?: TextResources; + textResources?: TextResource[]; }; export function CodeLists({ diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.tsx index 81f9b2bfd24..7eca46fe8da 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/EditCodeList.tsx @@ -1,4 +1,4 @@ -import type { CodeList, CodeListEditorTexts } from '@studio/components'; +import type { CodeList, CodeListEditorTexts, TextResource } from '@studio/components'; import { StudioDeleteButton, StudioModal, @@ -6,7 +6,7 @@ import { StudioCodeListEditor, StudioToggleableTextfield, } from '@studio/components'; -import React, { useCallback, useMemo } from 'react'; +import React from 'react'; import { useTranslation } from 'react-i18next'; import type { CodeListWithMetadata } from '../../CodeListPage'; import { useCodeListEditorTexts } from '../../hooks/useCodeListEditorTexts'; @@ -16,43 +16,32 @@ import { useInputCodeListNameErrorMessage } from '../../hooks/useInputCodeListNa import classes from './EditCodeList.module.css'; import type { CodeListIdSource } from '../../types/CodeListReference'; import { CodeListUsages } from './CodeListUsages/CodeListUsages'; -import type { TextResources } from '../../../../../../types/TextResources'; -import type { TextResource } from '../../../../../../types/TextResource'; -import type { TextResourceWithLanguage } from '../../../../../../types/TextResourceWithLanguage'; -import { createTextResourceWithLanguage, getTextResourcesForLanguage } from './utils'; export type EditCodeListProps = { codeList: CodeList; codeListTitle: string; + onChangeTextResource?: (textResource: TextResource) => void; onDeleteCodeList: (codeListId: string) => void; onUpdateCodeListId: (codeListId: string, newCodeListId: string) => void; onUpdateCodeList: (updatedCodeList: CodeListWithMetadata) => void; - onUpdateTextResource?: (textResource: TextResourceWithLanguage) => void; codeListNames: string[]; codeListSources: CodeListIdSource[]; - textResources?: TextResources; + textResources?: TextResource[]; }; -const language: string = 'nb'; // Todo: Let the user choose the language: https://github.com/Altinn/altinn-studio/issues/14572 - export function EditCodeList({ codeList, codeListTitle, + onChangeTextResource, onDeleteCodeList, onUpdateCodeListId, onUpdateCodeList, - onUpdateTextResource, codeListNames, codeListSources, textResources, }: EditCodeListProps): React.ReactElement { const editorTexts: CodeListEditorTexts = useCodeListEditorTexts(); - const textResourcesForLanguage = useMemo( - () => getTextResourcesForLanguage(language, textResources), - [textResources], - ); - const handleCodeListChange = (updatedCodeList: CodeList): void => { const updatedCodeListWithMetadata = updateCodeListWithMetadata( { title: codeListTitle, codeList: codeList }, @@ -63,14 +52,6 @@ export function EditCodeList({ const handleDeleteCodeList = (): void => onDeleteCodeList(codeListTitle); - const handleChangeTextResource = useCallback( - (textResource: TextResource) => { - const updatedTextResource = createTextResourceWithLanguage(language, textResource); - onUpdateTextResource?.(updatedTextResource); - }, - [onUpdateTextResource], - ); - const codeListHasUsages = codeListSources.length > 0; const isCodeListEditable = codeListSources.length === 0; @@ -86,9 +67,9 @@ export function EditCodeList({ codeList={codeList} onAddOrDeleteItem={handleCodeListChange} onBlurAny={handleCodeListChange} - onChangeTextResource={handleChangeTextResource} + onChangeTextResource={onChangeTextResource} texts={editorTexts} - textResources={textResourcesForLanguage} + textResources={textResources} /> { - describe('getTextResourcesForLanguage', () => { - it('Returns the list of text resources for the given language', () => { - expect(getTextResourcesForLanguage('nb', textResources)).toEqual(textResourcesNb); - }); - - it('Returns undefined when the language does not exist', () => { - expect(getTextResourcesForLanguage('eo', textResources)).toBeUndefined(); - }); - - it('Returns undefined when the textResources parameter is undefined', () => { - expect(getTextResourcesForLanguage('nb', undefined)).toBeUndefined(); - }); - }); - - describe('createTextResourceWithLanguage', () => { - it('Creates a TextResourceWithLanguage object from the parameters', () => { - const language = 'nb'; - const textResource = label1ResourceNb; - const expectedResult: TextResourceWithLanguage = { language, textResource }; - expect(createTextResourceWithLanguage(language, textResource)).toEqual(expectedResult); - }); - }); -}); diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/utils.ts b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/utils.ts deleted file mode 100644 index af555d9e755..00000000000 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeLists/EditCodeList/utils.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { TextResource } from '../../../../../../types/TextResource'; -import type { TextResources } from '../../../../../../types/TextResources'; -import type { TextResourceWithLanguage } from '../../../../../../types/TextResourceWithLanguage'; - -export const getTextResourcesForLanguage = ( - language: string, - textResources?: TextResources, -): TextResource[] | undefined => textResources?.[language]; - -export const createTextResourceWithLanguage = ( - language: string, - textResource: TextResource, -): TextResourceWithLanguage => ({ language, textResource }); diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CodeListsActionsBar.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CodeListsActionsBar.tsx index e827fddc4af..a3327896956 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CodeListsActionsBar.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/CodeListsActionsBar/CodeListsActionsBar.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import type { TextResource } from '@studio/components'; import { StudioFileUploader, StudioSearch } from '@studio/components'; import type { ChangeEvent } from 'react'; import classes from './CodeListsActionsBar.module.css'; @@ -10,17 +11,21 @@ import { useUploadCodeListNameErrorMessage } from '../hooks/useUploadCodeListNam import { toast } from 'react-toastify'; export type CodeListsActionsBarProps = { + onChangeTextResource?: (textResource: TextResource) => void; onUploadCodeList: (updatedCodeList: File) => void; onUpdateCodeList: (updatedCodeList: CodeListWithMetadata) => void; codeListNames: string[]; onSetSearchString: (searchString: string) => void; + textResources?: TextResource[]; }; export function CodeListsActionsBar({ + onChangeTextResource, onUploadCodeList, onUpdateCodeList, codeListNames, onSetSearchString, + textResources, }: CodeListsActionsBarProps) { const { t } = useTranslation(); const getInvalidUploadFileNameErrorMessage = useUploadCodeListNameErrorMessage(); @@ -49,7 +54,12 @@ export function CodeListsActionsBar({ clearButtonLabel={t('app_content_library.code_lists.clear_search_button_label')} onClear={handleClearSearch} /> - + void; onUpdateCodeList: (codeListWithMetadata: CodeListWithMetadata) => void; codeListNames: string[]; + textResources?: TextResource[]; }; export function CreateNewCodeListModal({ + onChangeTextResource, onUpdateCodeList, codeListNames, + textResources, }: CreateNewCodeListModalProps) { const { t } = useTranslation(); const modalRef = createRef(); @@ -46,8 +50,10 @@ export function CreateNewCodeListModal({ @@ -57,15 +63,19 @@ export function CreateNewCodeListModal({ type CreateNewCodeListProps = { codeList: CodeList; codeListNames: string[]; + onChangeTextResource?: (textResource: TextResource) => void; onUpdateCodeList: (codeListWithMetadata: CodeListWithMetadata) => void; onCloseModal: () => void; + textResources?: TextResource[]; }; function CreateNewCodeList({ codeList, codeListNames, + onChangeTextResource, onUpdateCodeList, onCloseModal, + textResources, }: CreateNewCodeListProps) { const { t } = useTranslation(); const editorTexts: CodeListEditorTexts = useCodeListEditorTexts(); @@ -125,8 +135,10 @@ function CreateNewCodeList({ { expect(codeListSources).toEqual([]); }); }); + describe('getCodeListUsageCount', () => { it('returns the total count of all component IDs across all codeListSources', () => { const codeListSources: CodeListIdSource[] = [ @@ -136,4 +149,27 @@ describe('utils', () => { expect(result).toEqual([{ title: 'Cakes & Cookies' }]); }); }); + + describe('getTextResourcesForLanguage', () => { + it('Returns the list of text resources for the given language', () => { + expect(getTextResourcesForLanguage('nb', textResources)).toEqual(textResourcesNb); + }); + + it('Returns undefined when the language does not exist', () => { + expect(getTextResourcesForLanguage('eo', textResources)).toBeUndefined(); + }); + + it('Returns undefined when the textResources parameter is undefined', () => { + expect(getTextResourcesForLanguage('nb', undefined)).toBeUndefined(); + }); + }); + + describe('createTextResourceWithLanguage', () => { + it('Creates a TextResourceWithLanguage object from the parameters', () => { + const language = 'nb'; + const textResource = label1ResourceNb; + const expectedResult: TextResourceWithLanguage = { language, textResource }; + expect(createTextResourceWithLanguage(language, textResource)).toEqual(expectedResult); + }); + }); }); diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/utils/utils.ts b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/utils/utils.ts index f36ffa5b310..c57a51aa619 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/utils/utils.ts +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeListPage/utils/utils.ts @@ -1,5 +1,8 @@ import type { CodeListIdSource, CodeListReference } from '../types/CodeListReference'; import type { CodeListData } from '../CodeListPage'; +import type { TextResources } from '../../../../../types/TextResources'; +import type { TextResource } from '@studio/components'; +import type { TextResourceWithLanguage } from '../../../../../types/TextResourceWithLanguage'; export const getCodeListSourcesById = ( codeListsUsages: CodeListReference[], @@ -33,3 +36,13 @@ function caseInsensitiveMatch(target: string, searchString: string): boolean { const lowerCaseSearchString = searchString.toLowerCase(); return lowerCaseTarget.includes(lowerCaseSearchString); } + +export const getTextResourcesForLanguage = ( + language: string, + textResources?: TextResources, +): TextResource[] | undefined => textResources?.[language]; + +export const createTextResourceWithLanguage = ( + language: string, + textResource: TextResource, +): TextResourceWithLanguage => ({ language, textResource }); diff --git a/frontend/libs/studio-content-library/src/index.ts b/frontend/libs/studio-content-library/src/index.ts index d06bfbe240b..ac53e2becd0 100644 --- a/frontend/libs/studio-content-library/src/index.ts +++ b/frontend/libs/studio-content-library/src/index.ts @@ -5,3 +5,6 @@ export type { CodeListIdSource, CodeListReference, } from './ContentLibrary/LibraryBody/pages'; +export type { TextResource } from './types/TextResource'; +export type { TextResourceWithLanguage } from './types/TextResourceWithLanguage'; +export type { TextResources } from './types/TextResources';