Skip to content

Commit

Permalink
Add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ciampo committed Jul 24, 2024
1 parent 5ecc65e commit bb3aa5c
Showing 1 changed file with 273 additions and 0 deletions.
273 changes: 273 additions & 0 deletions packages/components/src/radio-control/test/index.tsx
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
);
} );
} );
} );

0 comments on commit bb3aa5c

Please sign in to comment.