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: Add type selector for new codelists #14658

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
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 @@ -24,6 +24,7 @@
import { codeListWithBooleanValues } from './test-data/codeListWithBooleanValues';
import { codeListWithMultipleTypes } from './test-data/codeListWithMultipleTypes';
import { codeListWithUndefinedValues } from './test-data/codeListWithUndefinedValues';
import { emptyStringItem } from './utils';

Check failure on line 27 in frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx

View workflow job for this annotation

GitHub Actions / Typechecking and linting

'emptyStringItem' is declared but its value is never read.

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import emptyStringItem.

Copilot Autofix AI 1 day ago

To fix the problem, we need to remove the unused import statement. This will clean up the code and eliminate any confusion or potential maintenance issues related to the unused import. The change should be made in the file frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx on line 27.

Suggested changeset 1
frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx
--- a/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx
+++ b/frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx
@@ -26,3 +26,2 @@
 import { codeListWithUndefinedValues } from './test-data/codeListWithUndefinedValues';
-import { emptyStringItem } from './utils';
 
EOF
@@ -26,3 +26,2 @@
import { codeListWithUndefinedValues } from './test-data/codeListWithUndefinedValues';
import { emptyStringItem } from './utils';

Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options

// Test data:
const onAddOrDeleteItem = jest.fn();
Expand Down Expand Up @@ -96,7 +97,7 @@

it('Renders a message when the code list is empty', () => {
renderCodeListEditor({ codeList: [] });
expect(screen.getByText(texts.emptyCodeList)).toBeInTheDocument();

Check failure on line 100 in frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx

View workflow job for this annotation

GitHub Actions / Testing

StudioCodeListEditor › Renders a message when the code list is empty

TestingLibraryElementError: Unable to find an element with the text: The code list is empty.. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. Ignored nodes: comments, script, style <body> <div> <fieldset class="fds-fieldset fds-fieldset--spacing codeListEditor" > <legend class="fds-label fds-label--sm fds-label--medium-weight fds-fieldset__legend" > <span class="fds-fieldset__legend__content" > Code list </span> </legend> <p class="fds-label fds-label--sm fds-label--medium-weight" for=":r3l:" > Velg hvilken type verdiene i kodelisten skal ha: </p> <div class="fds-paragraph fds-paragraph--sm fds-native-select--container" > <select class="fds-native-select fds-native-select--sm fds-focus" id=":r3m:" name=":r3l:" > <option value="string" > Tekst (anbefalt) </option> <option value="number" > Tall </option> <option value="boolean" > Boolsk </option> </select> </div> <button class="fds-btn fds-focus fds-btn--sm fds-btn--secondary fds-btn--first studioButton" type="button" > <span class="innerContainer" > <span aria-hidden="true" class="iconWrapper" > <svg fill="none" focusable="false" height="1em" role="img" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg" > <path d="M12.75 5.5a.75.75 0 0 0-1.5 0v5.75H5.5a.75.75 0 0 0 0 1.5h5.75v5.75a.75.75 0 0 0 1.5 0v-5.75h5.75a.75.75 0 0 0 0-1.5h-5.75z" fill="currentColor" /> </svg> </span> Add </span> </button> <div aria-live="polite" aria-relevant="additions removals" class="fds-fieldset__error-message" id="fieldset-error-:r3k:" /> </fieldset> </div> </body> at Object.getElementError (../node_modules/@testing-library/dom/dist/config.js:37:19) at ../node_modules/@testing-library/dom/dist/query-helpers.js:76:38 at ../node_modules/@testing-library/dom/dist/query-helpers.js:52:17 at ../node_modules/@testing-library/dom/dist/query-helpers.js:95:19 at Object.getByText (libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx:100:19)
});

it('Calls the onChange callback with the new code list when a value is changed', async () => {
Expand Down Expand Up @@ -506,6 +507,22 @@
});

