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

feat: Infer and coerce code list value types in StudioCodeListEditor #14289

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9f04533
Preserve original value type
ErlingHauan Nov 21, 2024
1d0c887
Merge remote-tracking branch 'origin' into feat/preserve-value-type-i…
ErlingHauan Nov 27, 2024
2e72570
Merge remote-tracking branch 'origin' into feat/preserve-value-type-i…
ErlingHauan Nov 27, 2024
d14c29c
Improve boolean check in coerceValue
ErlingHauan Nov 28, 2024
1b7d0ac
Merge remote-tracking branch 'origin' into feat/preserve-value-type-i…
ErlingHauan Dec 6, 2024
b1b4f89
merge main to branch
ErlingHauan Dec 10, 2024
1ed6f79
add type tag to StudioCodeListEditor
ErlingHauan Dec 11, 2024
e4c1aa1
Add type tag to UI and set type of value if all values in a list matc…
ErlingHauan Dec 11, 2024
9500733
Merge branch 'main' of https://github.com/Altinn/altinn-studio into f…
ErlingHauan Dec 11, 2024
0e83ff7
Add text resources
ErlingHauan Dec 11, 2024
2149eda
Add tooltip text resource
ErlingHauan Dec 12, 2024
3e706cb
Add tests
ErlingHauan Dec 12, 2024
9e853e2
Merge main to branch
ErlingHauan Dec 12, 2024
c195e94
Add tests
ErlingHauan Dec 13, 2024
80f0dd0
Create utility directory and move files
ErlingHauan Dec 16, 2024
24c28c0
Update tests
ErlingHauan Dec 16, 2024
3b1fff8
Merge remote-tracking branch 'origin' into feat/preserve-value-type-i…
ErlingHauan Dec 16, 2024
2cda2cf
Clean up
ErlingHauan Dec 16, 2024
732c357
Fix indirect test failing
ErlingHauan Dec 16, 2024
e123ca5
Change logic to check actual type of value
ErlingHauan Dec 18, 2024
bb305de
Update tests
ErlingHauan Dec 19, 2024
632f094
Fix merge conflicts
ErlingHauan Dec 19, 2024
d3ed8e3
Make codeListWithDuplicatedValues more concise
ErlingHauan Dec 19, 2024
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 @@ -33,7 +33,7 @@ public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOp
writer.WriteBooleanValue(b);
break;
default:
throw new InvalidOptionsFormatException($"{value} is an unsupported type for Value field. Accepted types are string, double and bool.");
throw new InvalidOptionsFormatException($"{value} with type {value.GetType()} is unsupported for Value field. Accepted types are string, double and bool.");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I discovered a backend bug when enabling editing of numbers - see issue #14290.
The added information in this exception makes it easier to see what is wrong.

}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react';
import { StudioCodeListEditor } from './StudioCodeListEditor';
import { codeListWithoutTextResources } from './test-data/codeListWithoutTextResources';
import { codeListWithStrings } from './test-data/codeLists';
import { texts } from './test-data/texts';

type Story = StoryObj<typeof StudioCodeListEditor>;
Expand All @@ -13,7 +13,7 @@ export default meta;

