Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Add text resources to create code list dialog #14646

Merged
merged 5 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -62,7 +62,7 @@ export const StudioTextResourceInput = forwardRef<HTMLInputElement, StudioTextRe
const handleTextResourceChange = (newTextResource: TextResource): void => {
const newList = changeTextResourceInList(textResources, newTextResource);
setTextResources(newList);
onChangeTextResource(newTextResource);
onChangeTextResource?.(newTextResource);
};

const rootClass = cn(givenClass, classes.container);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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<void> => {
Expand All @@ -198,8 +234,12 @@ const openAndGetFirstLabelField = async (
): Promise<HTMLElement> => {
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 =>
Expand All @@ -215,3 +255,37 @@ const queryCodeListHeading = (codeListTitle: string): HTMLElement =>

const renderCodeListPage = (props: Partial<CodeListPageProps> = {}): RenderResult =>
render(<CodeListPage {...defaultCodeListPageProps} {...props} />);

const openCreateDialog = async (user: UserEvent): Promise<HTMLElement> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
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}`;
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
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';
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';

Expand Down Expand Up @@ -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<CodeListData, 'title'>(codeListsData, 'title');

const handleUploadCodeList = (uploadedCodeList: File) => {
Expand All @@ -70,22 +87,26 @@ export function CodeListPage({
<StudioHeading size='small'>{t('app_content_library.code_lists.page_name')}</StudioHeading>
<CodeListsCounterMessage codeListsCount={codeListsData.length} />
<CodeListsActionsBar
onChangeTextResource={handleChangeTextResource}
onUploadCodeList={handleUploadCodeList}
onUpdateCodeList={onUpdateCodeList}
codeListNames={codeListTitles}
onSetSearchString={setSearchString}
textResources={textResourcesForLanguage}
/>
<CodeLists
codeListsData={filteredCodeLists}
onChangeTextResource={handleChangeTextResource}
onDeleteCodeList={onDeleteCodeList}
onUpdateCodeListId={handleUpdateCodeListId}
onUpdateCodeList={onUpdateCodeList}
onUpdateTextResource={onUpdateTextResource}
codeListInEditMode={codeListInEditMode}
codeListNames={codeListTitles}
codeListsUsages={codeListsUsages}
textResources={textResources}
textResources={textResourcesForLanguage}
/>
</div>
);
}

const language: string = 'nb'; // Todo: Let the user choose the language: https://github.com/Altinn/altinn-studio/issues/14572
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { CodeList, CodeListEditorTexts } from '@studio/components';
import type { CodeList, CodeListEditorTexts, TextResource } from '@studio/components';
import {
StudioDeleteButton,
StudioModal,
StudioDisplayTile,
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';
Expand All @@ -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 },
Expand All @@ -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;

Expand All @@ -86,9 +67,9 @@ export function EditCodeList({
codeList={codeList}
onAddOrDeleteItem={handleCodeListChange}
onBlurAny={handleCodeListChange}
onChangeTextResource={handleChangeTextResource}
onChangeTextResource={onChangeTextResource}
texts={editorTexts}
textResources={textResourcesForLanguage}
textResources={textResources}
/>
<CodeListButtons
codeListHasUsages={codeListHasUsages}
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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();
Expand Down Expand Up @@ -49,7 +54,12 @@ export function CodeListsActionsBar({
clearButtonLabel={t('app_content_library.code_lists.clear_search_button_label')}
onClear={handleClearSearch}
/>
<CreateNewCodeListModal onUpdateCodeList={onUpdateCodeList} codeListNames={codeListNames} />
<CreateNewCodeListModal
codeListNames={codeListNames}
onChangeTextResource={onChangeTextResource}
onUpdateCodeList={onUpdateCodeList}
textResources={textResources}
/>
<StudioFileUploader
accept='.json'
size='small'
Expand Down
Loading