mirror of
https://github.com/overleaf/overleaf.git
synced 2024-12-27 00:41:40 +00:00
45274d9dff
[web] Allow keyboard interactions with custom select component GitOrigin-RevId: 81adea8e456bd6ce2483dfa17a352c24c36e5768
278 lines
8.3 KiB
TypeScript
278 lines
8.3 KiB
TypeScript
import { useCallback, FormEvent } from 'react'
|
|
import { Button, Form, FormControl } from 'react-bootstrap'
|
|
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}
|
|
disabled={props.disabled}
|
|
optionalLabel={props.optionalLabel}
|
|
loading={props.loading}
|
|
/>
|
|
<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.findByText('Choose an item')
|
|
})
|
|
|
|
it('renders default item', function () {
|
|
render({ defaultItem: testData[2] })
|
|
cy.findByText('Demo item 3')
|
|
})
|
|
|
|
it('default item takes precedence over default text', function () {
|
|
render({ defaultText: 'Choose an item', defaultItem: testData[2] })
|
|
cy.findByText('Demo item 3')
|
|
})
|
|
|
|
it('renders label', function () {
|
|
render({
|
|
defaultText: 'Choose an item',
|
|
label: 'test label',
|
|
optionalLabel: false,
|
|
})
|
|
cy.findByText('test label')
|
|
cy.findByText('(Optional)').should('not.exist')
|
|
})
|
|
|
|
it('renders optional label', function () {
|
|
render({
|
|
defaultText: 'Choose an item',
|
|
label: 'test label',
|
|
optionalLabel: true,
|
|
})
|
|
cy.findByText('test label')
|
|
cy.findByText('(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.findByText('Choose an item').click()
|
|
|
|
cy.findByText('Demo item 1')
|
|
cy.findByText('Demo item 2')
|
|
cy.findByText('Demo item 3')
|
|
})
|
|
|
|
it('renders subtitles', function () {
|
|
render({
|
|
defaultText: 'Choose an item',
|
|
itemToSubtitle: x => String(x?.sub),
|
|
})
|
|
cy.findByText('Choose an item').click()
|
|
|
|
cy.findByText('Subtitle 1')
|
|
cy.findByText('Subtitle 2')
|
|
cy.findByText('Subtitle 3')
|
|
})
|
|
})
|
|
|
|
describe('item selection', function () {
|
|
it('cannot select an item when disabled', function () {
|
|
render({ defaultText: 'Choose an item', disabled: true })
|
|
cy.findByText('Choose an item').click()
|
|
|
|
cy.findByText('Demo item 1').should('not.exist')
|
|
cy.findByText('Demo item 2').should('not.exist')
|
|
cy.findByText('Demo item 3').should('not.exist')
|
|
cy.findByText('Choose an item')
|
|
})
|
|
|
|
it('renders only the selected item after selection', function () {
|
|
render({ defaultText: 'Choose an item' })
|
|
cy.findByText('Choose an item').click()
|
|
|
|
cy.findByText('Demo item 1')
|
|
cy.findByText('Demo item 2')
|
|
cy.findByText('Demo item 3').click()
|
|
|
|
cy.findByText('Choose an item').should('not.exist')
|
|
cy.findByText('Demo item 1').should('not.exist')
|
|
cy.findByText('Demo item 2').should('not.exist')
|
|
cy.findByText('Demo item 3')
|
|
})
|
|
|
|
it('invokes callback after selection', function () {
|
|
const selectionHandler = cy.stub().as('selectionHandler')
|
|
|
|
render({
|
|
defaultText: 'Choose an item',
|
|
onSelectedItemChanged: selectionHandler,
|
|
})
|
|
cy.findByText('Choose an item').click()
|
|
cy.findByText('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.findByText('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<Form>) {
|
|
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 (
|
|
<Form onSubmit={handleSubmit}>
|
|
<FormControl componentClass={selectComponent} />
|
|
<Button type="submit">submit</Button>
|
|
</Form>
|
|
)
|
|
}
|
|
|
|
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.findByText('Demo item 1').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',
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('keyboard navigation', function () {
|
|
it('can select an item using the keyboard', function () {
|
|
render({ defaultText: 'Choose an item' })
|
|
cy.findByText('Choose an item').type('{Enter}{downArrow}{Enter}')
|
|
cy.findByText('Demo item 1').should('exist')
|
|
cy.findByText('Demo item 2').should('not.exist')
|
|
})
|
|
})
|
|
})
|