|
|
@@ -0,0 +1,269 @@
|
|
|
+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',
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+})
|