-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
273 additions
and
0 deletions.
There are no files selected for viewing
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,273 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { render, screen } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { useState } from '@wordpress/element'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import RadioControl from '../'; | ||
|
||
const ControlledRadioControl = ( { | ||
...props | ||
}: React.ComponentProps< typeof RadioControl > ) => { | ||
const [ option, setOption ] = useState( props.selected ); | ||
|
||
return ( | ||
<RadioControl | ||
{ ...props } | ||
onChange={ ( newValue ) => { | ||
setOption( newValue ); | ||
props.onChange?.( newValue ); | ||
} } | ||
selected={ option } | ||
/> | ||
); | ||
}; | ||
|
||
const defaultProps = { | ||
options: [ | ||
{ label: 'Mouse', value: 'mouse' }, | ||
{ label: 'Cat', value: 'cat' }, | ||
{ label: 'Dog', value: 'dog' }, | ||
], | ||
label: 'Animal', | ||
}; | ||
|
||
const defaultPropsWithDescriptions = { | ||
...defaultProps, | ||
options: defaultProps.options.map( ( option, index ) => ( { | ||
...option, | ||
description: `This is the option number ${ index + 1 }.`, | ||
} ) ), | ||
}; | ||
|
||
describe.each( [ | ||
// [ 'uncontrolled', RadioControl ], | ||
[ 'controlled', ControlledRadioControl ], | ||
] )( 'RadioControl %s', ( ...modeAndComponent ) => { | ||
const [ , Component ] = modeAndComponent; | ||
|
||
describe( 'semantics and labelling', () => { | ||
it( 'should render radio inputs with accessible labels', () => { | ||
const onChangeSpy = jest.fn(); | ||
render( | ||
<Component { ...defaultProps } onChange={ onChangeSpy } /> | ||
); | ||
|
||
for ( const option of defaultProps.options ) { | ||
const optionEl = screen.getByRole( 'radio', { | ||
name: option.label, | ||
} ); | ||
expect( optionEl ).toBeVisible(); | ||
expect( optionEl ).not.toBeChecked(); | ||
} | ||
} ); | ||
|
||
it( 'should not select have a selected value when the `selected` prop does not match any available options', () => { | ||
const onChangeSpy = jest.fn(); | ||
render( | ||
<Component { ...defaultProps } onChange={ onChangeSpy } /> | ||
); | ||
|
||
expect( | ||
screen.queryByRole( 'radio', { | ||
checked: true, | ||
} ) | ||
).not.toBeInTheDocument(); | ||
} ); | ||
|
||
it( 'should render mutually exclusive radio inputs', () => { | ||
const onChangeSpy = jest.fn(); | ||
render( | ||
<Component | ||
{ ...defaultProps } | ||
onChange={ onChangeSpy } | ||
selected={ defaultProps.options[ 1 ].value } | ||
/> | ||
); | ||
|
||
expect( | ||
screen.getByRole( 'radio', { | ||
checked: true, | ||
} ) | ||
).toHaveAccessibleName( defaultProps.options[ 1 ].label ); | ||
} ); | ||
|
||
it( 'should use the group help text to describe individual options', () => { | ||
const onChangeSpy = jest.fn(); | ||
render( | ||
<Component | ||
{ ...defaultProps } | ||
onChange={ onChangeSpy } | ||
selected={ defaultProps.options[ 1 ].value } | ||
help="Select your favorite animal." | ||
/> | ||
); | ||
|
||
for ( const option of defaultProps.options ) { | ||
expect( | ||
screen.getByRole( 'radio', { name: option.label } ) | ||
).toHaveAccessibleDescription( 'Select your favorite animal.' ); | ||
} | ||
} ); | ||
|
||
it( 'should use the option description text to describe individual options', () => { | ||
const onChangeSpy = jest.fn(); | ||
render( | ||
<Component | ||
{ ...defaultPropsWithDescriptions } | ||
onChange={ onChangeSpy } | ||
selected={ defaultProps.options[ 1 ].value } | ||
/> | ||
); | ||
|
||
let index = 1; | ||
for ( const option of defaultProps.options ) { | ||
expect( | ||
screen.getByRole( 'radio', { name: option.label } ) | ||
).toHaveAccessibleDescription( | ||
`This is the option number ${ index }.` | ||
); | ||
index += 1; | ||
} | ||
} ); | ||
|
||
it( 'should use both the option description text and the group help text to describe individual options', () => { | ||
const onChangeSpy = jest.fn(); | ||
render( | ||
<Component | ||
{ ...defaultPropsWithDescriptions } | ||
onChange={ onChangeSpy } | ||
selected={ defaultProps.options[ 1 ].value } | ||
help="Select your favorite animal" | ||
/> | ||
); | ||
|
||
let index = 1; | ||
for ( const option of defaultProps.options ) { | ||
expect( | ||
screen.getByRole( 'radio', { name: option.label } ) | ||
).toHaveAccessibleDescription( | ||
`This is the option number ${ index }. Select your favorite animal` | ||
); | ||
index += 1; | ||
} | ||
} ); | ||
} ); | ||
|
||
describe( 'interaction', () => { | ||
it( 'should select a new value when clicking on the radio input', async () => { | ||
const user = userEvent.setup(); | ||
const onChangeSpy = jest.fn(); | ||
render( | ||
<Component { ...defaultProps } onChange={ onChangeSpy } /> | ||
); | ||
|
||
// Click on the third radio, make sure it's selected. | ||
await user.click( | ||
screen.getByRole( 'radio', { | ||
name: defaultProps.options[ 2 ].label, | ||
} ) | ||
); | ||
expect( | ||
screen.getByRole( 'radio', { | ||
checked: true, | ||
} ) | ||
).toHaveAccessibleName( defaultProps.options[ 2 ].label ); | ||
|
||
expect( onChangeSpy ).toHaveBeenCalledTimes( 1 ); | ||
expect( onChangeSpy ).toHaveBeenLastCalledWith( | ||
defaultProps.options[ 2 ].value | ||
); | ||
} ); | ||
|
||
it( 'should select a new value when clicking on the radio label', async () => { | ||
const user = userEvent.setup(); | ||
const onChangeSpy = jest.fn(); | ||
render( | ||
<Component { ...defaultProps } onChange={ onChangeSpy } /> | ||
); | ||
|
||
// Click on the second radio's label, make sure it selects the associated radio. | ||
await user.click( | ||
screen.getByText( defaultProps.options[ 1 ].label ) | ||
); | ||
expect( | ||
screen.getByRole( 'radio', { | ||
checked: true, | ||
} ) | ||
).toHaveAccessibleName( defaultProps.options[ 1 ].label ); | ||
|
||
expect( onChangeSpy ).toHaveBeenCalledTimes( 1 ); | ||
expect( onChangeSpy ).toHaveBeenLastCalledWith( | ||
defaultProps.options[ 1 ].value | ||
); | ||
} ); | ||
|
||
it( 'should select a new value when using the arrow keys', async () => { | ||
const user = userEvent.setup(); | ||
const onChangeSpy = jest.fn(); | ||
render( | ||
<Component { ...defaultProps } onChange={ onChangeSpy } /> | ||
); | ||
|
||
await user.tab(); | ||
|
||
expect( | ||
screen.getByRole( 'radio', { | ||
name: defaultProps.options[ 0 ].label, | ||
} ) | ||
).toHaveFocus(); | ||
|
||
await user.keyboard( '{ArrowDown}' ); | ||
|
||
expect( | ||
screen.getByRole( 'radio', { | ||
checked: true, | ||
name: defaultProps.options[ 1 ].label, | ||
} ) | ||
).toHaveFocus(); | ||
expect( onChangeSpy ).toHaveBeenCalledTimes( 2 ); | ||
expect( onChangeSpy ).toHaveBeenLastCalledWith( | ||
defaultProps.options[ 1 ].value | ||
); | ||
|
||
await user.keyboard( '{ArrowDown}' ); | ||
await user.keyboard( '{ArrowDown}' ); | ||
|
||
// The selection wrap around. | ||
expect( | ||
screen.getByRole( 'radio', { | ||
checked: true, | ||
name: defaultProps.options[ 0 ].label, | ||
} ) | ||
).toHaveFocus(); | ||
// TODO: why called twice for every interaction? | ||
expect( onChangeSpy ).toHaveBeenCalledTimes( 6 ); | ||
expect( onChangeSpy ).toHaveBeenLastCalledWith( | ||
defaultProps.options[ 0 ].value | ||
); | ||
|
||
await user.keyboard( '{ArrowUp}' ); | ||
|
||
expect( | ||
screen.getByRole( 'radio', { | ||
checked: true, | ||
name: defaultProps.options[ 2 ].label, | ||
} ) | ||
).toHaveFocus(); | ||
|
||
expect( onChangeSpy ).toHaveBeenCalledTimes( 8 ); | ||
expect( onChangeSpy ).toHaveBeenLastCalledWith( | ||
defaultProps.options[ 2 ].value | ||
); | ||
} ); | ||
} ); | ||
} ); |