Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to trigger onClose for react ui Menu with react-testing-library?

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.

like image 469
kossmoboleat Avatar asked May 11 '26 10:05

kossmoboleat


2 Answers

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 }), {});
});
like image 73
Dylan Walker Avatar answered May 13 '26 00:05

Dylan Walker


I use @testing-library/user-event to trigger an Escape keypress.

NEW ANSWER

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 }), {}));
});

ORIGINAL ANSWER: Prior to @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 }), {}));
});
like image 44
Aerophite Avatar answered May 13 '26 01:05

Aerophite



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!