React Select Event Simulation
react-select-event is a specialized companion library for React Testing Library, designed to accurately simulate user interactions on `react-select` components in unit and integration tests. The current stable version is `5.5.1`. This library has an active release cadence, with frequent minor and patch updates, alongside occasional major versions introducing significant features or breaking changes. Its key differentiators include its tight integration with `react-testing-library`'s ecosystem, enabling robust testing of complex `react-select` scenarios like asynchronous option loading, multi-select, and portal-rendered menus. It automatically wraps actions in React's `act` utility (since v5.1.0) to prevent common `act` warnings, and supports `react-select` versions from 2.1.0 onwards. It streamlines testing workflows by providing high-level event simulation functions that mimic real user behavior, unlike lower-level `fireEvent` calls that might not fully replicate `react-select`'s internal state management.
Common errors
-
TestingLibraryElementError: Unable to find an element with the text: [Your Option Text]
cause The option text provided to `selectEvent.select` does not match any currently available or rendered options in the dropdown, or the dropdown menu itself is not open/visible.fixEnsure the text passed to `selectEvent.select` precisely matches the visible label of an option. If `react-select` loads options asynchronously (e.g., `AsyncSelect`), make sure to simulate typing into the input (`fireEvent.change`) to trigger option loading before attempting to select. Also, confirm the `container` config is set correctly if `menuPortalTarget` is used. -
Warning: An update to %s inside a test was not wrapped in act(...).
cause React's `act` warning indicates that a state update occurred during testing that was not properly batched or awaited. This often happens with asynchronous operations in components.fixUpgrade `react-select-event` to `v5.1.0` or newer. This version automatically wraps all interactions in `act` to prevent these warnings. If on an older version, manually wrap your `selectEvent.select` calls within `await act(async () => { ... })`. -
TypeError: Cannot read properties of undefined (reading 'querySelector')
cause This error typically occurs when `react-select-event` tries to find elements within the default testing container, but the `react-select` dropdown has been rendered into a different DOM node (a portal) using `menuPortalTarget`.fixWhen using `menuPortalTarget` in your `react-select` component, provide the same target element (e.g., `document.body`) to the `config.container` option of `selectEvent.select`. Example: `await selectEvent.select(input, 'Option', { container: document.body })`.
Warnings
- breaking The `autoSelect` option, introduced in `v4.2.0` for the `create` function, was removed in `v5.0.0`. It has been replaced by the `waitForElement` option, providing more explicit control over the option creation lifecycle. This change primarily impacts users who upgraded from `v4.2.0` to `v5.0.0` or newer.
- gotcha `react-select-event` versions prior to `v5.5.1` might encounter compatibility issues, specifically `select` not working as expected, when used with React 18 and `@testing-library/react` v13. This was addressed in `v5.5.1`.
- gotcha When `react-select` renders its dropdown menu into a portal (e.g., using `menuPortalTarget={document.body}`), `react-select-event` might fail to locate and interact with the options. This results in selection failures or elements not found errors if the `container` option is not explicitly provided.
- gotcha Prior to `v5.1.0`, interactions with `react-select-event` might sometimes lead to 'An update to [...] inside a test was not wrapped in act(...)' warnings from React Testing Library. This occurs when state updates happen outside of React's `act` utility calls.
Install
-
npm install react-select-event -
yarn add react-select-event -
pnpm add react-select-event
Imports
- selectEvent
import { selectEvent } from 'react-select-event'; // Or incorrect direct import: import { select } from 'react-select-event';import selectEvent from 'react-select-event';
- select
import { select } from 'react-select-event'; // `select` is a method on the `selectEvent` default export, not a top-level named export.import selectEvent from 'react-select-event'; await selectEvent.select(inputElement, 'Option Label');
- create
import { create } from 'react-select-event'; // `create` is a method on the `selectEvent` default export.import selectEvent from 'react-select-event'; await selectEvent.create(inputElement, 'New Option');
Quickstart
import React from 'react';
import Select from 'react-select';
import { render, screen, fireEvent } from '@testing-library/react';
import selectEvent from 'react-select-event';
const OPTIONS = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
{ value: 'mango', label: 'Mango' },
];
const MyForm = ({ isMulti = false, menuPortalTarget = undefined }) => (
<form data-testid="form">
<label htmlFor="food">Favorite Food</label>
<Select
options={OPTIONS}
name="food"
inputId="food"
isMulti={isMulti}
menuPortalTarget={menuPortalTarget}
/>
</form>
);
describe('react-select-event basic usage', () => {
it('should select a single option', async () => {
render(<MyForm />);
expect(screen.getByTestId('form')).toHaveFormValues({ food: '' });
await selectEvent.select(screen.getByLabelText('Favorite Food'), 'Vanilla');
expect(screen.getByTestId('form')).toHaveFormValues({ food: 'vanilla' });
});
it('should select multiple options when isMulti is true', async () => {
render(<MyForm isMulti />);
expect(screen.getByTestId('form')).toHaveFormValues({ food: [] });
await selectEvent.select(screen.getByLabelText('Favorite Food'), ['Strawberry', 'Mango']);
expect(screen.getByTestId('form')).toHaveFormValues({ food: ['strawberry', 'mango'] });
});
it('should handle async select options by typing', async () => {
const loadOptions = (inputValue: string) =>
Promise.resolve(
OPTIONS.filter(option => option.label.toLowerCase().includes(inputValue.toLowerCase()))
);
// A minimal Async component for demonstration
const AsyncSelect = (props: any) => (
<Select {...props} loadOptions={loadOptions} cacheOptions defaultOptions />
);
render(
<form data-testid="async-form">
<label htmlFor="async-food">Async Food</label>
<AsyncSelect name="async-food" inputId="async-food" />
</form>
);
expect(screen.getByTestId('async-form')).toHaveFormValues({ 'async-food': '' });
// Simulate typing to trigger loadOptions
fireEvent.change(screen.getByLabelText('Async Food'), { target: { value: 'choco' } });
await selectEvent.select(screen.getByLabelText('Async Food'), 'Chocolate');
expect(screen.getByTestId('async-form')).toHaveFormValues({ 'async-food': 'chocolate' });
});
});