Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modals cannot be tested with React's TestUtil #71

Closed
baer opened this issue Sep 25, 2015 · 29 comments
Closed

Modals cannot be tested with React's TestUtil #71

baer opened this issue Sep 25, 2015 · 29 comments

Comments

@baer
Copy link

baer commented Sep 25, 2015

react-modal is acting outside of the purview of any of React's test utilities.

renderedModal.getDomNode()
// => <noscript data-reactid=".0"></noscript>

React.scryRenderedDOMComponentsWithClass(renderedModal, "my-modal-class");
// => []

I see there are some work arounds that were developed circa React 0.12 but I can't figure out why that is necessary. If I was guessing I would say that this has to do with how you mount to the DOM to properly apply the overlay. If this is true, maybe a small wrapper around those helpers as a part of the release would be a helpful thing for us app devs who want to write lots of tests :)

https://github.com/rackt/react-modal/blob/master/specs/helper.js

@dviramontes
Copy link

Having trouble testing this as well.
👍 thanks for the pointer @baer !

@jkimbo
Copy link

jkimbo commented Oct 22, 2015

@baer I guess this is because the modal creates a "portal" by mounting the actual modal in a new div at the bottom of the page. You can see in the Modal.js component the render method is not actually returning anything: https://github.com/rackt/react-modal/blob/1ff6a02c689e9fdc03616c4ab82ec53317f14c69/lib/components/Modal.js#L74

