概要:
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
ここでの問題は、jsdomが提供するdocument
が実際にはEnzymeテストで使用されていないことでした。
EnzymeはReact.TestUtils
からrenderIntoDocument
を使用します。
{
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メソッドを使用して出力をアサートすることにしました。
イベントをdocument
にディスパッチしているので、デフォルトではバブルアップしないため、window
はイベントを表示しません。 bubbles
をtrue
に設定してイベントを作成する必要があります。例:
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);
コード: 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);
});