diff --git a/src/renderers/dom/shared/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js b/src/renderers/dom/shared/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js index 1540236b8ed..c9bb7863a93 100644 --- a/src/renderers/dom/shared/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js +++ b/src/renderers/dom/shared/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js @@ -13,6 +13,7 @@ var EnterLeaveEventPlugin; var React; var ReactDOM; var ReactDOMComponentTree; +var ReactTestUtils; describe('EnterLeaveEventPlugin', () => { beforeEach(() => { @@ -20,6 +21,7 @@ describe('EnterLeaveEventPlugin', () => { React = require('react'); ReactDOM = require('react-dom'); + ReactTestUtils = require('react-dom/test-utils'); // TODO: can we express this test with only public API? ReactDOMComponentTree = require('ReactDOMComponentTree'); EnterLeaveEventPlugin = require('EnterLeaveEventPlugin'); @@ -58,4 +60,38 @@ describe('EnterLeaveEventPlugin', () => { expect(enter.target).toBe(div); expect(enter.relatedTarget).toBe(iframe.contentWindow); }); + + // Regression test for https://github.com/facebook/react/issues/10906. + it('should find the common parent after updates', () => { + let parentEnterCalls = 0; + let childEnterCalls = 0; + let parent = null; + class Parent extends React.Component { + render() { + return ( +
parentEnterCalls++} + ref={node => (parent = node)}> + {this.props.showChild && +
childEnterCalls++} />} +
+ ); + } + } + + const div = document.createElement('div'); + ReactDOM.render(, div); + // The issue only reproduced on insertion during the first update. + ReactDOM.render(, div); + + // Enter from parent into the child. + ReactTestUtils.simulateNativeEventOnNode('topMouseOut', parent, { + target: parent, + relatedTarget: parent.firstChild, + }); + + // Entering a child should fire on the child, not on the parent. + expect(childEnterCalls).toBe(1); + expect(parentEnterCalls).toBe(0); + }); }); diff --git a/src/renderers/shared/shared/ReactTreeTraversal.js b/src/renderers/shared/shared/ReactTreeTraversal.js index 8b58d7b10b7..d483881530b 100644 --- a/src/renderers/shared/shared/ReactTreeTraversal.js +++ b/src/renderers/shared/shared/ReactTreeTraversal.js @@ -110,18 +110,38 @@ function traverseTwoPhase(inst, fn, arg) { * "entered" or "left" that element. */ function traverseEnterLeave(from, to, fn, argFrom, argTo) { - var common = from && to ? getLowestCommonAncestor(from, to) : null; - var pathFrom = []; - while (from && from !== common) { + const common = from && to ? getLowestCommonAncestor(from, to) : null; + const pathFrom = []; + while (true) { + if (!from) { + break; + } + if (from === common) { + break; + } + const alternate = from.alternate; + if (alternate !== null && alternate === common) { + break; + } pathFrom.push(from); from = getParent(from); } - var pathTo = []; - while (to && to !== common) { + const pathTo = []; + while (true) { + if (!to) { + break; + } + if (to === common) { + break; + } + const alternate = to.alternate; + if (alternate !== null && alternate === common) { + break; + } pathTo.push(to); to = getParent(to); } - var i; + let i; for (i = 0; i < pathFrom.length; i++) { fn(pathFrom[i], 'bubbled', argFrom); }