Тестирование фронтенд компонентов с jest-dom на видимость пользователю
Решаем проблему с тестирование элемента что он виден пользователю или не виден.
Предпосылки
Разработчики react-testing-library рекомендуют нам писать тесты так, как если бы код работал как у пользователя. А это значит что нужно вызывать пользовательские события, проверять что пользователь видит тот компонент, который должен.
Для тестирования компонентов мы используем библиотеку react-testing-library с testing-library/jest-dom, в рамках которого мы проверяем показывается тот или иной компонент на странице или нет.
Варианты решения
Проверять с помощью метод toHaveClass то, что у элемента есть тот или иной класс. Не является хорошей практикой, т.к. на скрытие элемента также влияют его родители. Если родительский элемент не виден пользователь, соответственно и его дочерние тоже не будут.
Использовать метод toBeVisible, который под капотом использует getComputedStyle, на расчет которого как раз влияют родительские компоненты и примененные классы к ним и к самому элементу. Но мы чаще всего css файлы стабаем (заменяем пустым объектом), чтобы тесты быстрее проходили, т.к. нам в принципе не важно, как выглядят компоненты у пользователя. Но есть проблема, как нам дать знать тестовому окружению, что у нас есть классы которые влияют на скрытие элемента.
Решение
Можно конечно попробовать каким-то спосом загрузить все стили приложения при выполнении тестов, но это негативно может повлиять на скорость загрузки. Поэтому такой вариант нас не устраивает, такой вариант описан здесь.
Самый простое и легкое решение, это загрузить только те классы, которые у нас вызывает сокрытие у пользователя компонента. Если мы говорим про tailwindcss, то это классы hidden и invisible.
Добавим в файл jest.setup.js небольшой код, который решает нашу проблему:
import '@testing-library/jest-dom';
beforeEach(() => {
const style = document.createElement('style');
style.innerHTML = `
.hidden {
display: none;
}
.invisible {
visibility: hidden;
}
`;
document.head.appendChild(style);
});
Пример теста:
...
test('Check element is show and hidden', async () => {
render( );
const searchInput = screen.getByPlaceholderText(/Найти/i);
await userEvent.click(searchInput);
const listbox = screen.getByRole('listbox');
expect(listbox).toBeVisible();
await userEvent.click(document.body);
// если вы вдруг будете запрашивать элемент когда он скрыт используется getByRole с hidden: true, иначе элемент не найдет, т.к. для пользователя он скрыт, но присутствует в DOM-дереве
// const listbox = screen.getByRole('listbox', { hidden: true });
expect(listbox).not.toBeVisible();
});
...
В данном тесте мы находим элемент у которого placeholder со значением «Найти», после мы имитируем нажатие на этот элемент. Компонент реализован у нас таким образом, что при нажатии на него, у нас показывается список элементов.
Затем с помощью getByRole ищем семантический тег список, проверяем его что он показывает на экране. Затем мы имитируем нажатие на тело документа, в приложении у нас компонент список скрывается на это действие и мы проверяем это с помощью expect (listbox).not.toBeVisible ();
Но если бы мы в файле jest.setup.js не добавили описание для этих css стилей, которые скрывают элемент, то у нас тест бы не прошел, т.к. jest-dom думал бы, что элемент показывается, т.к. он не знает «реализацию» класса.
P.S. Также есть метод toBeInTheDocument, он проверяет если ли элемент в DOM-дереве элементов, но он не проверяет что видит пользователь или нет его на экране.