Модальные окна и порталы являются важной частью пользовательских интерфейсов, часто используемыми для отображения всплывающих сообщений, окон подтверждения или диалогов. В современных приложениях на React тестирование таких компонентов важно для обеспечения их правильного функционирования. Enzyme, с его функциональностью для рендеринга компонентов и манипуляций с DOM, является отличным инструментом для тестирования модальных окон и порталов.
Портал в React — это механизм, позволяющий рендерить дочерние элементы компонента в другом месте DOM-дерева, отличном от обычного родительского компонента. Это полезно для отображения компонентов поверх других, например, модальных окон или всплывающих подсказок, которые должны быть независимыми от структуры родительского компонента, но всё равно должны быть частью того же приложения.
Для создания портала используется React-функция
ReactDOM.createPortal. Например:
ReactDOM.createPortal(
<Modal />,
document.getElementById('modal-root')
)
Здесь компонент Modal рендерится в DOM-элемент с ID
modal-root, а не в стандартную иерархию компонентов.
Модальные окна часто требуют особого подхода при тестировании, поскольку они отображаются поверх основного контента, могут иметь скрытый или изменяющийся вид в зависимости от состояния, а также могут требовать манипуляций с фокусом и взаимодействием с пользователем.
Для тестирования модального окна начнём с примера компонента:
const Modal = ({ isOpen, onClose }) => {
if (!isOpen) return null;
return (
<div className="modal">
<div className="modal-content">
<button className="close" onCl ick={onClose}>Закрыть</button>
<p>Содержимое модального окна</p>
</div>
</div>
);
};
В этом примере компонент модального окна отображается только если
свойство isOpen равно true. В противном случае
компонент возвращает null, что предотвращает его
рендеринг.
Для начала напишем тест с использованием Enzyme для проверки отображения модального окна при условии, что оно открыто:
import { shallow } from 'enzyme';
import Modal from './Modal';
describe('Modal', () => {
it('не отображается, если isOpen = false', () => {
const wrapper = shallow(<Modal isOpen={false} />);
expect(wrapper.isEmptyRender()).toBe(true);
});
it('отображается, если isOpen = true', () => {
const wrapper = shallow(<Modal isOpen={true} />);
expect(wrapper.exists('.modal')).toBe(true);
});
});
Этот тест проверяет, что модальное окно не рендерится, если свойство
isOpen равно false, и что оно отображается при
значении true.
Следующий шаг — это тестирование взаимодействий с компонентом, таких
как закрытие модального окна при клике на кнопку закрытия. Для этого
добавим обработчик onClose, который будет вызываться при
клике на кнопку закрытия.
const Modal = ({ isOpen, onClose }) => {
if (!isOpen) return null;
return (
<div className="modal">
<div className="modal-content">
<button className="close" onCl ick={onClose}>Закрыть</button>
<p>Содержимое модального окна</p>
</div>
</div>
);
};
Теперь напишем тест для проверки корректности работы события:
describe('Modal', () => {
it('вызывает onClose при клике на кнопку закрытия', () => {
const onCloseM ock = jest.fn();
const wrapper = shallow(<Modal isOpen={true} onCl ose={onCloseMock} />);
wrapper.find('.close').simulate('click');
expect(onCloseMock).toHaveBeenCalledTimes(1);
});
});
В этом тесте мы создаём мок-функцию для обработчика
onClose, проверяем, что при клике на кнопку эта функция
вызывается.
Поскольку портал рендерит компонент в другой элемент DOM, нам необходимо убедиться, что Enzyme может корректно работать с такими компонентами. Enzyme сам по себе не рендерит порталы в стандартном DOM-дереве, поэтому необходимо использовать рендеринг с полным DOM, чтобы корректно тестировать порталы.
Допустим, у нас есть модальное окно, которое рендерится через портал:
import ReactDOM from 'react-dom';
const Modal = ({ isOpen, onClose }) => {
if (!isOpen) return null;
return ReactDOM.createPortal(
<div className="modal">
<div className="modal-content">
<button className="close" onCl ick={onClose}>Закрыть</button>
<p>Содержимое модального окна</p>
</div>
</div>,
document.getElementById('modal-root')
);
};
Для корректного тестирования компонента с использованием портала,
необходимо использовать mount() вместо
shallow(), так как shallow() не поддерживает
рендеринг порталов.
import { mount } from 'enzyme';
describe('Modal с порталом', () => {
it('рендерит портал в правильный элемент', () => {
const onCloseM ock = jest.fn();
const wrapper = mount(<Modal isOpen={true} onCl ose={onCloseMock} />);
// Проверяем, что элемент модального окна находится в элементе с id 'modal-root'
expect(document.getElementById('modal-root').querySelector('.modal')).not.toBeNull();
});
it('вызывает onClose при клике на кнопку закрытия', () => {
const onCloseM ock = jest.fn();
const wrapper = mount(<Modal isOpen={true} onCl ose={onCloseMock} />);
wrapper.find('.close').simulate('click');
expect(onCloseMock).toHaveBeenCalledTimes(1);
});
});
Здесь мы используем mount() для рендеринга компонента,
что позволяет работать с порталом. В тестах проверяем, что модальное
окно рендерится в правильный DOM-элемент и что обработчик закрытия
срабатывает корректно.
При тестировании модальных окон важно учитывать, что такие компоненты должны правильно управлять фокусом, чтобы обеспечить доступность. Модальные окна обычно требуют перемещения фокуса на себя при открытии и возвращения его на прежний элемент при закрытии.
Для тестирования фокуса можно использовать библиотеку
jsdom в сочетании с Enzyme, чтобы симулировать перемещение
фокуса и проверку его состояния:
describe('Modal с фокусом', () => {
it('перемещает фокус на модальное окно при открытии', () => {
const onCloseM ock = jest.fn();
const wrapper = mount(<Modal isOpen={true} onCl ose={onCloseMock} />);
const modal = wrapper.find('.modal');
const closeButton = wrapper.find('.close');
expect(document.activeElement).toBe(closeButton.getDOMNode());
});
});
Этот тест проверяет, что при открытии модального окна фокус перемещается на кнопку закрытия.
Тестирование модальных окон и порталов с помощью Enzyme позволяет
эффективно проверять не только визуальное отображение, но и поведение
компонентов в сложных сценариях. Использование mount() для
рендеринга с полным DOM и тщательная проверка взаимодействий с
пользователем, таких как клики и управление фокусом, позволяют убедиться
в правильности работы таких компонентов в реальных условиях.