Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CustomSelect: add tests for new features #58583

Merged
merged 5 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
- `Tabs`: improve controlled mode focus handling and keyboard navigation ([#57696](https://github.com/WordPress/gutenberg/pull/57696)).
- `Tabs`: prevent internal focus from updating too early ([#58625](https://github.com/WordPress/gutenberg/pull/58625)).
- Expand theming support in the `COLORS` variable object ([#58097](https://github.com/WordPress/gutenberg/pull/58097)).
- `CustomSelect`: disable `virtualFocus` to fix issue for screenreaders ([#58585](https://github.com/WordPress/gutenberg/pull/58585))
- `CustomSelect`: disable `virtualFocus` to fix issue for screenreaders ([#58585](https://github.com/WordPress/gutenberg/pull/58585)).

### Enhancements

Expand All @@ -35,6 +35,7 @@

- `Composite`: Removing Reakit `Composite` implementation ([#58620](https://github.com/WordPress/gutenberg/pull/58620)).
- Removing Reakit as a dependency of the components package ([#58631](https://github.com/WordPress/gutenberg/pull/58631)).
- `CustomSelect`: add unit tests ([#58583](https://github.com/WordPress/gutenberg/pull/58583)).

## 25.16.0 (2024-01-24)

Expand Down
219 changes: 219 additions & 0 deletions packages/components/src/custom-select-control-v2/test/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/**
* 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 { CustomSelect, CustomSelectItem } from '..';
import type { CustomSelectProps } from '../types';

const ControlledCustomSelect = ( props: CustomSelectProps ) => {
const [ value, setValue ] = useState< string | string[] >();
return (
<CustomSelect
{ ...props }
onChange={ ( nextValue ) => {
setValue( nextValue );
props.onChange?.( nextValue );
} }
value={ value }
/>
);
};

describe.each( [
[ 'uncontrolled', CustomSelect ],
[ 'controlled', ControlledCustomSelect ],
] )( 'CustomSelect %s', ( ...modeAndComponent ) => {
const [ , Component ] = modeAndComponent;

describe( 'Multiple selection', () => {
it( 'Should be able to select multiple items when provided an array', async () => {
const user = userEvent.setup();
const onChangeMock = jest.fn();

// initial selection as defaultValue
const defaultValues = [
'incandescent glow',
'ultraviolet morning light',
];

render(
<Component
defaultValue={ defaultValues }
onChange={ onChangeMock }
label="Multi-select"
>
{ [
'aurora borealis green',
'flamingo pink sunrise',
'incandescent glow',
'rose blush',
'ultraviolet morning light',
].map( ( item ) => (
<CustomSelectItem key={ item } value={ item }>
{ item }
</CustomSelectItem>
) ) }
</Component>
);

const currentSelectedItem = screen.getByRole( 'combobox', {
expanded: false,
} );

// ensure more than one item is selected due to defaultValues
expect( currentSelectedItem ).toHaveTextContent(
`${ defaultValues.length } items selected`
);

await user.click( currentSelectedItem );

expect( screen.getByRole( 'listbox' ) ).toHaveAttribute(
'aria-multiselectable'
);

// ensure defaultValues are selected in list of items
defaultValues.forEach( ( value ) =>
expect(
screen.getByRole( 'option', {
name: value,
selected: true,
} )
).toBeVisible()
);

// name of next selection
const nextSelectionName = 'rose blush';

// element for next selection
const nextSelection = screen.getByRole( 'option', {
name: nextSelectionName,
} );

// click next selection to add another item to current selection
await user.click( nextSelection );

// updated array containing defaultValues + the item just selected
const updatedSelection = defaultValues.concat( nextSelectionName );

expect( onChangeMock ).toHaveBeenCalledWith( updatedSelection );

expect( nextSelection ).toHaveAttribute( 'aria-selected' );

// expect increased array length for current selection
expect( currentSelectedItem ).toHaveTextContent(
`${ updatedSelection.length } items selected`
);
} );

it( 'Should be able to deselect items when provided an array', async () => {
const user = userEvent.setup();

// initial selection as defaultValue
const defaultValues = [
'aurora borealis green',
'incandescent glow',
'key lime green',
'rose blush',
'ultraviolet morning light',
];

render(
<Component defaultValue={ defaultValues } label="Multi-select">
{ defaultValues.map( ( item ) => (
<CustomSelectItem key={ item } value={ item }>
{ item }
</CustomSelectItem>
) ) }
</Component>
);

const currentSelectedItem = screen.getByRole( 'combobox', {
expanded: false,
} );

await user.click( currentSelectedItem );

// Array containing items to deselect
const nextSelection = [
'aurora borealis green',
'rose blush',
'incandescent glow',
];

// Deselect some items by clicking them to ensure that changes
// are reflected correctly
await Promise.all(
nextSelection.map( async ( value ) => {
await user.click(
screen.getByRole( 'option', { name: value } )
);
expect(
screen.getByRole( 'option', {
name: value,
selected: false,
} )
).toBeVisible();
} )
);

// expect different array length from defaultValues due to deselecting items
expect( currentSelectedItem ).toHaveTextContent(
`${
defaultValues.length - nextSelection.length
} items selected`
);
} );
} );

it( 'Should allow rendering a custom value when using `renderSelectedValue`', async () => {
const user = userEvent.setup();

const renderValue = ( value: string | string[] ) => {
return <img src={ `${ value }.jpg` } alt={ value as string } />;
};

render(
<Component label="Rendered" renderSelectedValue={ renderValue }>
<CustomSelectItem value="april-29">
{ renderValue( 'april-29' ) }
</CustomSelectItem>
<CustomSelectItem value="july-9">
{ renderValue( 'july-9' ) }
</CustomSelectItem>
</Component>
);

const currentSelectedItem = screen.getByRole( 'combobox', {
expanded: false,
} );

expect( currentSelectedItem ).toBeVisible();
brookewp marked this conversation as resolved.
Show resolved Hide resolved

// expect that the initial selection renders an image
expect( currentSelectedItem ).toContainElement(
screen.getByRole( 'img', { name: 'april-29' } )
);

expect(
screen.queryByRole( 'img', { name: 'july-9' } )
).not.toBeInTheDocument();

await user.click( currentSelectedItem );

// expect that the other image is only visible after opening popover with options
expect( screen.getByRole( 'img', { name: 'july-9' } ) ).toBeVisible();
brookewp marked this conversation as resolved.
Show resolved Hide resolved
expect(
screen.getByRole( 'option', { name: 'july-9' } )
).toBeVisible();
} );
} );
Loading