@samitouri / QOS-React / commits / 80596c2c92

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.

Dan Abramov committed Oct 9, 2017 at 20:30 UTC 80596c2c92dce264f8572038730ce7aef647c5fa
2 files changed +62 -6
src/renderers/dom/shared/eventPlugins/__tests__/EnterLeaveEventPlugin-test.js
+36
@@ -13,6 +13,7 @@ var EnterLeaveEventPlugin;
13 var React;
14 var ReactDOM;
15 var ReactDOMComponentTree;
16 +var ReactTestUtils;
17
18 describe('EnterLeaveEventPlugin', () => {
19 beforeEach(() => {
@@ -20,6 +21,7 @@ describe('EnterLeaveEventPlugin', () => {
21
22 React = require('react');
23 ReactDOM = require('react-dom');
24 + ReactTestUtils = require('react-dom/test-utils');
25 // TODO: can we express this test with only public API?
26 ReactDOMComponentTree = require('ReactDOMComponentTree');
27 EnterLeaveEventPlugin = require('EnterLeaveEventPlugin');
@@ -58,4 +60,38 @@ describe('EnterLeaveEventPlugin', () => {
60 expect(enter.target).toBe(div);
61 expect(enter.relatedTarget).toBe(iframe.contentWindow);
62 });
63 +
64 + // Regression test for https://github.com/facebook/react/issues/10906.
65 + it('should find the common parent after updates', () => {
66 + let parentEnterCalls = 0;
67 + let childEnterCalls = 0;
68 + let parent = null;
69 + class Parent extends React.Component {
70 + render() {
71 + return (
72 + <div
73 + onMouseEnter={() => parentEnterCalls++}
74 + ref={node => (parent = node)}>
75 + {this.props.showChild &&
76 + <div onMouseEnter={() => childEnterCalls++} />}
77 + </div>
78 + );
79 + }
80 + }
81 +
82 + const div = document.createElement('div');
83 + ReactDOM.render(<Parent />, div);
84 + // The issue only reproduced on insertion during the first update.
85 + ReactDOM.render(<Parent showChild={true} />, div);
86 +
87 + // Enter from parent into the child.
88 + ReactTestUtils.simulateNativeEventOnNode('topMouseOut', parent, {
89 + target: parent,
90 + relatedTarget: parent.firstChild,
91 + });
92 +
93 + // Entering a child should fire on the child, not on the parent.
94 + expect(childEnterCalls).toBe(1);
95 + expect(parentEnterCalls).toBe(0);
96 + });
97 });
src/renderers/shared/shared/ReactTreeTraversal.js
+26 -6
@@ -110,18 +110,38 @@ function traverseTwoPhase(inst, fn, arg) {
110 * "entered" or "left" that element.
111 */
112 function traverseEnterLeave(from, to, fn, argFrom, argTo) {
113 - var common = from && to ? getLowestCommonAncestor(from, to) : null;
114 - var pathFrom = [];
115 - while (from && from !== common) {
113 + const common = from && to ? getLowestCommonAncestor(from, to) : null;
114 + const pathFrom = [];
115 + while (true) {
116 + if (!from) {
117 + break;
118 + }
119 + if (from === common) {
120 + break;
121 + }
122 + const alternate = from.alternate;
123 + if (alternate !== null && alternate === common) {
124 + break;
125 + }
126 pathFrom.push(from);
127 from = getParent(from);
128 }
119 - var pathTo = [];
120 - while (to && to !== common) {
129 + const pathTo = [];
130 + while (true) {
131 + if (!to) {
132 + break;
133 + }
134 + if (to === common) {
135 + break;
136 + }
137 + const alternate = to.alternate;
138 + if (alternate !== null && alternate === common) {
139 + break;
140 + }
141 pathTo.push(to);
142 to = getParent(to);
143 }
124 - var i;
144 + let i;
145 for (i = 0; i < pathFrom.length; i++) {
146 fn(pathFrom[i], 'bubbled', argFrom);
147 }