I'm testing a react Material UI Menu component using react-testing-library with an onClose prop that is triggered when the menu loses focus. I cannot trigger this state though even when I add a click to a component outside of the menu or a focus an input element outside.
const UserMenu: React.FunctionComponent<UserMenuProps> = ({ className }) => {
const signedIn = useAuthState(selectors => selectors.SignedIn);
const username = useAuthState(selectors => selectors.Username);
const messages = useMapState((state: AppRootState) => state.app.messages);
const signOut = useSignOut();
const [open, updateOpenStatus] = useState(false);
const anchorRef = useRef(null);
if (!signedIn) {
return <div data-testid="user-menu" className={className}>
<LinkButton to={ROUTES.SignIn.link()}>{messages.SignIn.Title}</LinkButton>
<LinkButton to={ROUTES.SignUp.link()}>{messages.SignUp.Title}</LinkButton>
<LinkButton to={ROUTES.ConfirmSignUp.link()}>{messages.ConfirmSignUp.Title}</LinkButton>
</div>;
}
return <div data-testid="user-menu" className={className}>
<Grid container direction="row" alignItems="center">
<Typography noWrap variant="subtitle2">
<span id="username" className="bold">{username}</span>
</Typography>
<IconButton id="menu-toggle" buttonRef={anchorRef} onClick={() => updateOpenStatus(true)}>
<AccountCircle/>
</IconButton>
<Menu
anchorEl={anchorRef.current}
anchorOrigin={{
vertical: 'top',
horizontal: 'right'
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right'
}}
open={open}
onClose={() => updateOpenStatus(false)}
>
<MenuItem id="sign-out" onClick={() => { updateOpenStatus(false); signOut(); }}>{messages.SignOut.Action}</MenuItem>
</Menu>
</Grid>
</div>;
};
Test code
it('should open and close menu', async () => {
const { getByTestId } = render(<><UserMenu/>
<input data-testid="other"/>
</>, { state });
fireEvent.click(getByTestId('menu-toggle'));
expect(MockMenu).toHaveBeenLastCalledWith(expect.objectContaining({ open: true }), {});
fireEvent.focus(getByTestId('other'));
expect(MockMenu).toHaveBeenLastCalledWith(expect.objectContaining({ open: false }), {});
});
I've also tried fireEvent.click(getByTestId('other')); without success.
This question for enzyme has a solution by using tree.find(Menu).simulate("close");, but I don't think that's possible with react-testing-library.
You can trigger the close by clicking on the backdrop generated by the Menu. The easiest way I found to do that was to select the backdrop via the getByRole('presentation') method of @testing-library.
Test Code:
it('should open and close the menu', () => {
const { getByTestId, getByRole } = render(<UserMenu />);
fireEvent.click(getByTestId('menu-toggle'));
// Get the backdrop, then get the firstChild because this is where the event listener is attached
fireEvent.click(getByRole('presentation').firstChild));
expect(MockMenu).toHaveBeenLastCalledWith(expect.objectContaining({ open: false }), {});
});
I use @testing-library/user-event to trigger an Escape keypress.
As of @testing-library/user-event v14.0.0 (2022-03-29), they have changed the pattern matching for escape to be {Escape} instead of {esc}
import {
render,
screen,
waitFor,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('should open and close the menu', async () => {
render(<UserMenu />);
// Click to open
userEvent.click(
// Note that grabbing by test id is frowned upon if there are other ways to grab it https://testing-library.com/docs/queries/about/#priority
screen.getByTestId('menu-toggle')
);
// Wait for dialog to open
await waitFor(() => expect(MockMenu).toHaveBeenLastCalledWith(expect.objectContaining({ open: true }), {}));
// Press `esc` to close
userEvent.keyboard('{Escape}');
// Wait for dialog to close
await waitFor(() => expect(MockMenu).toHaveBeenLastCalledWith(expect.objectContaining({ open: false }), {}));
});
@testing-library/user-event v14.0.0 (2022-03-29)import {
render,
screen,
waitFor,
} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('should open and close the menu', async () => {
render(<UserMenu />);
// Click to open
userEvent.click(
// Note that grabbing by test id is frowned upon if there are other ways to grab it https://testing-library.com/docs/queries/about/#priority
screen.getByTestId('menu-toggle')
);
// Wait for dialog to open
await waitFor(() => expect(MockMenu).toHaveBeenLastCalledWith(expect.objectContaining({ open: true }), {}));
// Press `esc` to close
userEvent.keyboard('{esc}');
// Wait for dialog to close
await waitFor(() => expect(MockMenu).toHaveBeenLastCalledWith(expect.objectContaining({ open: false }), {}));
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With