web-dev-qa-db-ja.com

jsdom:dispatchEvent / addEventListenerが機能していないようです

概要:

componentWillMountでネイティブDOMイベントをリッスンするReactコンポーネントをテストしようとしています。

私はそのjsdomを見つけています(@8.4.0)イベントのディスパッチとイベントリスナーの追加に関しては、期待どおりに機能しません。

私が抽出できる最も単純なコード:

window.addEventListener('click', () => {
  throw new Error("success")
})

const event = new Event('click')
document.dispatchEvent(event)

throw new Error('failure')

これは「失敗」をスローします。


コンテキスト:

上記が XY問題 になるリスクがあるので、より多くのコンテキストを提供したいと思います。

これは、私がテストしようとしているコンポーネントの抽出/簡略化されたバージョンです。 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>
  }
}

これが私が書き込もうとしているテストです。

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/)
  })
})

完全を期すために、ここに私のtest_helper.js 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]
  }
})

複製の場合:

ここに再現ケースがあります: 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

9
Jesse Buchanan

ここでの問題は、jsdomが提供するdocumentが実際にはEnzymeテストで使用されていないことでした。

EnzymeはReact.TestUtilsからrenderIntoDocumentを使用します。

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);
  },
// ...
}

つまり、すべてのEnzymeテストは、jsdomが提供するdocumentに対して実行されるのではなく、ドキュメントから切り離されたdivノードに対して実行されます。

Enzymeは、静的メソッドにjsdomが提供するdocumentのみを使用します。 getElementByIdなど。DOM要素の保存/操作には使用されません。

この種のテストを行うために、私は実際にReactDOM.renderを呼び出し、DOMメソッドを使用して出力をアサートすることにしました。

5
Jesse Buchanan

イベントをdocumentにディスパッチしているので、デフォルトではバブルアップしないため、windowはイベントを表示しません。 bubblestrueに設定してイベントを作成する必要があります。例:

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);
7
Louis

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

リンク: ReactTestUtils.SimulateはaddEventListenerによるイベントバインドをトリガーできませんか?

成分:

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

テスト:

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