describe('Type handling', () => {
it('Renders type selector when code list is empty', () => {
expect(1).toBeNull();

Check failure on line 511 in frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx

View workflow job for this annotation

GitHub Actions / Testing

StudioCodeListEditor › Type handling › Renders type selector when code list is empty

expect(received).toBeNull() Received: 1 at Object.toBeNull (libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx:511:17)
});

it("Creates an empty string item when 'text' is selected and 'Add new' is pressed", () => {
expect(1).toBeNull();

Check failure on line 515 in frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx

View workflow job for this annotation

GitHub Actions / Testing

StudioCodeListEditor › Type handling › Creates an empty string item when 'text' is selected and 'Add new' is pressed

expect(received).toBeNull() Received: 1 at Object.toBeNull (libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx:515:17)
});

it("Creates an empty number item when 'number' is selected and 'Add new' is pressed", () => {
expect(1).toBeNull();

Check failure on line 519 in frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx

View workflow job for this annotation

GitHub Actions / Testing

StudioCodeListEditor › Type handling › Creates an empty number item when 'number' is selected and 'Add new' is pressed

expect(received).toBeNull() Received: 1 at Object.toBeNull (libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx:519:17)
});

it("Creates an empty boolean item when 'boolean' is selected and 'Add new' is pressed", () => {
expect(1).toBeNull();

Check failure on line 523 in frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx

View workflow job for this annotation

GitHub Actions / Testing

StudioCodeListEditor › Type handling › Creates an empty boolean item when 'boolean' is selected and 'Add new' is pressed

expect(received).toBeNull() Received: 1 at Object.toBeNull (libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx:523:17)
});

it('Renders textfield when item value is a string', () => {
renderCodeListEditor();
const textfield = screen.getByRole('textbox', { name: texts.itemValue(1) });
Expand Down Expand Up @@ -584,6 +601,10 @@

expect(onBlurAny).toHaveBeenCalledWith([...codeListWithNumberValues]);
});

it('Added new items should be same type as previous item', () => {
expect(1).toBeNull();

Check failure on line 606 in frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx

View workflow job for this annotation

GitHub Actions / Testing

StudioCodeListEditor › Type handling › Added new items should be same type as previous item

expect(received).toBeNull() Received: 1 at Object.toBeNull (libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx:606:17)
});
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { CodeList } from './types/CodeList';
import type { ReactElement } from 'react';
import React, { useMemo, useRef, useCallback } from 'react';
import React, { useState, useId, useMemo, useRef, useCallback } from 'react';
import { StudioInputTable } from '../StudioInputTable';
import type { CodeListItem } from './types/CodeListItem';
import { StudioButton } from '../StudioButton';
Expand All @@ -9,6 +9,7 @@
addNewCodeListItem,
changeCodeListItem,
isCodeListEmpty,
getTypeOfLastValue,
} from './utils';
import { StudioCodeListEditorRow } from './StudioCodeListEditorRow/StudioCodeListEditorRow';
import type { CodeListEditorTexts } from './types/CodeListEditorTexts';
Expand All @@ -18,7 +19,6 @@
} from './StudioCodeListEditorContext';
import classes from './StudioCodeListEditor.module.css';
import { PlusIcon } from '@studio/icons';
import { StudioParagraph } from '../StudioParagraph';
import { areThereCodeListErrors, findCodeListErrors, isCodeListValid } from './validation';
import type { ValueErrorMap } from './types/ValueErrorMap';
import { StudioFieldset } from '../StudioFieldset';
Expand All @@ -27,6 +27,9 @@
import { usePropState } from '@studio/hooks';
import type { Override } from '../../types/Override';
import type { StudioInputTableProps } from '../StudioInputTable/StudioInputTable';
import { StudioLabelAsParagraph } from '../StudioLabelAsParagraph';
import { StudioNativeSelect } from '../StudioNativeSelect';
import type { CodeListItemValueLiteral } from './types/CodeListItemValue';

