ant design v4 breaks react testing library tests for Select and Autocomplete
Asked Answered
H

4

14

I have recently upgraded my React project to ant design v4 and all the tests that use a Select, AutoComplete or Tooltip are broken. Basically when clicking the components, the modal or select options are not present in JSDOM. This used to work fine in v3.

Can somebody show me how to test antd v4 with react testing library ?

Example:

My Component:

import React from "react";
import "./styles.css";
import { Select } from "antd";

const { Option } = Select;

function handleChange(value) {
  console.log(`selected ${value}`);
}

export default function App() {
  return (
    <div className="App" style={{ marginTop: "40px" }}>
      <Select
        defaultValue="lucy"
        style={{ width: 120 }}
        onChange={handleChange}
      >
        <Option value="jack">Jack</Option>
        <Option value="lucy">Lucy</Option>
        <Option value="disabled" disabled>
          Disabled
        </Option>
        <Option value="Yiminghe">yiminghe</Option>
      </Select>
    </div>
  );
}

My test

import "@testing-library/jest-dom/extend-expect";
import React from "react";
import { render, fireEvent, prettyDOM } from "@testing-library/react";
import App from "./App";

test("App Test", () => {
  const { queryAllByText, getByText, container } = render(<App />);

  expect(queryAllByText("Lucy").length).toBe(1);
  expect(queryAllByText("Jack").length).toBe(0);
  fireEvent.click(getByText("Lucy"));
  console.log(prettyDOM(container));
  // This line fails although I would expect the dropdown to be open and all the options visible
  expect(queryAllByText("Jack").length).toBe(1);
});

Here is a link to a codesandbox that reproduces the issue. (As mentioned, that code used to work in v3).

https://codesandbox.io/s/staging-shape-0xkrl?file=/src/App.test.js:0-494

Hookworm answered 7/4, 2020 at 12:32 Comment(0)
H
36

After losing 2 days on this, here is the problem and solution:

Problem

In antd v3 it used to be possible to open a Select by doing selectHtmlElement.click(). You can test in the chrome dev tools console. In v4 this does not work.

This means that RTL which uses JSDOM under the hood will have the same behaviour. When you do fireEvent.click(selectElement); nothing happens !

Solution

This put me on the right track: https://github.com/ant-design/ant-design/issues/22074

The event you need to trigger is not a click() but a mouseDown() on the first child of the select.

const elt = getByTestId('your-select-test-id').firstElementChild;
fireEvent.mouseDown(elt); // THIS WILL OPEN THE SELECT !

Now at this point you probably want to select an option from the list but there is an animation going on so the following code (that used to work in v3) will also fail.

expect(getByText('Option from Select')).toBeVisible(); // FAILS !

You have 2 options, use toBeInTheDocument() or wait for the animation to be over by using waitFor(...)

Option 1: Faster but not totally accurate, I prefer to use this for simple use cases as it makes the tests faster and synchronous

expect(getByText('Option from Select')).toBeInTheDocument(); // WORKS !

Option 2: Slower as you need to wait for the animation to finish but more accurate for complex cases

