Как протестировать реагирующий модальный портал с библиотекой реагирующего тестирования?
В приложениях React обычно есть модальные окна или диалоги, которые появляются поверх основного содержимого. Однако тестирование этих модальных окон может оказаться сложной задачей, поскольку они обычно реализуются с помощью порталов, которые представляют собой элементы, отображаемые вне основной иерархии DOM. В этом блоге мы рассмотрим, как протестировать модальное окно, использующее порталы, с помощью библиотеки тестирования React.
Во-первых, давайте создадим простой модальный компонент, использующий порталы:
Modal.js
import React, { useRef, useEffect } from 'react'; import ReactDOM from 'react-dom'; const Modal = ({ children, onClose }) => { const modalRoot = useRef(document.createElement('div')); useEffect(() => { const root = document.getElementById('modal-root'); root.appendChild(modalRoot.current); return () => { root.removeChild(modalRoot.current); }; }, []); return ReactDOM.createPortal( <div className="modal-overlay"> <div className="modal"> <button onClick={onClose}>Close</button> {children} </div> </div>, modalRoot.current ); }; export default Modal;
В этом компоненте мы создаем ссылку modalRoot
, которая ссылается на новый элемент div
. Мы используем хук useEffect
, чтобы добавить этот элемент к элементу modal-root
в основной иерархии DOM. Мы также удаляем элемент modalRoot
, когда компонент размонтирован. Наконец, мы используем ReactDOM.createPortal
для рендеринга модального содержимого внутри элемента modalRoot
.
Теперь давайте напишем тест для этого компонента, используя библиотеку тестирования React:
Modal.test.js
import React from 'react'; import { render, fireEvent, screen } from '@testing-library/react'; import Modal from './Modal'; describe('Modal', () => { it('renders modal content', () => { render( <Modal onClose={jest.fn()}> <h1>Modal Title</h1> <p>Modal Content</p> </Modal> ); expect(screen.getByText('Modal Title')).toBeInTheDocument(); expect(screen.getByText('Modal Content')).toBeInTheDocument(); }); it('calls onClose when close button is clicked', () => { const onClose = jest.fn(); render( <Modal onClose={onClose}> <h1>Modal Title</h1> <p>Modal Content</p> </Modal> ); fireEvent.click(screen.getByText('Close')); expect(onClose).toHaveBeenCalled(); }); });
В первом тесте мы отображаем модальный компонент с некоторыми дочерними элементами и используем screen.getByText
для проверки того, что дочерние элементы отображаются внутри модального окна. Во втором тесте мы визуализируем модальное окно с помощью фиктивной функции onClose
и используем fireEvent.click
для имитации нажатия кнопки закрытия. Затем мы проверяем, что функция onClose
вызывается.
Обратите внимание, что нам не нужно беспокоиться о том, что модальное окно отображается вне основной иерархии DOM. Объект screen
, предоставляемый библиотекой тестирования React, использует объект document
, который включает в себя любые элементы, отображаемые через порталы.
Таким образом, тестировать модальные окна, использующие порталы, легко с помощью библиотеки тестирования React. Мы можем визуализировать модальный компонент так же, как и любой другой компонент, и использовать объект screen
для взаимодействия с модальным и его содержимым.
Вот еще один пример повторно используемого модального компонента, использующего портал:
В этом примере компонент Portal
определяется в собственном файле и экспортируется как компонент по умолчанию. Компонент Modal
определяется в отдельном файле и импортирует компонент Portal
.
Портал.js
import React, { useRef, useEffect } from 'react'; import ReactDOM from 'react-dom'; const Portal = ({ children }) => { const portalNode = useRef(document.createElement('div')); useEffect(() => { const portalRoot = document.getElementById('portal-root'); portalRoot.appendChild(portalNode.current); return () => { portalRoot.removeChild(portalNode.current); }; }, []); return ReactDOM.createPortal(children, portalNode.current); }; export default Portal;
Modal.js
import React from 'react'; import Portal from './Portal'; const Modal = ({ onClose, children }) => ( <Portal> <div className="modal"> <div className="modal-content"> <button onClick={onClose}>Close</button> {children} </div> </div> </Portal> ); export default Modal;
Тестирование компонента Portal
может быть немного сложным, так как он в основном взаимодействует с DOM и не имеет никакого видимого вывода. Однако есть несколько вещей, которые мы можем проверить, чтобы убедиться, что компонент Portal
работает правильно.
Один из подходов к тестированию компонента Portal
заключается в использовании document.createElement
для создания узла DOM, рендеринга компонента Portal
с этим узлом в качестве его дочернего элемента, а затем создания утверждений о визуализированных выходных данных. Вот пример теста для компонента Portal
:
Портал.test.js
import React from 'react'; import ReactDOM from 'react-dom'; import Portal from './Portal'; describe('Portal', () => { it('renders children in a portal', () => { const container = document.createElement('div'); const child = <h1>Portal Content</h1>; ReactDOM.render(<Portal>{child}</Portal>, container); expect(container.firstChild).toMatchSnapshot(); }); });
В этом тесте мы создаем элемент div
, используя document.createElement
, и передаем его как дочерний элемент компоненту Portal
. Затем мы используем ReactDOM.render
для рендеринга компонента Portal
и его дочерних элементов в контейнере.
Наконец, мы делаем утверждение, используя сопоставитель Jest toMatchSnapshot
, который делает снимок отрендеренного вывода и сравнивает его с сохраненным снимком. Это позволяет нам легко увидеть, соответствует ли визуализированный вывод нашим ожиданиям.
Имейте в виду, что тестирование компонента Portal
в первую очередь касается проверки того, что он правильно взаимодействует с DOM и отображает свои дочерние элементы в правильном месте. Вы также можете протестировать пограничные случаи, такие как рендеринг компонента Portal
без каких-либо дочерних элементов или его рендеринг с несколькими дочерними элементами.
Modal.test.js
import React from 'react'; import { render, fireEvent, screen } from '@testing-library/react'; import Modal from './Modal'; describe('Modal', () => { it('renders modal content', () => { render( <Modal onClose={jest.fn()}> <h1>Modal Title</h1> <p>Modal Content</p> </Modal> ); expect(screen.getByText('Modal Title')).toBeInTheDocument(); expect(screen.getByText('Modal Content')).toBeInTheDocument(); }); it('calls onClose when close button is clicked', () => { const onClose = jest.fn(); render( <Modal onClose={onClose}> <h1>Modal Title</h1> <p>Modal Content</p> </Modal> ); fireEvent.click(screen.getByText('Close')); expect(onClose).toHaveBeenCalled(); }); });
Этот тест очень похож на предыдущие примеры, но теперь мы импортируем компонент Modal
из отдельного файла и тестируем его как составной компонент, который использует компонент Portal
для рендеринга своего содержимого.
В целом, разделение компонентов Portal
и Modal
на отдельные файлы может сделать ваш код более модульным и простым в обслуживании.
Заключение
Использование портала для рендеринга модального содержимого — отличный способ отделить модальное содержимое от основной иерархии DOM и улучшить доступность вашего приложения. Библиотека тестирования React предоставляет отличные инструменты для тестирования компонентов, использующих порталы, и вы даже можете повторно использовать компонент Portal
для других компонентов.
Если у вас есть дополнительные вопросы, вам нужны дополнительные рекомендации или вы хотите связаться со мной, не стесняйтесь обращаться ко мне в моем профиле LinkedIn. Буду рад помочь вам чем смогу!