因此,我很难使用React Fiber的门户为模态组件编写测试。因为我的模态安装到根目录上的domNode上,<body />但是因为该domNode不存在,所以测试失败。
<body />
给出一些上下文的代码:
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="theme-color" content="#000000"> <link rel="manifest" href="%PUBLIC_URL%/manifest.json"> <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> <title>React App</title> </head> <body> <noscript> You need to enable JavaScript to run this app. </noscript> <div id="modal-root"></div> <div id="root"></div> </body> </html>
App.js
import React, { Component } from 'react'; import logo from './logo.svg'; import './App.css'; import { Modal, ModalHeader } from './Modal'; class App extends Component { constructor(props) { super(props); this.state = { show: false }; this.toggleModal = this.toggleModal.bind(this); } toggleModal(show) { this.setState({ show: show !== undefined ? show : !this.state.show }); } render() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h1 className="App-title">Welcome to React</h1> </header> <p className="App-intro"> To get started, edit <code>src/App.js</code> and save to reload. </p> <button onClick={() => this.toggleModal()}>show modal</button> <Modal toggle={this.toggleModal} show={this.state.show}> <ModalHeader> <span>I'm a header</span> <button onClick={() => this.toggleModal(false)}> <span aria-hidden="true">×</span> </button> </ModalHeader> <p>Modal Body!!!</p> </Modal> </div> ); } } export default App;
Modal.js
import React, { Fragment } from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; // the next components are styled components, they are just for adding style no logic at all import { ModalBackdrop, ModalContent, ModalDialog, ModalWrap, } from './components'; class Modal extends React.Component { constructor(props) { super(props); this.el = document.createElement('div'); this.modalRoot = document.getElementById('modal-root'); this.outerClick = this.outerClick.bind(this); } componentDidMount() { this.modalRoot.appendChild(this.el); this.modalRoot.parentNode.style.overflow = ''; } componentWillUpdate(nextProps) { if (this.props.show !== nextProps.show) { this.modalRoot.parentNode.style.overflow = nextProps.show ? 'hidden' : ''; } } componentWillUnmount() { this.props.toggle(false); this.modalRoot.removeChild(this.el); } outerClick(event) { event.preventDefault(); if ( event.target === event.currentTarget || event.target.nodeName.toLowerCase() === 'a' ) { this.props.toggle(false); } } render() { const ModalMarkup = ( <Fragment> <ModalBackdrop show={this.props.show} /> <ModalWrap show={this.props.show} onClick={this.outerClick}> <ModalDialog show={this.props.show}> <ModalContent>{this.props.children}</ModalContent> </ModalDialog> </ModalWrap> </Fragment> ); return ReactDOM.createPortal(ModalMarkup, this.el); } } Modal.defaultProps = { show: false, toggle: () => {}, }; Modal.propTypes = { children: PropTypes.node.isRequired, show: PropTypes.bool, toggle: PropTypes.func, }; export default Modal;
最后但并非最不重要的测试: Modal.test.js
import React from 'react'; import Modal from './Modal.component'; import { ModalBackdrop, ModalContent, ModalDialog, ModalWrap, } from './components'; describe('Modal component', () => { const Child = () => <div>Yolo</div>; it('should render all the styled components and the children', () => { const component = mount( <Modal> <Child /> </Modal> ); expect(component.find(ModalBackdrop).exists()).toBeTruthy(); expect(component.find(ModalWrap).exists()).toBeTruthy(); expect(component.find(ModalWrap).contains(ModalDialog)).toBeTruthy(); expect(component.find(ModalDialog).contains(ModalContent)).toBeTruthy(); expect(component.find(ModalContent).contains(Child)).toBeTruthy(); }); });
一个codesandbox,以便您可以看到它的实际效果
因此,经过大量的奋斗,尝试和希望。我设法使测试正常工作,这个秘密,在我最终记得有可能之后,这很明显,就是修改 jsdom 并添加我们的 domNode ,我们只是忘了每次测试后都要卸载该组件。
Modal.test.js
import React from 'react'; import { mount } from 'enzyme'; import Modal from './Modal.component'; import { ModalBackdrop, ModalContent, ModalDialog, ModalWrap, } from './components'; describe('Modal component', () => { const Child = () => <div>Yolo</div>; let component; // add a div with #modal-root id to the global body const modalRoot = global.document.createElement('div'); modalRoot.setAttribute('id', 'modal-root'); const body = global.document.querySelector('body'); body.appendChild(modalRoot); afterEach(() => { component.unmount(); }); it('should render all the styled components and the children', () => { component = mount( <Modal> <Child /> </Modal>, ); expect(component.find(ModalBackdrop).exists()).toBeTruthy(); expect(component.find(ModalWrap).exists()).toBeTruthy(); expect(component.find(ModalWrap).contains(ModalDialog)).toBeTruthy(); expect(component.find(ModalDialog).contains(ModalContent)).toBeTruthy(); expect(component.find(ModalContent).contains(Child)).toBeTruthy(); }); it('should trigger toggle when clicked', () => { const toggle = jest.fn(); component = mount( <Modal toggle={toggle}> <Child /> </Modal>, ); component.find(ModalWrap).simulate('click'); expect(toggle.mock.calls).toHaveLength(1); expect(toggle.mock.calls[0][0]).toBeFalsy(); }); it('should mount modal on the div with id modal-root', () => { const modalRoot = global.document.querySelector('#modal-root'); expect(modalRoot.hasChildNodes()).toBeFalsy(); component = mount( <Modal> <Child /> </Modal>, ); expect(modalRoot.hasChildNodes()).toBeTruthy(); }); it('should clear the div with id modal-root on unmount', () => { const modalRoot = global.document.querySelector('#modal-root'); component = mount( <Modal> <Child /> </Modal>, ); expect(modalRoot.hasChildNodes()).toBeTruthy(); component.unmount(); expect(modalRoot.hasChildNodes()).toBeFalsy(); }); it('should set overflow hidden on the boddy element', () => { const body = global.document.querySelector('body'); expect(body.style.overflow).toBeFalsy(); component = mount( <Modal> <Child /> </Modal>, ); component.setProps({ show: true }); expect(body.style.overflow).toEqual('hidden'); component.setProps({ show: false }); expect(body.style.overflow).toBeFalsy(); }); });
一件大事是,酶尚未完全支持react 16,github问题。理论上所有测试都应该通过,但是它们仍然失败了,解决方案是更改模式上的包装器,而不是使用<Fragment />我们需要使用旧的普通格式<div />
<Fragment />
<div />
Modal.js 渲染方法:
render() { const ModalMarkup = ( <div> <ModalBackdrop show={this.props.show} /> <ModalWrap show={this.props.show} onClick={this.outerClick}> <ModalDialog show={this.props.show}> <ModalContent>{this.props.children}</ModalContent> </ModalDialog> </ModalWrap> </div> ); return ReactDOM.createPortal(ModalMarkup, this.el); }
您可以在此处找到包含所有代码的仓库