diff --git a/packages/react-dom/src/__tests__/ReactDOMEventListener-test.js b/packages/react-dom/src/__tests__/ReactDOMEventListener-test.js index 0e04aac94e369..c1d2fd6758054 100644 --- a/packages/react-dom/src/__tests__/ReactDOMEventListener-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMEventListener-test.js @@ -9,184 +9,183 @@ 'use strict'; -var EVENT_TARGET_PARAM = 1; - describe('ReactDOMEventListener', () => { var React; var ReactDOM; - var ReactDOMComponentTree; - var ReactDOMEventListener; - var ReactTestUtils; - var handleTopLevel; beforeEach(() => { jest.resetModules(); React = require('react'); ReactDOM = require('react-dom'); - // TODO: can we express this test with only public API? - ReactDOMComponentTree = require('../client/ReactDOMComponentTree').default; - ReactDOMEventListener = require('../events/ReactDOMEventListener').default; - ReactTestUtils = require('react-dom/test-utils'); - - handleTopLevel = jest.fn(); - ReactDOMEventListener._handleTopLevel = handleTopLevel; }); it('should dispatch events from outside React tree', () => { + var mock = jest.fn(); + + var container = document.createElement('div'); + var node = ReactDOM.render(
, container); var otherNode = document.createElement('h1'); - var component = ReactDOM.render(
, document.createElement('div')); - expect(handleTopLevel.mock.calls.length).toBe(0); - ReactDOMEventListener.dispatchEvent('topMouseOut', { - type: 'mouseout', - fromElement: otherNode, - target: otherNode, - srcElement: otherNode, - toElement: ReactDOM.findDOMNode(component), - relatedTarget: ReactDOM.findDOMNode(component), - view: window, - path: [otherNode, otherNode], - }); - expect(handleTopLevel.mock.calls.length).toBe(1); + document.body.appendChild(container); + document.body.appendChild(otherNode); + + otherNode.dispatchEvent( + new MouseEvent('mouseout', { + bubbles: true, + cancelable: true, + relatedTarget: node, + }), + ); + expect(mock).toBeCalled(); }); describe('Propagation', () => { it('should propagate events one level down', () => { + var mouseOut = jest.fn(); + var onMouseOut = event => mouseOut(event.currentTarget); + var childContainer = document.createElement('div'); - var childControl =
Child
; var parentContainer = document.createElement('div'); - var parentControl =
Parent
; - childControl = ReactDOM.render(childControl, childContainer); - parentControl = ReactDOM.render(parentControl, parentContainer); - ReactDOM.findDOMNode(parentControl).appendChild(childContainer); - - var callback = ReactDOMEventListener.dispatchEvent.bind(null, 'test'); - callback({ - target: ReactDOM.findDOMNode(childControl), - }); - - var calls = handleTopLevel.mock.calls; - expect(calls.length).toBe(2); - expect(calls[0][EVENT_TARGET_PARAM]).toBe( - ReactDOMComponentTree.getInstanceFromNode(childControl), + var childNode = ReactDOM.render( +
Child
, + childContainer, ); - expect(calls[1][EVENT_TARGET_PARAM]).toBe( - ReactDOMComponentTree.getInstanceFromNode(parentControl), + var parentNode = ReactDOM.render( +
div
, + parentContainer, ); + parentNode.appendChild(childContainer); + document.body.appendChild(parentContainer); + + var nativeEvent = document.createEvent('Event'); + nativeEvent.initEvent('mouseout', true, true); + childNode.dispatchEvent(nativeEvent); + + expect(mouseOut).toBeCalled(); + expect(mouseOut.mock.calls.length).toBe(2); + expect(mouseOut.mock.calls[0][0]).toEqual(childNode); + expect(mouseOut.mock.calls[1][0]).toEqual(parentNode); + + document.body.removeChild(parentContainer); }); it('should propagate events two levels down', () => { + var mouseOut = jest.fn(); + var onMouseOut = event => mouseOut(event.currentTarget); + var childContainer = document.createElement('div'); - var childControl =
Child
; var parentContainer = document.createElement('div'); - var parentControl =
Parent
; var grandParentContainer = document.createElement('div'); - var grandParentControl =
Parent
; - childControl = ReactDOM.render(childControl, childContainer); - parentControl = ReactDOM.render(parentControl, parentContainer); - grandParentControl = ReactDOM.render( - grandParentControl, - grandParentContainer, - ); - ReactDOM.findDOMNode(parentControl).appendChild(childContainer); - ReactDOM.findDOMNode(grandParentControl).appendChild(parentContainer); - - var callback = ReactDOMEventListener.dispatchEvent.bind(null, 'test'); - callback({ - target: ReactDOM.findDOMNode(childControl), - }); - - var calls = handleTopLevel.mock.calls; - expect(calls.length).toBe(3); - expect(calls[0][EVENT_TARGET_PARAM]).toBe( - ReactDOMComponentTree.getInstanceFromNode(childControl), + var childNode = ReactDOM.render( +
Child
, + childContainer, ); - expect(calls[1][EVENT_TARGET_PARAM]).toBe( - ReactDOMComponentTree.getInstanceFromNode(parentControl), + var parentNode = ReactDOM.render( +
Parent
, + parentContainer, ); - expect(calls[2][EVENT_TARGET_PARAM]).toBe( - ReactDOMComponentTree.getInstanceFromNode(grandParentControl), + var grandParentNode = ReactDOM.render( +
Parent
, + grandParentContainer, ); + parentNode.appendChild(childContainer); + grandParentNode.appendChild(parentContainer); + + document.body.appendChild(grandParentContainer); + + var nativeEvent = document.createEvent('Event'); + nativeEvent.initEvent('mouseout', true, true); + childNode.dispatchEvent(nativeEvent); + + expect(mouseOut).toBeCalled(); + expect(mouseOut.mock.calls.length).toBe(3); + expect(mouseOut.mock.calls[0][0]).toEqual(childNode); + expect(mouseOut.mock.calls[1][0]).toEqual(parentNode); + expect(mouseOut.mock.calls[2][0]).toEqual(grandParentNode); + + document.body.removeChild(grandParentContainer); }); + // Regression test for https://github.com/facebook/react/issues/1105 it('should not get confused by disappearing elements', () => { - var childContainer = document.createElement('div'); - var childControl =
Child
; - var parentContainer = document.createElement('div'); - var parentControl =
Parent
; - childControl = ReactDOM.render(childControl, childContainer); - parentControl = ReactDOM.render(parentControl, parentContainer); - ReactDOM.findDOMNode(parentControl).appendChild(childContainer); - - // ReactBrowserEventEmitter.handleTopLevel might remove the - // target from the DOM. Here, we have handleTopLevel remove the - // node when the first event handlers are called; we'll still - // expect to receive a second call for the parent control. - var childNode = ReactDOM.findDOMNode(childControl); - handleTopLevel.mockImplementation(function( - topLevelType, - topLevelTarget, - topLevelTargetID, - nativeEvent, - ) { - if (topLevelTarget === childNode) { - ReactDOM.unmountComponentAtNode(childContainer); + var container = document.createElement('div'); + document.body.appendChild(container); + class MyComponent extends React.Component { + state = {clicked: false}; + handleClick = () => { + this.setState({clicked: true}); + }; + componentDidMount() { + expect(ReactDOM.findDOMNode(this)).toBe(container.firstChild); } - }); - - var callback = ReactDOMEventListener.dispatchEvent.bind(null, 'test'); - callback({ - target: childNode, - }); - - var calls = handleTopLevel.mock.calls; - expect(calls.length).toBe(2); - expect(calls[0][EVENT_TARGET_PARAM]).toBe( - ReactDOMComponentTree.getInstanceFromNode(childNode), - ); - expect(calls[1][EVENT_TARGET_PARAM]).toBe( - ReactDOMComponentTree.getInstanceFromNode(parentControl), + componentDidUpdate() { + expect(ReactDOM.findDOMNode(this)).toBe(container.firstChild); + } + render() { + if (this.state.clicked) { + return clicked!; + } else { + return ; + } + } + } + ReactDOM.render(, container); + container.firstChild.dispatchEvent( + new MouseEvent('click', { + bubbles: true, + }), ); + expect(container.firstChild.textContent).toBe('clicked!'); + document.body.removeChild(container); }); it('should batch between handlers from different roots', () => { + var mock = jest.fn(); + var childContainer = document.createElement('div'); + var handleChildMouseOut = () => { + ReactDOM.render(
1
, childContainer); + mock(childNode.textContent); + }; + var parentContainer = document.createElement('div'); - var childControl = ReactDOM.render(
Child
, childContainer); - var parentControl = ReactDOM.render(
Parent
, parentContainer); - ReactDOM.findDOMNode(parentControl).appendChild(childContainer); - - // Suppose an event handler in each root enqueues an update to the - // childControl element -- the two updates should get batched together. - var childNode = ReactDOM.findDOMNode(childControl); - handleTopLevel.mockImplementation(function( - topLevelType, - topLevelTarget, - topLevelTargetID, - nativeEvent, - ) { - ReactDOM.render( -
{topLevelTarget === childNode ? '1' : '2'}
, - childContainer, - ); - // Since we're batching, neither update should yet have gone through. - expect(childNode.textContent).toBe('Child'); - }); - - var callback = ReactDOMEventListener.dispatchEvent.bind( - ReactDOMEventListener, - 'test', - ); - callback({ - target: childNode, - }); + var handleParentMouseOut = () => { + ReactDOM.render(
2
, childContainer); + mock(childNode.textContent); + }; - var calls = handleTopLevel.mock.calls; - expect(calls.length).toBe(2); + var childNode = ReactDOM.render( +
Child
, + childContainer, + ); + var parentNode = ReactDOM.render( +
Parent
, + parentContainer, + ); + parentNode.appendChild(childContainer); + document.body.appendChild(parentContainer); + + var nativeEvent = document.createEvent('Event'); + nativeEvent.initEvent('mouseout', true, true); + childNode.dispatchEvent(nativeEvent); + + // Child and parent should both call from event handlers. + expect(mock.mock.calls.length).toBe(2); + // The first call schedules a render of '1' into the 'Child'. + // However, we're batching so it isn't flushed yet. + expect(mock.mock.calls[0][0]).toBe('Child'); + // The first call schedules a render of '2' into the 'Child'. + // We're still batching so it isn't flushed yet either. + expect(mock.mock.calls[1][0]).toBe('Child'); + // By the time we leave the handler, the second update is flushed. expect(childNode.textContent).toBe('2'); + document.body.removeChild(parentContainer); }); }); it('should not fire duplicate events for a React DOM tree', () => { + var mouseOut = jest.fn(); + var onMouseOut = event => mouseOut(event.target); + class Wrapper extends React.Component { getInner = () => { return this.refs.inner; @@ -194,21 +193,22 @@ describe('ReactDOMEventListener', () => { render() { var inner =
Inner
; - return
{inner}
; + return
{inner}
; } } - var instance = ReactTestUtils.renderIntoDocument(); + var container = document.createElement('div'); + var instance = ReactDOM.render(, container); - var callback = ReactDOMEventListener.dispatchEvent.bind(null, 'test'); - callback({ - target: ReactDOM.findDOMNode(instance.getInner()), - }); + document.body.appendChild(container); - var calls = handleTopLevel.mock.calls; - expect(calls.length).toBe(1); - expect(calls[0][EVENT_TARGET_PARAM]).toBe( - ReactDOMComponentTree.getInstanceFromNode(instance.getInner()), - ); + var nativeEvent = document.createEvent('Event'); + nativeEvent.initEvent('mouseout', true, true); + instance.getInner().dispatchEvent(nativeEvent); + + expect(mouseOut).toBeCalled(); + expect(mouseOut.mock.calls.length).toBe(1); + expect(mouseOut.mock.calls[0][0]).toEqual(instance.getInner()); + document.body.removeChild(container); }); });