Fix incorrect calculation of onMouseEnter/Leave component path (#11164)

* Add a regression test for #10906

* Turn while conditions into breaks without changing the logic

This will be easier to follow when we add more code there.

* var => const/let

So that I can add a block scoped variable.

* Check alternates when comparing to the common ancestor

This is the actual bugfix.
This commit is contained in:
Dan Abramov
2017-10-09 20:30:03 +01:00
committed by GitHub
parent 08cbc257bd
commit 80596c2c92
2 changed files with 62 additions and 6 deletions
@@ -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 (
<div
onMouseEnter={() => parentEnterCalls++}
ref={node => (parent = node)}>
{this.props.showChild &&
<div onMouseEnter={() => childEnterCalls++} />}
</div>
);
}
}
const div = document.createElement('div');
ReactDOM.render(<Parent />, div);
// The issue only reproduced on insertion during the first update.
ReactDOM.render(<Parent showChild={true} />, 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);
});
});
@@ -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);
}