346 lines
11 KiB
TypeScript
346 lines
11 KiB
TypeScript
import { useCallback, FormEvent } from 'react'
|
|
import OLButton from '@/features/ui/components/ol/ol-button'
|
|
import OLForm from '@/features/ui/components/ol/ol-form'
|
|
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
|
|
import {
|
|
Select,
|
|
SelectProps,
|
|
} from '../../../../frontend/js/shared/components/select'
|
|
|
|
const testData = [1, 2, 3].map(index => ({
|
|
key: index,
|
|
value: `Demo item ${index}`,
|
|
sub: `Subtitle ${index}`,
|
|
}))
|
|
|
|
type RenderProps = Partial<SelectProps<(typeof testData)[number]>> & {
|
|
onSubmit?: (formData: object) => void
|
|
}
|
|
|
|
function render(props: RenderProps) {
|
|
const submitHandler = (event: FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault()
|
|
if (props.onSubmit) {
|
|
const formData = new FormData(event.target as HTMLFormElement)
|
|
// a plain object is more convenient to work later with assertions
|
|
props.onSubmit(Object.fromEntries(formData.entries()))
|
|
}
|
|
}
|
|
cy.mount(
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
height: '100vh',
|
|
}}
|
|
>
|
|
<form onSubmit={submitHandler}>
|
|
<Select
|
|
items={testData}
|
|
itemToString={x => String(x?.value)}
|
|
label={props.label}
|
|
name="select_control"
|
|
defaultText={props.defaultText}
|
|
defaultItem={props.defaultItem}
|
|
itemToSubtitle={props.itemToSubtitle}
|
|
itemToKey={x => String(x.key)}
|
|
onSelectedItemChanged={props.onSelectedItemChanged}
|
|
selected={props.selected}
|
|
disabled={props.disabled}
|
|
itemToDisabled={props.itemToDisabled}
|
|
optionalLabel={props.optionalLabel}
|
|
loading={props.loading}
|
|
selectedIcon={props.selectedIcon}
|
|
/>
|
|
<button type="submit">submit</button>
|
|
</form>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
describe('<Select />', function () {
|
|
describe('initial rendering', function () {
|
|
it('renders default text', function () {
|
|
render({ defaultText: 'Choose an item' })
|
|
cy.findByTestId('spinner').should('not.exist')
|
|
cy.findByRole('textbox', { name: 'Choose an item' })
|
|
})
|
|
|
|
it('renders default item', function () {
|
|
render({ defaultItem: testData[2] })
|
|
cy.findByRole('textbox', { name: 'Demo item 3' })
|
|
})
|
|
|
|
it('default item takes precedence over default text', function () {
|
|
render({ defaultText: 'Choose an item', defaultItem: testData[2] })
|
|
cy.findByRole('textbox', { name: 'Demo item 3' })
|
|
})
|
|
|
|
it('renders label', function () {
|
|
render({
|
|
defaultText: 'Choose an item',
|
|
label: 'test label',
|
|
optionalLabel: false,
|
|
})
|
|
cy.findByRole('textbox', { name: 'test label' })
|
|
cy.findByRole('textbox', { name: '(Optional)' }).should('not.exist')
|
|
})
|
|
|
|
it('renders optional label', function () {
|
|
render({
|
|
defaultText: 'Choose an item',
|
|
label: 'test label',
|
|
optionalLabel: true,
|
|
})
|
|
cy.findByRole('textbox', { name: 'test label (Optional)' })
|
|
})
|
|
|
|
it('renders a spinner while loading when there is a label', function () {
|
|
render({
|
|
defaultText: 'Choose an item',
|
|
label: 'test label',
|
|
loading: true,
|
|
})
|
|
cy.findByTestId('spinner')
|
|
})
|
|
|
|
it('does not render a spinner while loading if there is no label', function () {
|
|
render({
|
|
defaultText: 'Choose an item',
|
|
loading: true,
|
|
})
|
|
cy.findByTestId('spinner').should('not.exist')
|
|
})
|
|
})
|
|
|
|
describe('items rendering', function () {
|
|
it('renders all items', function () {
|
|
render({ defaultText: 'Choose an item' })
|
|
cy.findByRole('textbox', { name: 'Choose an item' }).click()
|
|
|
|
cy.findByRole('option', { name: 'Demo item 1' })
|
|
cy.findByRole('option', { name: 'Demo item 2' })
|
|
cy.findByRole('option', { name: 'Demo item 3' })
|
|
})
|
|
|
|
it('renders subtitles', function () {
|
|
render({
|
|
defaultText: 'Choose an item',
|
|
itemToSubtitle: x => String(x?.sub),
|
|
})
|
|
cy.findByRole('textbox', { name: 'Choose an item' }).click()
|
|
|
|
cy.findByRole('option', { name: 'Demo item 1 Subtitle 1' })
|
|
cy.findByRole('option', { name: 'Demo item 2 Subtitle 2' })
|
|
cy.findByRole('option', { name: 'Demo item 3 Subtitle 3' })
|
|
})
|
|
})
|
|
|
|
describe('item selection', function () {
|
|
it('cannot select an item when disabled', function () {
|
|
render({ defaultText: 'Choose an item', disabled: true })
|
|
cy.findByRole('textbox', { name: 'Choose an item' }).click({
|
|
force: true,
|
|
})
|
|
cy.findByRole('option', { name: 'Demo item 1' }).should('not.exist')
|
|
cy.findByRole('option', { name: 'Demo item 2' }).should('not.exist')
|
|
cy.findByRole('option', { name: 'Demo item 3' }).should('not.exist')
|
|
cy.findByRole('textbox', { name: 'Choose an item' })
|
|
})
|
|
|
|
it('renders only the selected item after selection', function () {
|
|
render({ defaultText: 'Choose an item' })
|
|
cy.findByRole('textbox', { name: 'Choose an item' }).click()
|
|
|
|
cy.findByRole('option', { name: 'Demo item 1' })
|
|
cy.findByRole('option', { name: 'Demo item 2' })
|
|
cy.findByRole('option', { name: 'Demo item 3' }).click()
|
|
|
|
cy.findByRole('textbox', { name: 'Choose an item' }).should('not.exist')
|
|
cy.findByRole('option', { name: 'Demo item 1' }).should('not.exist')
|
|
cy.findByRole('option', { name: 'Demo item 2' }).should('not.exist')
|
|
cy.findByRole('textbox', { name: 'Demo item 3' })
|
|
})
|
|
|
|
it('invokes callback after selection', function () {
|
|
const selectionHandler = cy.stub().as('selectionHandler')
|
|
|
|
render({
|
|
defaultText: 'Choose an item',
|
|
onSelectedItemChanged: selectionHandler,
|
|
})
|
|
cy.findByRole('textbox', { name: 'Choose an item' }).click()
|
|
cy.findByRole('option', { name: 'Demo item 2' }).click()
|
|
|
|
cy.get('@selectionHandler').should(
|
|
'have.been.calledOnceWith',
|
|
testData[1]
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('when the form is submitted', function () {
|
|
it('populates FormData with the default selected item', function () {
|
|
const submitHandler = cy.stub().as('submitHandler')
|
|
render({ defaultItem: testData[1], onSubmit: submitHandler })
|
|
|
|
cy.findByText('submit').click()
|
|
cy.get('@submitHandler').should('have.been.calledOnceWith', {
|
|
select_control: 'Demo item 2',
|
|
})
|
|
})
|
|
|
|
it('populates FormData with the selected item', function () {
|
|
const submitHandler = cy.stub().as('submitHandler')
|
|
render({ defaultItem: testData[1], onSubmit: submitHandler })
|
|
|
|
cy.findByRole('textbox', { name: 'Demo item 2' }).click() // open dropdown
|
|
cy.findByText('Demo item 3').click() // choose a different item
|
|
|
|
cy.findByText('submit').click()
|
|
cy.get('@submitHandler').should('have.been.calledOnceWith', {
|
|
select_control: 'Demo item 3',
|
|
})
|
|
})
|
|
|
|
it('does not populate FormData when no item is selected', function () {
|
|
const submitHandler = cy.stub().as('submitHandler')
|
|
render({ defaultText: 'Choose an item', onSubmit: submitHandler })
|
|
|
|
cy.findByText('submit').click()
|
|
cy.get('@submitHandler').should('have.been.calledOnceWith', {})
|
|
})
|
|
})
|
|
|
|
describe('with react-bootstrap forms', function () {
|
|
type FormWithSelectProps = {
|
|
onSubmit: (formData: object) => void
|
|
}
|
|
|
|
const FormWithSelect = ({ onSubmit }: FormWithSelectProps) => {
|
|
const selectComponent = useCallback(
|
|
() => (
|
|
<Select
|
|
name="select_control"
|
|
items={testData}
|
|
defaultItem={testData[0]}
|
|
itemToString={x => String(x?.value)}
|
|
itemToKey={x => String(x.key)}
|
|
/>
|
|
),
|
|
[]
|
|
)
|
|
|
|
function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
|
event.preventDefault()
|
|
const formData = new FormData(event.target as HTMLFormElement)
|
|
// a plain object is more convenient to work later with assertions
|
|
onSubmit(Object.fromEntries(formData.entries()))
|
|
}
|
|
|
|
return (
|
|
<OLForm onSubmit={handleSubmit}>
|
|
<OLFormControl as={selectComponent} />
|
|
<OLButton type="submit">submit</OLButton>
|
|
</OLForm>
|
|
)
|
|
}
|
|
|
|
it('populates FormData with the selected item when the form is submitted', function () {
|
|
const submitHandler = cy.stub().as('submitHandler')
|
|
cy.mount(
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
height: '100vh',
|
|
}}
|
|
>
|
|
<FormWithSelect onSubmit={submitHandler} />
|
|
</div>
|
|
)
|
|
|
|
cy.findByRole('textbox', { name: 'Demo item 1' }).click() // open dropdown
|
|
cy.findByRole('option', { name: 'Demo item 3' }).click() // choose a different item
|
|
|
|
cy.findByText('submit').click()
|
|
cy.get('@submitHandler').should('have.been.calledOnceWith', {
|
|
select_control: 'Demo item 3',
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('keyboard navigation', function () {
|
|
it('can select an item using the keyboard', function () {
|
|
render({ defaultText: 'Choose an item' })
|
|
cy.findByRole('textbox', { name: 'Choose an item' }).type(
|
|
'{Enter}{downArrow}{Enter}',
|
|
{ force: true }
|
|
)
|
|
cy.findByRole('textbox', { name: 'Demo item 1' }).should('exist')
|
|
cy.findByRole('option', { name: 'Demo item 2' }).should('not.exist')
|
|
})
|
|
})
|
|
|
|
describe('selectedIcon', function () {
|
|
it('renders a selected icon if the prop is set', function () {
|
|
render({
|
|
defaultText: 'Choose an item',
|
|
selectedIcon: true,
|
|
})
|
|
cy.findByRole('textbox', { name: 'Choose an item' }).click()
|
|
cy.findByRole('option', { name: 'Demo item 1' }).click()
|
|
cy.findByRole('textbox', { name: 'Demo item 1' }).click()
|
|
|
|
cy.findByText('check').should('exist')
|
|
})
|
|
it('renders no selected icon if the prop is not set', function () {
|
|
render({
|
|
defaultText: 'Choose an item',
|
|
selectedIcon: false,
|
|
})
|
|
cy.findByRole('textbox', { name: 'Choose an item' }).click()
|
|
cy.findByRole('option', { name: 'Demo item 1' }).click()
|
|
cy.findByRole('textbox', { name: 'Demo item 1' }).click()
|
|
|
|
cy.findByText('check').should('not.exist')
|
|
})
|
|
})
|
|
|
|
describe('itemToDisabled', function () {
|
|
it('prevents selecting a disabled item', function () {
|
|
render({
|
|
defaultText: 'Choose an item',
|
|
itemToDisabled: x => x?.key === 2,
|
|
})
|
|
cy.findByRole('textbox', { name: 'Choose an item' }).click()
|
|
cy.findByRole('option', { name: 'Demo item 2' }).click({ force: true })
|
|
// still showing other list items
|
|
cy.findByRole('option', { name: 'Demo item 3' }).should('exist')
|
|
cy.findByRole('option', { name: 'Demo item 1' }).click()
|
|
// clicking an enabled item dismisses the list
|
|
cy.findByRole('option', { name: 'Demo item 3' }).should('not.exist')
|
|
})
|
|
})
|
|
|
|
describe('selected', function () {
|
|
it('shows the item provided in the selected prop', function () {
|
|
render({
|
|
defaultText: 'Choose an item',
|
|
selected: testData[1],
|
|
})
|
|
cy.findByRole('textbox', { name: 'Demo item 2' }).should('exist')
|
|
})
|
|
|
|
it('should show default text when selected is null', function () {
|
|
render({
|
|
selected: null,
|
|
defaultText: 'Choose an item',
|
|
})
|
|
cy.findByRole('textbox', { name: 'Choose an item' }).should('exist')
|
|
})
|
|
})
|
|
})
|