export const Preview: Story = {
args: {
codeList: codeListWithoutTextResources,
codeList: codeListWithStrings,
texts,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,25 @@ import React from 'react';
import { render, screen } from '@testing-library/react';
import type { StudioCodeListEditorProps } from './StudioCodeListEditor';
import { StudioCodeListEditor } from './StudioCodeListEditor';
import type { CodeList } from './types/CodeList';
import userEvent from '@testing-library/user-event';
import { codeListWithoutTextResources } from './test-data/codeListWithoutTextResources';
import { texts } from './test-data/texts';
import {
codeListWithDuplicatedValues,
codeListWithNumbers,
codeListWithStrings,
} from './test-data/codeLists';

// Test data:
const onBlurAny = jest.fn();
const onChange = jest.fn();
const onInvalid = jest.fn();
const defaultProps: StudioCodeListEditorProps = {
codeList: codeListWithoutTextResources,
codeList: codeListWithStrings,
texts,
onBlurAny,
onChange,
onInvalid,
};
const duplicatedValue = 'duplicate';
const codeListWithDuplicatedValues: CodeList = [
{
label: 'Test 1',
value: duplicatedValue,
description: 'Test 1 description',
helpText: 'Test 1 help text',
},
{
label: 'Test 2',
value: duplicatedValue,
description: 'Test 2 description',
helpText: 'Test 2 help text',
},
{
label: 'Test 3',
value: 'unique',
description: 'Test 3 description',
helpText: 'Test 3 help text',
},
];

const numberOfHeadingRows = 1;

describe('StudioCodeListEditor', () => {
Expand All @@ -53,7 +34,7 @@ describe('StudioCodeListEditor', () => {
it('Renders a table of code list items', () => {
renderCodeListEditor();
expect(screen.getByRole('table')).toBeInTheDocument();
const numberOfCodeListItems = codeListWithoutTextResources.length;
const numberOfCodeListItems = codeListWithStrings.length;
const expectedNumberOfRows = numberOfCodeListItems + numberOfHeadingRows;
expect(screen.getAllByRole('row')).toHaveLength(expectedNumberOfRows);
});
Expand Down Expand Up @@ -85,9 +66,9 @@ describe('StudioCodeListEditor', () => {
await user.type(labelInput, newValue);
expect(onChange).toHaveBeenCalledTimes(newValue.length);
expect(onChange).toHaveBeenLastCalledWith([
{ ...codeListWithoutTextResources[0], label: newValue },
codeListWithoutTextResources[1],
codeListWithoutTextResources[2],
{ ...codeListWithStrings[0], label: newValue },
codeListWithStrings[1],
codeListWithStrings[2],
]);
});

Expand All @@ -99,9 +80,9 @@ describe('StudioCodeListEditor', () => {
await user.type(valueInput, newValue);
expect(onChange).toHaveBeenCalledTimes(newValue.length);
expect(onChange).toHaveBeenLastCalledWith([
{ ...codeListWithoutTextResources[0], value: newValue },
codeListWithoutTextResources[1],
codeListWithoutTextResources[2],
{ ...codeListWithStrings[0], value: newValue },
codeListWithStrings[1],
codeListWithStrings[2],
]);
});

Expand All @@ -113,9 +94,9 @@ describe('StudioCodeListEditor', () => {
await user.type(descriptionInput, newValue);
expect(onChange).toHaveBeenCalledTimes(newValue.length);
expect(onChange).toHaveBeenLastCalledWith([
{ ...codeListWithoutTextResources[0], description: newValue },
codeListWithoutTextResources[1],
codeListWithoutTextResources[2],
{ ...codeListWithStrings[0], description: newValue },
codeListWithStrings[1],
codeListWithStrings[2],
]);
});

Expand All @@ -127,9 +108,9 @@ describe('StudioCodeListEditor', () => {
await user.type(helpTextInput, newValue);
expect(onChange).toHaveBeenCalledTimes(newValue.length);
expect(onChange).toHaveBeenLastCalledWith([
{ ...codeListWithoutTextResources[0], helpText: newValue },
codeListWithoutTextResources[1],
codeListWithoutTextResources[2],
{ ...codeListWithStrings[0], helpText: newValue },
codeListWithStrings[1],
codeListWithStrings[2],
]);
});

Expand All @@ -139,10 +120,7 @@ describe('StudioCodeListEditor', () => {
const deleteButton = screen.getByRole('button', { name: texts.deleteItem(1) });
await user.click(deleteButton);
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenCalledWith([
codeListWithoutTextResources[1],
codeListWithoutTextResources[2],
]);
expect(onChange).toHaveBeenCalledWith([codeListWithStrings[1], codeListWithStrings[2]]);
});

it('Calls the onChange callback with the new code list when an item is added', async () => {
Expand All @@ -151,7 +129,7 @@ describe('StudioCodeListEditor', () => {
await userEvent.click(addButton);
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenCalledWith([
...codeListWithoutTextResources,
...codeListWithStrings,
{
label: '',
value: '',
Expand All @@ -168,16 +146,16 @@ describe('StudioCodeListEditor', () => {
await user.tab();
expect(onBlurAny).toHaveBeenCalledTimes(1);
expect(onBlurAny).toHaveBeenLastCalledWith([
{ ...codeListWithoutTextResources[0], value: newValue },
codeListWithoutTextResources[1],
codeListWithoutTextResources[2],
{ ...codeListWithStrings[0], value: newValue },
codeListWithStrings[1],
codeListWithStrings[2],
]);
});

it('Updates itself when the user changes something', async () => {
const user = userEvent.setup();
renderCodeListEditor();
const numberOfCodeListItems = codeListWithoutTextResources.length;
const numberOfCodeListItems = codeListWithStrings.length;
const expectedNumberOfRows = numberOfCodeListItems + numberOfHeadingRows;
const addButton = screen.getByRole('button', { name: texts.add });
await user.click(addButton);
Expand All @@ -198,7 +176,7 @@ describe('StudioCodeListEditor', () => {
},
];
const { rerender } = renderCodeListEditor();
const numberOfCodeListItems = codeListWithoutTextResources.length;
const numberOfCodeListItems = codeListWithStrings.length;
const expectedNumberOfRows = numberOfCodeListItems + numberOfHeadingRows;
expect(screen.getAllByRole('row')).toHaveLength(expectedNumberOfRows);
rerender(<StudioCodeListEditor {...defaultProps} codeList={newCodeList} />);
Expand Down Expand Up @@ -264,6 +242,42 @@ describe('StudioCodeListEditor', () => {
await user.type(validValueInput, newValue);
expect(onInvalid).not.toHaveBeenCalled();
});

it('Should save all values as numbers, when a code list that contains numbers is modified', async () => {
const user = userEvent.setup();
renderCodeListEditor({ codeList: codeListWithNumbers });

const valueInput = screen.getByRole('textbox', { name: texts.itemValue(1) });
const newValue = '3.1416';
await user.type(valueInput, newValue);

expect(onChange).toHaveBeenCalledTimes(newValue.length);
expect(onChange).toHaveBeenLastCalledWith([
{ ...codeListWithNumbers[0], value: Number(newValue) },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Når vi tester konvertering av typer, er det en fordel å skrive verdien rett ut. Her brukes Number() både i testen og i funksjonen som testes, så hvis noe er feil i forbindelse med nettopp Number-funksjonen, vil ikke testen oppfatte feilen.

Suggested change
{ ...codeListWithNumbers[0], value: Number(newValue) },
{ ...codeListWithNumbers[0], value: 3.1416 },

codeListWithNumbers[1],
codeListWithNumbers[2],
]);
});
Comment on lines +246 to +260
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jeg prøvde å modifisere denne testen til å teste en kodeliste med kun ett element. Da feiler den fordi verdien blir konvertert til en tekststreng.

Hvis jeg endrer verdien til '3,1416' (med komma), feiler testen også. Vi bør nok ta høyde for komma siden det er slik vi skriver desimaltall på norsk.


it('Should save all values as booleans, when a code list that contains booleans is modified', async () => {
const user = userEvent.setup();
const codeListWithBooleans = [{ value: true, label: 'Yes' }];
renderCodeListEditor({ codeList: codeListWithBooleans });

const addButton = screen.getByRole('button', { name: texts.add });
await userEvent.click(addButton);
const addButtonClickCount = 1;

const valueInput = screen.getByRole('textbox', { name: texts.itemValue(2) });
const newValue = 'false';
await user.type(valueInput, newValue);

expect(onChange).toHaveBeenCalledTimes(addButtonClickCount + newValue.length);
expect(onChange).toHaveBeenLastCalledWith([
codeListWithBooleans[0],
{ label: '', value: false },
]);
});
});

function renderCodeListEditor(props: Partial<StudioCodeListEditorProps> = {}) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
addEmptyCodeListItem,
changeCodeListItem,
isCodeListEmpty,
} from './utils';
} from './utils/editorUtils';
import { StudioCodeListEditorRow } from './StudioCodeListEditorRow/StudioCodeListEditorRow';
import type { CodeListEditorTexts } from './types/CodeListEditorTexts';
import {
Expand All @@ -25,6 +25,7 @@ import { StudioFieldset } from '../StudioFieldset';
import { StudioErrorMessage } from '../StudioErrorMessage';
import type { Override } from '../../types/Override';
import type { StudioInputTableProps } from '../StudioInputTable/StudioInputTable';
import { updateCodeListValueType } from './utils/valueTypeUtils';

export type StudioCodeListEditorProps = {
codeList: CodeList;
Expand Down Expand Up @@ -69,6 +70,7 @@ function StatefulCodeListEditor({

const handleChange = useCallback(
(newCodeList: CodeList) => {
updateCodeListValueType(newCodeList);
setCodeList(newCodeList);
isCodeListValid(newCodeList) ? onChange?.(newCodeList) : onInvalid?.();
},
Expand Down
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jeg er usikker på om vi burde slette denne. I nyeste main er det også en kodeliste som heter codeListWithTextResources. Kanksje vi kan kalle de nye testlistene for stringCodeListWithoutTextResources osv.?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Godt forslag! Tar det inn.

This file was deleted.

Loading
Loading