await waitFor(() => expect(getByText('Option from Select')).toBeVisible()); // WORKS !
Hookworm answered 9/4, 2020 at 6:32 Comment(6)
Worth mentionning that fireEvent.change(elt.querySelector('input'), {target:{value:{'Option text'}}) works for searchable selects (only the right option will be visible, and ready to be clicked).Epoxy
works perfect, but instead of await waitFor(), you can also use setInterval(() => expect(), 0), you can also just set the open prop to true instead of doing the mouseDown eventThenceforth
How about the tooltip? Did you figure out how to test it? I can't seems to find the tooltip in the test even I put the data-testid @HookwormHepatitis
I only lost 20 mins thanks to your 2 days, thank you for this!!Constitutionally
Actually I'm able to userEvent.click (import userEvent from '@testing-library/user-event') it to open the dropdown, but when I try to click on the option, it occurss error about unsable to focus.Incandesce
To open the dropdown and select an option, this comment saves my life: github.com/ant-design/ant-design/issues/…Levulose
S
7

Looks like now ("antd": "4.17.3", "@testing-library/user-event": "^13.5.0") userEvent.click with { skipPointerEventsCheck: true } works:

const options = [
  { label: "🐱", value: "cat", },
  { label: "🐶", value: "dog", }
];

const onChangeMock = jest.fn();

render(
  <Select
    options={options}
    onChange={onChangeMock}
  />,
);

const select = screen.getByRole("combobox");

userEvent.click(select);

const option = screen.getByText("🐶");

userEvent.click(option, undefined, { skipPointerEventsCheck: true });

expect(onChangeMock).toHaveBeenCalledWith("dog", {
  label: "🐶",
  value: "dog",
});
Suspensor answered 14/12, 2021 at 13:45 Comment(1)
It seems to be { pointerEventsCheck: 0 } in RTL 14Portemonnaie
A
0

Unfortunately @klugjo answer did not work for me with antd-mobile's Radio component.

I solved the issue with adding an extra onClick property to the component:

<Radio.RadioItem
    key={key}
    checked={isSelected}
    onChange={onChange}
    onClick={onChange} // needed for rtl
    >
    {label}
</Radio.RadioItem>

This is not a clean solution, as it modifies the production code for testing. It might miss a failure on a TouchEvent, but this should be an issue to the antd-mobile library – not this test.

Ardellaardelle answered 12/5, 2021 at 13:47 Comment(0)
H
0

I had simillar problem. I've decided to control open state by myself from onDropdownVisibleChange and from onFocus.

import { AutoComplete, AutoCompleteProps, Input } from "antd";
import { useState } from "react";

export interface Option {
  id: string;
  name: string;
}

export interface AutocompleteFieldProps {
  // eslint-disable-next-line no-unused-vars
  onSearch: (name: string) => Promise<Option[]>;
}

export function AutocompleteField({ onSearch }: AutocompleteFieldProps) {
  const [open, setOpen] = useState(false);
  const [options, setOptions] = useState<Option[]>();

  async function handleSearch(name: string) {
    if (onSearch) {
      const matchingOptions = await onSearch(name);
      setOptions(matchingOptions);
      setOpen(true);
    }
  }

  function search() {
    handleSearch("");
  }

  function handleDropdownVisibleChange(open: boolean) {
    setOpen(open);
  }

  const antOptions: AutoCompleteProps["options"] = options?.map((option) => ({
    value: option.id,
    label: option.name,
  }));

  return (
    <AutoComplete
      onSearch={handleSearch}
      options={antOptions}
      data-testid="autocomplete"
      open={open}
      onDropdownVisibleChange={handleDropdownVisibleChange}
    >
      <Input.Search
        data-testid="autocomplete-input"
        className="ui-autocomplete-input"
        onFocus={search}
      />
    </AutoComplete>
  );
}

import { fireEvent, render, screen } from "@testing-library/react";

import { AutocompleteField } from "./autocomplete-field";
import { Option } from "./option";

const option1Name = "option A";
const option2Name = "option B";

const options: Option[] = [
  { id: "1", name: option1Name },
  { id: "2", name: option2Name },
];

function findOptions(name: string): Promise<Option[]> {
  return new Promise<Option[]>((resolve) => {
    const matching = options.filter((option) => option.name.includes(name));
    resolve(matching);
  });
}

it("AutocompleteField, focus, shows options", async () => {
  render(<AutocompleteField onSearch={findOptions} />);
  const input = autocompleteInput();

  fireEvent.focus(input);

  await screen.findByText(option1Name);
  await screen.findByText(option2Name);
});

function autocompleteInput() {
  return screen.getByTestId("autocomplete-input");
}
Handclap answered 18/7, 2024 at 12:33 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.