export type StudioCodeListEditorProps = {
codeList: CodeList;
Expand Down Expand Up @@ -104,28 +107,35 @@
onChangeTextResource,
textResources,
}: ControlledCodeListEditorProps): ReactElement {
const isFirstItem = !codeList || isCodeListEmpty(codeList);
const initialValueType = isFirstItem ? 'string' : getTypeOfLastValue(codeList);
const [valueType, setValueType] = useState<CodeListItemValueLiteral>(initialValueType);

Check failure on line 112 in frontend/libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.tsx

View workflow job for this annotation

GitHub Actions / Typechecking and linting

Argument of type 'TypeofResult' is not assignable to parameter of type 'CodeListItemValueLiteral | (() => CodeListItemValueLiteral)'.

const { texts } = useStudioCodeListEditorContext();
const fieldsetRef = useRef<HTMLFieldSetElement>(null);

const errorMap = useMemo<ValueErrorMap>(() => findCodeListErrors(codeList), [codeList]);

const handleAddButtonClick = useCallback(() => {
const updatedCodeList = addNewCodeListItem(codeList);
const updatedCodeList = addNewCodeListItem(codeList, valueType);
onChange(updatedCodeList);
onAddOrDeleteItem?.(updatedCodeList);
}, [codeList, onChange, onAddOrDeleteItem]);
}, [codeList, valueType, onChange, onAddOrDeleteItem]);

