| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- 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('combobox').should('have.value', 'Choose an item')
- })
- it('renders default item', function () {
- render({ defaultItem: testData[2] })
- cy.findByRole('combobox').should('have.value', 'Demo item 3')
- })
- it('default item takes precedence over default text', function () {
- render({ defaultText: 'Choose an item', defaultItem: testData[2] })
- cy.findByRole('combobox').should('have.value', 'Demo item 3')
- })
- it('renders label', function () {
- render({
- defaultText: 'Choose an item',
- label: 'test label',
- optionalLabel: false,
- })
- cy.findByRole('combobox', { name: 'test label' })
- cy.findByRole('combobox', { name: '(Optional)' }).should('not.exist')
- })
- it('renders optional label', function () {
- render({
- defaultText: 'Choose an item',
- label: 'test label',
- optionalLabel: true,
- })
- cy.findByRole('combobox', { 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('combobox').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('combobox').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('combobox').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('combobox').should('have.value', 'Choose an item')
- })
- it('renders only the selected item after selection', function () {
- render({ defaultText: 'Choose an item' })
- cy.findByRole('combobox').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('combobox').should('not.have.value', 'Choose an item')
- cy.findByRole('option', { name: 'Demo item 1' }).should('not.exist')
- cy.findByRole('option', { name: 'Demo item 2' }).should('not.exist')
- cy.findByRole('combobox').should('have.value', 'Demo item 3')
- })
- it('invokes callback after selection', function () {
- const selectionHandler = cy.stub().as('selectionHandler')
- render({
- defaultText: 'Choose an item',
- onSelectedItemChanged: selectionHandler,
- })
- cy.findByRole('combobox').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('combobox').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('combobox').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('combobox').type('{Enter}{downArrow}{Enter}', {
- force: true,
- })
- cy.findByRole('combobox').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('combobox').click()
- cy.findByRole('option', { name: 'Demo item 1' }).click()
- cy.findByRole('combobox').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('combobox').click()
- cy.findByRole('option', { name: 'Demo item 1' }).click()
- cy.findByRole('combobox').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('combobox').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('combobox').should('have.value', 'Demo item 2')
- })
- it('should show default text when selected is null', function () {
- render({
- selected: null,
- defaultText: 'Choose an item',
- })
- cy.findByRole('combobox').should('have.value', 'Choose an item')
- })
- })
- })
|