To get around this you need to access the .portal property (https://github.com/rackt/react-modal/blob/1ff6a02c689e9fdc03616c4ab82ec53317f14c69/lib/components/Modal.js#L70) on the renderedModal to access the correct React tree to run the TestUtils on. So React.scryRenderedDOMComponentsWithClass(renderedModal.portal, "my-modal-class"); should work.

Hope that makes sense!

@ArthurCideron
Copy link

@jkimbo that sounded promising, unfortunately that didn't do the trick for me. I get an undefined on renderedModal.portal. Do you get the React tree on that ?

@jkimbo
Copy link

jkimbo commented Nov 11, 2015

@ArthurCid I've written up a passing test for you using mocha and jsdom: https://gist.github.com/jkimbo/872f42732e6c772bf8e1

@gbrassey
Copy link

I'm still having trouble with this. I've tried react-modal@0.6.1 w/ react@0.14.2 and react-modal@0.5.0 w/ react@0.13.3. Testing with karma, karma-chrome and mocha.

the .portal property exists and renders but it appears to have no children

import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
import { expect } from 'chai';

import Modal from 'react-modal';

describe('component - common - Modal', () => {
  it('renders without problems', () => {
    const renderedModal = TestUtils.renderIntoDocument(
      <Modal>
        <div className="my-modal-class">
          <h1>Hi!</h1>
        </div>
      </Modal>
    );

    // passing
    expect(ReactDOM.findDOMNode(renderedModal.portal).tagName).to.equal('DIV');
    expect(ReactDOM.findDOMNode(renderedModal.portal).children).to.be.empty;

    const divs = TestUtils.scryRenderedDOMComponentsWithClass(renderedModal.portal, 'my-modal-class');
    // fails
    expect(divs.length).to.equal(1);
  });
});

@jkimbo
Copy link

jkimbo commented Nov 12, 2015

@gbrassey you need to set isOpen={true} on the modal component for it to render it's children.

@gbrassey
Copy link

wow thanks @jkimbo 😵 🔫

@ArthurCideron
Copy link

@jkimbo I figured out my issue. You example worked fine so it was indeed an issue on my side.
I was rendering from an external module like

var  renderedModal = TestUtils.renderIntoDocument(
      <MyModule
           props1:{stubblabla}
      />
    );

and calling MyModule.portal, which was obviously wrong, I needed the Modal.portal
So for whoever is in trouble like I was: remember to get the Modal component !

TestUtils.findRenderedComponentWithType(component, Modal);

@jkimbo
Copy link

jkimbo commented Nov 23, 2015

@ArthurCid glad you figured it out!

@claydiffrient
Copy link
Contributor

There hasn't been much traction on this issue in over a year. I'm assuming that this is no longer an issue, especially given that #136 was merged. If it is, please feel free to re-open.

@begincalendar
Copy link

I know this issue is about React test utils, but is it possible to make testing work with Enzyme.js?

Enzyme.js provides a simpler/cleaner API over React test utils (in my opinion) and it would be handy to be able to utilise that when testing code regarding react-modal.

@claydiffrient
Copy link
Contributor

@begincalendar Is there a specific issue with using Enzyme? In theory it should "just work" assuming the notes about testing from the read me are followed. If there's a specific issue where it fails, please file an issue for it

@begincalendar
Copy link

There is no issue per se, it's more of that the React test utils give you DOM elements, whereas Enzyme gives you wrappers and pseudo elements.

I.e. The whole library is designed to work with these wrappers/pseudoelements, e.g. for making simulate() calls.
So getting back DOM elements from React test utils, means that Enzyme can't really be utilised completely.

@claydiffrient
Copy link
Contributor

Right. I actually really like Enzyme and have used it frequently in other projects. I don't see any reason that you couldn't use Enzyme when testing a component that uses react-modal.

@begincalendar
Copy link

But you need to extract the portal property to get access to child elements, right?

As far as I'm aware, with Enzyme, once you take out the reference to the portal property, you can't then go do things such as: render the child elements, render the text of the child elements, find sub-child elements, etc.

Am I missing something?

@claydiffrient
Copy link
Contributor

Hmmm... I'd have to put together an example of this to be sure. I don't have time at the moment, but I'll see if I can't put something together soon.

@cool-acid
Copy link

I'm also interested on how to test with Enzyme. I've been struggling with this for a while.

@oliversong
Copy link

oliversong commented Mar 3, 2017

Bump. We're currently struggling this as well. Has anyone figured out a reasonable pattern for testing with enzyme?

@pzmudzinski
Copy link

Anyone.. ?

@asborisov
Copy link

#291

@bethcodes
Copy link

With enzyme, shallow supports portal components while mount does not. If you absolutely must use mount, you can use ref={(ref) => { this.namedRef = ref; } in order to access the element and then wrap it in an enzyme ReactWrapper.

@Cory-Christensen
Copy link

Cory-Christensen commented May 18, 2017

Here's an example where I used enzyme to test a component with react-modal. The modal is used to confirm a user wants to go forward, and tests to see if confirmed() was called.

confirm_modal.jsx

import React from 'react';
import ReactModal from 'react-modal';
import PropTypes from 'prop-types';

export default class ConfirmModal extends React.Component {

  static propTypes = {
    closeModal: PropTypes.func.isRequired,
    isOpen: PropTypes.bool.isRequired,
  }

  confirmed() {
    console.log('PASSED');
  }

  modalContent() {
    return (
      <div>
        <div className="c-modal__main">
          Do you wish to proceed?
        </div>
        <div className="c-modal__bottom">
          <button
            type="button"
            className="c-btn c-btn--gray"
            onClick={this.props.closeModal}
          >
            No
          </button>
          <button
            type="button"
            className="c-btn c-btn--blue"
            onClick={() => this.confirmed()}
          >
            Yes
          </button>
        </div>
      </div>
    );
  }

  render() {

    return (
      <ReactModal
        isOpen={this.props.isOpen}
        onRequestClose={this.props.closeModal}
        contentLabel="Modal"
        overlayClassName="c-modal__background"
        className="c-modal is-open"
      >
        {this.modalContent()}
      </ReactModal>
    );
  }
}

confirm_modal.spec.jsx

import React from 'react';
import { shallow } from 'enzyme';
import ConfirmModal from './confirm_modal';

describe('confirm modal', () => {
  let props;
  beforeEach(() => {
    props = {
      closeModal: () => {},
      isOpen: true,
    };
  });

  it('confirms user wants to proceed', () => {
    spyOn(ConfirmModal.prototype, 'confirmed');
    const wrapper = shallow(<ConfirmModal {...props} />);
    const content = wrapper.instance().modalContent();
    const modalContent = shallow(content);
    modalContent.find('.c-btn--blue').simulate('click');
    expect(wrapper.instance().confirmed).toHaveBeenCalled();
  });

  it('renders modalContent', () => {
    spyOn(ConfirmModal.prototype, 'modalContent');
    const wrapper = shallow(<ConfirmModal {...props} />);
    expect(wrapper.instance().modalContent).toHaveBeenCalled();
  });
});

Hopefully that helps you guys out 👍

@dy-dx
Copy link

dy-dx commented May 31, 2017

This blog post has some examples for testing react-modal with enzyme: http://remarkablemark.org/blog/2017/05/17/testing-react-modal/

@ramusus
Copy link

ramusus commented Nov 14, 2017

@dy-dx thanks for great solution, but it doesn't work with the last enzyme

    "enzyme": "3.1.1",
    "enzyme-adapter-react-15": "1.0.5",

Do you have any workaround?

@techrah
Copy link

techrah commented Nov 17, 2017

@ramusus I too am unable to get this to work with enzyme 3 and enzyme-adapter-react-15. If you've come up with a workaround, please share.

I've tried react-dom/test-utils renderIntoDocument then ReactDOM.findDOMNode and scryRenderedDOMComponentsWithClass as recommended here with little to no success.

I've also tried the examples from the blog post recommended by @dy-dx with limited success. I've been able to get a reference to the portal component but it doesn't seem to contain any HTML. (Yes, I've set isOpen to true.)

I've tried using both shallow and mount from enzyme and despite @bethcodes' experience with shallow, I actually got further with mount. With shallow I had no luck finding any portal.

@ramusus
Copy link

ramusus commented Nov 24, 2017

@ryanhomer, found solution - do not use ReactWrapper or any other hacks - content of modals get rendered into the main wrapper, properly in place where it should be logically (but not in the real DOM).

So everything becomes more simple and easy to test, just not forget to do wrapper.update() ...

@aaronvanston
Copy link

@ramusus Are you able to post an example? I'm still have issues rendering modal content without ReactWrapper.

@ramusus
Copy link

ramusus commented Dec 5, 2017

// ModalContainer.js
import React, { Component } from 'react';
import ReactModal from 'react-modal';

export default class ModalContainer extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isModalOpen: false
    };
    this.toggleModal = this.toggleModal.bind(this);
  }

  toggleModal() {
    this.setState({
      isModalOpen: !this.state.isModalOpen
    });
  }

  render() {
    return (
      <div>
        <button onClick={this.toggleModal}>
          Open Modal
        </button>
        <ReactModal
          {...this.props}
          isOpen={this.state.isModalOpen}
          onRequestClose={this.toggleModal}
        >
          <div>Content</div>
        </ReactModal>
      </div>
    );
  }
}

// ModalContainer.spec.js
import React from 'react';
import ReactModal from 'react-modal';
import ModalContainer from './ModalContainer';
import { mount } from 'enzyme';

describe('<ModalContainer>', () => {
  it('opens modal and render it\'s content when button is clicked', () => {
    const wrapper = mount(<ModalContainer />);
    wrapper.find('button').simulate('click');
    expect(wrapper.find(ReactModal).text()).toBe('Content');
  });
});

@hng0
Copy link

hng0 commented May 16, 2018

@ramusus The example above no longer works for me. I got this error

  1. opens modal and render it's content when button is clicked:
    TypeError: Cannot read property 'textContent' of null
    at ReactWrapper. (node_modules/enzyme/build/ReactWrapper.js:799:43)
    at ReactWrapper.single (node_modules/enzyme/build/ReactWrapper.js:1534:25)
    at ReactWrapper.text (node_modules/enzyme/build/ReactWrapper.js:798:21)
    at Context. (src/a/test.spec.js:14:37)

I'm using react-modal 3.4.4 and enzyme 3.3.0. Is there anything that I miss?
Update: If I shallow render the modal, then its children is rendered.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests