Jsdom: dispatchEvent / addEventListener doesn't seem to work

Summary:

I am trying to test a React component that listens for its own DOM events in its componentWillMount .

I find jsdom ( @8.4.0 ) is not working properly when it comes to dispatching events and adding event listeners.

The simplest bit of code I can extract:

 window.addEventListener('click', () => { throw new Error("success") }) const event = new Event('click') document.dispatchEvent(event) throw new Error('failure') 

This causes a failure.


Context:

Due to the fact that the above problem is XY , I want to provide more context.

Here is an extracted / simplified version of the component I'm trying to verify. You can see how it works in Webpackbin.

 import React from 'react' export default class Example extends React.Component { constructor() { super() this._onDocumentClick = this._onDocumentClick.bind(this) } componentWillMount() { this.setState({ clicked: false }) window.addEventListener('click', this._onDocumentClick) } _onDocumentClick() { const clicked = this.state.clicked || false this.setState({ clicked: !clicked }) } render() { return <p>{JSON.stringify(this.state.clicked)}</p> } } 

Here is the test I'm trying to write.

 import React from 'react' import ReactDOM from 'react-dom' import { mount } from 'enzyme' import Example from '../src/example' describe('test', () => { it('test', () => { const wrapper = mount(<Example />) const event = new Event('click') document.dispatchEvent(event) // at this point, I expect the component to re-render, // with updated state. expect(wrapper.text()).to.match(/true/) }) }) 

Just for completeness, here is my test_helper.js that initializes jsdom:

 import { jsdom } from 'jsdom' import chai from 'chai' const doc = jsdom('<!doctype html><html><body></body></html>') const win = doc.defaultView global.document = doc global.window = win Object.keys(window).forEach((key) => { if (!(key in global)) { global[key] = window[key] } }) 

Playback Case:

I have an example to play here: https://github.com/jbinto/repro-jsdom-events-not-firing

git clone https://github.com/jbinto/repro-jsdom-events-not-firing.git cd repro-jsdom-events-not-firing npm install npm test

+7
javascript testing jsdom
source share
3 answers

The problem was that the jsdom-provided document is not actually used in enzyme tests.

The enzyme uses renderIntoDocument from React.TestUtils .

https://github.com/facebook/react/blob/510155e027d56ce3cf5c890c9939d894528cf007/src/test/ReactTestUtils.js#L85

 { renderIntoDocument: function(instance) { var div = document.createElement('div'); // None of our tests actually require attaching the container to the // DOM, and doing so creates a mess that we rely on test isolation to // clean up, so we're going to stop honoring the name of this method // (and probably rename it eventually) if no problems arise. // document.documentElement.appendChild(div); return ReactDOM.render(instance, div); }, // ... } 

This means that all our Enzyme tests are not executed with the provided jsdom document , but instead in a div node, separated from any document.

The enzyme uses only jsdom-provided document for static methods, for example. getElementById etc. It is not used to store / work with DOM elements.

To run these tests, I resorted to actually calling ReactDOM.render and approving the output using the DOM methods.

+3
source share

You send an event to document , so window will not see it, because by default it will not bubble. You need to create an event with bubbles set to true . Example:

 var jsdom = require("jsdom"); var document = jsdom.jsdom(""); var window = document.defaultView; window.addEventListener('click', function (ev) { console.log('window click', ev.target.constructor.name, ev.currentTarget.constructor.name); }); document.addEventListener('click', function (ev) { console.log('document click', ev.target.constructor.name, ev.currentTarget.constructor.name); }); console.log("not bubbling"); var event = new window.Event("click"); document.dispatchEvent(event); console.log("bubbling"); event = new window.Event("click", {bubbles: true}); document.dispatchEvent(event); 
+6
source share

Code: https://github.com/LVCarnevalli/create-react-app/blob/master/src/components/datepicker

Reference: ReactTestUtils.Simulate cannot trigger event binding with addEventListener?

component:

 componentDidMount() { ReactDOM.findDOMNode(this.datePicker.refs.input).addEventListener("change", (event) => { const value = event.target.value; this.handleChange(Moment(value).toISOString(), value); }); } 

Test:

 it('change empty value date picker', () => { const app = ReactTestUtils.renderIntoDocument(<Datepicker />); const datePicker = ReactDOM.findDOMNode(app.datePicker.refs.input); const value = ""; const event = new Event("change"); datePicker.value = value; datePicker.dispatchEvent(event); expect(app.state.formattedValue).toEqual(value); }); 
0
source share

All Articles