Тест React проходит, но компонент не работает должным образом при обновлении состояния в обработчике document.addListener

#javascript #reactjs #jestjs #react-testing-library

#javascript #reactjs #jestjs #react-testing-library

Вопрос:

У меня есть компонент выбора даты, подобный этому:

 
const MonthPanel = ({ setMode = () => {} }) => {
  return (
    <div>
      <button
        onClick={(e) => setMode("year")}
        data-testid="datepicker-year-button"
      >
        2021
      </button>
    </div>
  );
};

const YearPanel = () => {
  return <div data-testid="datepicker-year-panel">YearPanel</div>;
};

const DatePicker = (props) => {
  const [open, setOpen] = useState(false);
  const [mode, setMode] = useState("month");
  const containerRef = useRef();

  const handleOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
  };

  const handleModeChange = (newMode) => {
    setMode(newMode);
  };

  const generatePanel = () => {
    switch (mode) {
      case "month":
        return <MonthPanel setMode={handleModeChange} />;
      case "year":
        return <YearPanel />;
      default:
        return null;
    }
  };

  useEffect(() => {
    const handleOutsideClick = (e) => {
      if (containerRef.current amp;amp; !containerRef.current.contains(e.target)) {
        handleClose();
      }
    };
    window.addEventListener("click", handleOutsideClick);
    return () => {
      window.removeEventListener("click", handleOutsideClick);
    };
  }, []);

  return (
    <div
      ref={containerRef}
      data-testid="datepicker-container"
    >
      <div onClick={handleOpen} data-testid="datepicker-input">
        Select a date
      </div>
      {open amp;amp; <div data-testid="datepicker-dialog">{generatePanel()}</div>}
    </div>
  );
};
 

И у меня есть такой тестовый файл, как этот:

 it.only("should close DatePicker dialog when clicked only outside DatePicker", () => {
    const { getByTestId, queryByTestId } = render(
      <DatePicker />
    );
    userEvent.click(getByTestId("datepicker-input"));
    userEvent.click(getByTestId("datepicker-year-button"));
    expect(queryByTestId("datepicker-dialog")).toBeInTheDocument();
    userEvent.click(document.body);
    expect(queryByTestId("datepicker-dialog")).toBeNull();
  });
 

Желаемое состояние:

Когда вы нажимаете вне DatePicker, он должен закрыться. И когда вы нажмете [data-testid="datepicker-year-button"] на него, он должен изменить режим выбора даты на «год», чтобы была показана панель «год».

Текущее состояние:

Когда вы нажимаете [data-testid="datepicker-year-button"] , он меняет режим выбора даты на «год», а панель месяцев (а вместе с ней и сама кнопка) удаляется. Поскольку кнопка является целью события и уже удалена, containerRef.current.contains(e.target) условие is false и Диалоговое окно также будут удалены. Но тест показывает, что диалоговое окно находится в документе.

Вопрос в том, как я должен правильно протестировать эту функциональность.

Комментарии:

1. вы уверены, что ваш тестировщик знает, как обращаться document.body ?

2. кажется, ваше желаемое состояние на самом деле заключается в том, что тест завершается неудачей, когда он должен. Вы могли бы обновить вопрос, чтобы отразить это.

Ответ №1:

Вы можете вызвать e.stopPropagation() обработчик нажатия кнопки <MonthPanel> , чтобы предотвратить всплывающее событие и его перехват window . eventListener

 <button
    onClick={(e) => {
        e.stopPropagation();
        setMode("year");
    }}
    data-testid="datepicker-year-button"
>
 

Комментарии:

1. Я это знаю. Проблема в том, что тест должен завершиться неудачей, но он проходит. Я хочу знать, что не так с моим тестом и как я должен его протестировать.

2. Итак, вы пытаетесь сделать тест неудачным специально? Вероятно, вам следует упомянуть об этом в исходном сообщении.

3. Нет. Код работает некорректно, верно. Таким образом, тест должен завершиться неудачей, но он проходит. Я спрашиваю, что не так с моим тестом.

4. Я имею в виду, что вы специально изменили код, чтобы он не работал должным образом, чтобы тест не прошел. Но в данном случае это на самом деле не сбой.

5. Да. Это так.

Ответ №2:

Попробуйте использовать асинхронные методы, такие как waitFor или waitForElementToBeRemoved :

   await waitFor(() => {
     expect(queryByTestId("datepicker-dialog")).toBeNull();
  });
 

React-dom введенный act API для переноса кода, который отображает или обновляет компоненты, это приближает выполнение теста к тому, как React работает в браузере. Библиотека тестирования React включает некоторые из своих API в функцию act, но в некоторых случаях вам все равно потребуется использовать waitFor или waitForElementToBeRemoved

Ответ №3:

Я считаю, что ваш userEvent.click(document.body); не работает должным образом.

Скорее document.body всего, не решается так, как должно. вероятно, вы используете пользовательское событие testing-library, и их основным примером является:

 import { screen } from '@testing-library/dom'
import userEvent from '@testing-library/user-event'

test('types inside textarea', () => {
  document.body.innerHTML = `<textarea />`

  userEvent.type(screen.getByRole('textbox'), 'Hello, World!')
  expect(screen.getByRole('textbox')).toHaveValue('Hello, World!')
})
 

убедитесь, что у вас document.body.innerHTML установлено значение something