-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Create code list editor component (#13737)
Co-authored-by: Erling Hauan <148075168+ErlingHauan@users.noreply.github.com>
- Loading branch information
1 parent
c33e573
commit 1b47df6
Showing
15 changed files
with
735 additions
and
0 deletions.
There are no files selected for viewing
12 changes: 12 additions & 0 deletions
12
...ibs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
.codeListEditor { | ||
border: 0; | ||
display: flex; | ||
flex-direction: column; | ||
gap: var(--fds-spacing-3); | ||
margin: 0; | ||
padding: 0; | ||
} | ||
|
||
.codeListEditor > legend { | ||
display: none; | ||
} |
45 changes: 45 additions & 0 deletions
45
...bs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
import { StudioCodeListEditor } from './StudioCodeListEditor'; | ||
|
||
type Story = StoryObj<typeof StudioCodeListEditor>; | ||
|
||
const meta: Meta<typeof StudioCodeListEditor> = { | ||
title: 'Components/StudioCodeListEditor', | ||
component: StudioCodeListEditor, | ||
}; | ||
export default meta; | ||
|
||
export const Preview: Story = { | ||
args: { | ||
codeList: [ | ||
{ | ||
label: 'Test 1', | ||
value: 'test1', | ||
description: 'Test 1 description', | ||
}, | ||
{ | ||
label: 'Test 2', | ||
value: 'test2', | ||
description: 'Test 2 description', | ||
}, | ||
{ | ||
label: 'Test 3', | ||
value: 'test3', | ||
description: 'Test 3 description', | ||
}, | ||
], | ||
texts: { | ||
add: 'Add', | ||
codeList: 'Code list', | ||
delete: 'Delete', | ||
deleteItem: (number) => `Delete item number ${number}`, | ||
description: 'Description', | ||
emptyCodeList: 'The code list is empty.', | ||
itemDescription: (number) => `Description for item number ${number}`, | ||
itemLabel: (number) => `Label for item number ${number}`, | ||
itemValue: (number) => `Value for item number ${number}`, | ||
label: 'Label', | ||
value: 'Value', | ||
}, | ||
}, | ||
}; |
182 changes: 182 additions & 0 deletions
182
.../libs/studio-components/src/components/StudioCodelistEditor/StudioCodeListEditor.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
import React from 'react'; | ||
import { render, screen } from '@testing-library/react'; | ||
import type { StudioCodeListEditorProps } from './StudioCodeListEditor'; | ||
import { StudioCodeListEditor } from './StudioCodeListEditor'; | ||
import type { CodeListEditorTexts } from './types/CodeListEditorTexts'; | ||
import type { CodeList } from './types/CodeList'; | ||
import userEvent from '@testing-library/user-event'; | ||
|
||
// Test data: | ||
const texts: CodeListEditorTexts = { | ||
add: 'Add', | ||
codeList: 'Code list', | ||
delete: 'Delete', | ||
deleteItem: (number) => `Delete item number ${number}`, | ||
description: 'Description', | ||
emptyCodeList: 'The code list is empty.', | ||
itemDescription: (number) => `Description for item number ${number}`, | ||
itemLabel: (number) => `Label for item number ${number}`, | ||
itemValue: (number) => `Value for item number ${number}`, | ||
label: 'Label', | ||
value: 'Value', | ||
}; | ||
const codeList: CodeList = [ | ||
{ | ||
label: 'Test 1', | ||
value: 'test1', | ||
description: 'Test 1 description', | ||
}, | ||
{ | ||
label: 'Test 2', | ||
value: 'test2', | ||
description: 'Test 2 description', | ||
}, | ||
{ | ||
label: 'Test 3', | ||
value: 'test3', | ||
description: 'Test 3 description', | ||
}, | ||
]; | ||
const onChange = jest.fn(); | ||
const defaultProps: StudioCodeListEditorProps = { | ||
codeList, | ||
texts, | ||
onChange, | ||
}; | ||
const numberOfHeadingRows = 1; | ||
|
||
describe('StudioCodeListEditor', () => { | ||
afterEach(jest.clearAllMocks); | ||
|
||
it('Renders a group element with the given title', () => { | ||
renderCodeListEditor(); | ||
expect(screen.getByRole('group', { name: texts.codeList })).toBeInTheDocument(); | ||
}); | ||
|
||
it('Renders a table of code list items', () => { | ||
renderCodeListEditor(); | ||
expect(screen.getByRole('table')).toBeInTheDocument(); | ||
const numberOfCodeListItems = codeList.length; | ||
const expectedNumberOfRows = numberOfCodeListItems + numberOfHeadingRows; | ||
expect(screen.getAllByRole('row')).toHaveLength(expectedNumberOfRows); | ||
}); | ||
|
||
it('Renders the given column headers', () => { | ||
renderCodeListEditor(); | ||
expect(screen.getByRole('columnheader', { name: texts.label })).toBeInTheDocument(); | ||
expect(screen.getByRole('columnheader', { name: texts.value })).toBeInTheDocument(); | ||
expect(screen.getByRole('columnheader', { name: texts.description })).toBeInTheDocument(); | ||
}); | ||
|
||
it('Renders a button to add a new code list item', () => { | ||
renderCodeListEditor(); | ||
expect(screen.getByRole('button', { name: texts.add })).toBeInTheDocument(); | ||
}); | ||
|
||
it('Renders a message when the code list is empty', () => { | ||
renderCodeListEditor({ codeList: [] }); | ||
expect(screen.getByText(texts.emptyCodeList)).toBeInTheDocument(); | ||
}); | ||
|
||
it('Calls the onChange callback with the new code list when a label is changed', async () => { | ||
const user = userEvent.setup(); | ||
renderCodeListEditor(); | ||
const labelInput = screen.getByRole('textbox', { name: texts.itemLabel(1) }); | ||
const newValue = 'new text'; | ||
await user.type(labelInput, newValue); | ||
expect(onChange).toHaveBeenCalledTimes(newValue.length); | ||
expect(onChange).toHaveBeenLastCalledWith([ | ||
{ ...codeList[0], label: newValue }, | ||
codeList[1], | ||
codeList[2], | ||
]); | ||
}); | ||
|
||
it('Calls the onChange callback with the new code list when a value is changed', async () => { | ||
const user = userEvent.setup(); | ||
renderCodeListEditor(); | ||
const valueInput = screen.getByRole('textbox', { name: texts.itemValue(1) }); | ||
const newValue = 'new text'; | ||
await user.type(valueInput, newValue); | ||
expect(onChange).toHaveBeenCalledTimes(newValue.length); | ||
expect(onChange).toHaveBeenLastCalledWith([ | ||
{ ...codeList[0], value: newValue }, | ||
codeList[1], | ||
codeList[2], | ||
]); | ||
}); | ||
|
||
it('Calls the onChange callback with the new code list when a description is changed', async () => { | ||
const user = userEvent.setup(); | ||
renderCodeListEditor(); | ||
const descriptionInput = screen.getByRole('textbox', { name: texts.itemDescription(1) }); | ||
const newValue = 'new text'; | ||
await user.type(descriptionInput, newValue); | ||
expect(onChange).toHaveBeenCalledTimes(newValue.length); | ||
expect(onChange).toHaveBeenLastCalledWith([ | ||
{ ...codeList[0], description: newValue }, | ||
codeList[1], | ||
codeList[2], | ||
]); | ||
}); | ||
|
||
it('Calls the onChange callback with the new code list when an item is removed', async () => { | ||
const user = userEvent.setup(); | ||
renderCodeListEditor(); | ||
const deleteButton = screen.getByRole('button', { name: texts.deleteItem(1) }); | ||
await user.click(deleteButton); | ||
expect(onChange).toHaveBeenCalledTimes(1); | ||
expect(onChange).toHaveBeenCalledWith([codeList[1], codeList[2]]); | ||
}); | ||
|
||
it('Calls the onChange callback with the new code list when an item is added', async () => { | ||
renderCodeListEditor(); | ||
const addButton = screen.getByRole('button', { name: texts.add }); | ||
await userEvent.click(addButton); | ||
expect(onChange).toHaveBeenCalledTimes(1); | ||
expect(onChange).toHaveBeenCalledWith([ | ||
...codeList, | ||
{ | ||
label: '', | ||
value: '', | ||
}, | ||
]); | ||
}); | ||
|
||
it('Updates itself when the user changes something', async () => { | ||
const user = userEvent.setup(); | ||
renderCodeListEditor(); | ||
const numberOfCodeListItems = codeList.length; | ||
const expectedNumberOfRows = numberOfCodeListItems + numberOfHeadingRows; | ||
const addButton = screen.getByRole('button', { name: texts.add }); | ||
await user.click(addButton); | ||
expect(screen.getAllByRole('row')).toHaveLength(expectedNumberOfRows + 1); | ||
}); | ||
|
||
it('Rerenders with the new code list when the code list prop changes', () => { | ||
const newCodeList = [ | ||
{ | ||
label: 'New test 1', | ||
value: 'newTest1', | ||
description: 'New test 1 description', | ||
}, | ||
{ | ||
label: 'New test 2', | ||
value: 'newTest2', | ||
description: 'New test 2 description', | ||
}, | ||
]; | ||
const { rerender } = renderCodeListEditor(); | ||
const numberOfCodeListItems = codeList.length; | ||
const expectedNumberOfRows = numberOfCodeListItems + numberOfHeadingRows; | ||
expect(screen.getAllByRole('row')).toHaveLength(expectedNumberOfRows); | ||
rerender(<StudioCodeListEditor {...defaultProps} codeList={newCodeList} />); | ||
const newNumberOfCodeListItems = newCodeList.length; | ||
const newExpectedNumberOfRows = newNumberOfCodeListItems + numberOfHeadingRows; | ||
expect(screen.getAllByRole('row')).toHaveLength(newExpectedNumberOfRows); | ||
}); | ||
}); | ||
|
||
function renderCodeListEditor(props: Partial<StudioCodeListEditorProps> = {}) { | ||
return render(<StudioCodeListEditor {...defaultProps} {...props} />); | ||
} |
Oops, something went wrong.