return (
<StudioFieldset legend={texts.codeList} className={classes.codeListEditor} ref={fieldsetRef}>
<CodeListTable
codeList={codeList}
errorMap={errorMap}
onAddOrDeleteItem={onAddOrDeleteItem}
onBlurAny={onBlurAny}
onChange={onChange}
onChangeTextResource={onChangeTextResource}
textResources={textResources}
/>
{isFirstItem ? (
<TypeSelector setValueType={setValueType} />
) : (
<CodeListTable
codeList={codeList}
errorMap={errorMap}
onAddOrDeleteItem={onAddOrDeleteItem}
onBlurAny={onBlurAny}
onChange={onChange}
onChangeTextResource={onChangeTextResource}
textResources={textResources}
/>
)}
<AddButton onClick={handleAddButtonClick} />
<Errors errorMap={errorMap} />
</StudioFieldset>
Expand All @@ -134,20 +144,7 @@

type CodeListTableProps = ControlledCodeListEditorProps & ErrorsProps;

function CodeListTable(props: CodeListTableProps): ReactElement {
return isCodeListEmpty(props.codeList) ? (
<EmptyCodeListTable />
) : (
<CodeListTableWithContent {...props} />
);
}

function EmptyCodeListTable(): ReactElement {
const { texts } = useStudioCodeListEditorContext();
return <StudioParagraph size='small'>{texts.emptyCodeList}</StudioParagraph>;
}

function CodeListTableWithContent({ onBlurAny, ...rest }: CodeListTableProps): ReactElement {
function CodeListTable({ onBlurAny, ...rest }: CodeListTableProps): ReactElement {
return (
<StudioInputTable onBlurAny={onBlurAny}>
<TableHeadings />
Expand Down Expand Up @@ -230,8 +227,33 @@
}
}

type TypeSelectorProps = {
setValueType: (valueType: CodeListItemValueLiteral) => void;
};

function TypeSelector({ setValueType }: TypeSelectorProps): ReactElement {
// const { texts } = useStudioCodeListEditorContext();
const id = useId();

return (
<>
<StudioLabelAsParagraph size='sm' htmlFor={id}>
Velg hvilken type verdiene i kodelisten skal ha:
</StudioLabelAsParagraph>
<StudioNativeSelect
name={id}
onChange={(event) => setValueType(event.target.value as 'string' | 'number' | 'boolean')}
>
<option value='string'>Tekst (anbefalt)</option>
<option value='number'>Tall</option>
<option value='boolean'>Boolsk</option>
</StudioNativeSelect>
</>
);
}

type AddButtonProps = {
onClick: () => void;
onClick: (valueType?: CodeListItemValueLiteral) => void;
};

function AddButton({ onClick }: AddButtonProps): ReactElement {
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export type CodeListItemValue = string | boolean | number;

export type CodeListItemValueLiteral = "string" | "boolean" | "number";
Original file line number Diff line number Diff line change
Expand Up @@ -27,43 +27,28 @@
const createTestCodeList = (): CodeList => ObjectUtils.deepCopy(testCodeList);

describe('StudioCodelistEditor utils', () => {
describe('addEmptyCodeListItem', () => {
it('Adds an empty string item when the code list is empty', () => {
const codeList: CodeList = [];
const updatedCodeList = addNewCodeListItem(codeList);
describe('addNewCodeListItem', () => {
it('Adds an empty string when valueType is string', () => {
const codeList: CodeList = [{ value: 'test-value', label: 'stringItem' }];
const updatedCodeList = addNewCodeListItem(codeList, 'string');
expect(updatedCodeList).toEqual([...codeList, emptyStringItem]);
});

it("Adds an empty string item when the last item's value is a string", () => {
const codeList: CodeList = [
{ value: 1, label: 'numberItem' },
{ value: 'two', label: 'stringItem' },
];
const updatedCodeList = addNewCodeListItem(codeList);
expect(updatedCodeList).toEqual([...codeList, emptyStringItem]);
});

it("Adds an empty number item when the last item's value is a number", () => {
const codeList: CodeList = [
{ value: 'one', label: 'stringItem' },
{ value: 2, label: 'numberItem' },
];
const updatedCodeList = addNewCodeListItem(codeList);
it('Adds an empty number item when valueType is number', () => {
const codeList: CodeList = [{ value: 1, label: 'numberItem' }];
const updatedCodeList = addNewCodeListItem(codeList, 'number');
expect(updatedCodeList).toEqual([...codeList, emptyNumberItem]);
});

it("Adds an empty boolean item when the last item's value is a boolean", () => {
const codeList: CodeList = [
{ value: 0, label: 'numberItem' },
{ value: true, label: 'booleanItem' },
];
const updatedCodeList = addNewCodeListItem(codeList);
it('Adds an empty boolean item when valueType is boolean', () => {
const codeList: CodeList = [{ value: true, label: 'booleanItem' }];
const updatedCodeList = addNewCodeListItem(codeList, 'boolean');
expect(updatedCodeList).toEqual([...codeList, emptyBooleanItem]);
});

it('Returns a new instance', () => {
const codeList = createTestCodeList();
const updatedCodeList = addNewCodeListItem(codeList);

Check failure on line 51 in frontend/libs/studio-components/src/components/StudioCodelistEditor/utils.test.ts

View workflow job for this annotation

GitHub Actions / Typechecking and linting

Expected 2 arguments, but got 1.
expect(updatedCodeList).not.toBe(codeList);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { CodeListItem } from './types/CodeListItem';
import type { CodeList } from './types/CodeList';
import type { TypeofResult } from './types/TypeofResult';
import { ArrayUtils } from '@studio/pure-functions';
import type { CodeListItemValueLiteral } from './types/CodeListItemValue';

export const emptyStringItem: CodeListItem = {
value: '',
Expand All @@ -18,15 +19,16 @@ export const emptyBooleanItem: CodeListItem = {
label: '',
};

export function addNewCodeListItem(codeList: CodeList): CodeList {
const newEmptyItem: CodeListItem = createNewEmptyItem(codeList);
export function addNewCodeListItem(
codeList: CodeList,
valueType: CodeListItemValueLiteral,
): CodeList {
const newEmptyItem = createNewEmptyItem(codeList, valueType);
return addCodeListItem(codeList, newEmptyItem);
}

function createNewEmptyItem(codeList: CodeList): CodeListItem {
if (isCodeListEmpty(codeList)) return emptyStringItem;

switch (getTypeOfLastValue(codeList)) {
function createNewEmptyItem(codeList: CodeList, valueType: CodeListItemValueLiteral): CodeListItem {
switch (valueType) {
case 'number':
return emptyNumberItem;
case 'boolean':
Expand Down
Loading