I am testing user click interaction with using Jest and RTL in my React app. Component states are updating with user interaction, i.e., a click event.
My component has one combo box. When clicked, it should open a list box and show options.
test.only('option should open when clicked', async () => {
// Arrange
const props = {
searchProcessing: false,
setSearchProcessing: () => {},
setFilterInfo: () => {},
setData: () => {},
sortBy: {
FieldName: 'state',
Direction: 'ASC',
},
pageNumber: 1,
pageSize: 10,
};
// Act
await act(async () => {
await render(<Component{...props} />);
const combobox = await screen.findByRole('combobox', { owns: 'select-state' });
fireEvent.click(combobox);
});
const options = within(screen.getByRole('listbox')).getAllByRole('option');
screen.debug(options);
// Assert
expect(options).toHaveLength(2);
});
When I run the above code, it throws an error:
Unable to find role="combobox"
Ignored nodes: comments, script, style
<body>
<div />
</body>
await act(async () => {
23 | await render(<Component {...props} />);
> 24 | combobox = await screen.findByRole('combobox', { owns: 'select-state' });
| ^
25 | });
When I put fireEvent
in the waitFor
async method insted of act
per the below snippet, it works.
await waitFor(() => {
fireEvent.click(screen.getByRole('combobox', { owns: 'select-state' }));
});
console.log
<a
aria-label="AK"
aria-selected="false"
class="dropdown-item"
href="#"
id="select-state-item-0"
role="option"
>
AK
</a>
at logDOM (node_modules/@testing-library/dom/dist/pretty-dom.js:94:13)
at Array.forEach (<anonymous>)
console.log
<a
aria-label="AK2"
aria-selected="false"
class="dropdown-item"
href="#"
id="select-state-item-1"
role="option"
>
AK2
</a>
at logDOM (node_modules/@testing-library/dom/dist/pretty-dom.js:94:13)
at Array.forEach (<anonymous>)
PASS src/App/Main/Component.test.js (11.508 s)
√ state option should open when clicked (454 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 16.5 s
But according to the linting rules mentioned here: no-wait-for-side-effects
, it is a bad practice.
__
What am I missing here? Why are render
and findByRole
promise not waiting for an actual component DOM in the act
function. But the same code passed with waitFor
?
Your help is appreciated here. Thanks in advance.
After so many trials and errors I found 2 possible solutions.
Wrap the second action/event in another act:
await act(async () => {
await render(<Component {...props} />);
});
await act(async () => {
fireEvent.click(await screen.findByRole('combobox', { owns: 'select-state' }));
});
RTL has another utility userEvent. You can use it instead of fireEvent as below.
const user = userEvent.setup();
await user.click(await screen.findByRole('combobox', { owns: 'select-state' }));
Hope it helps. Thanks.