diff --git a/packages/events/__tests__/ResponderEventPlugin-test.internal.js b/packages/events/__tests__/ResponderEventPlugin-test.internal.js index ea3d7325e6d58..15cdd32617034 100644 --- a/packages/events/__tests__/ResponderEventPlugin-test.internal.js +++ b/packages/events/__tests__/ResponderEventPlugin-test.internal.js @@ -1360,4 +1360,90 @@ describe('ResponderEventPlugin', () => { run(config, three, nativeEvent); expect(ResponderEventPlugin._getResponder()).toBe(null); }); + + it('should determine the first common ancestor correctly', () => { + // This test was moved here from the ReactTreeTraversal test since only the + // ResponderEventPlugin uses `getLowestCommonAncestor` + var React = require('react'); + var ReactTestUtils = require('react-dom/test-utils'); + var ReactTreeTraversal = require('shared/ReactTreeTraversal'); + var ReactDOMComponentTree = require('../../react-dom/src/client/ReactDOMComponentTree'); + + class ChildComponent extends React.Component { + render() { + return ( +
+
+
+
+ ); + } + } + + class ParentComponent extends React.Component { + render() { + return ( +
+
+ + +
+
+
+ ); + } + } + + var parent = ReactTestUtils.renderIntoDocument(); + + var ancestors = [ + // Common ancestor with self is self. + { + one: parent.refs.P_P1_C1.refs.DIV_1, + two: parent.refs.P_P1_C1.refs.DIV_1, + com: parent.refs.P_P1_C1.refs.DIV_1, + }, + // Common ancestor with self is self - even if topmost DOM. + {one: parent.refs.P, two: parent.refs.P, com: parent.refs.P}, + // Siblings + { + one: parent.refs.P_P1_C1.refs.DIV_1, + two: parent.refs.P_P1_C1.refs.DIV_2, + com: parent.refs.P_P1_C1.refs.DIV, + }, + // Common ancestor with parent is the parent. + { + one: parent.refs.P_P1_C1.refs.DIV_1, + two: parent.refs.P_P1_C1.refs.DIV, + com: parent.refs.P_P1_C1.refs.DIV, + }, + // Common ancestor with grandparent is the grandparent. + { + one: parent.refs.P_P1_C1.refs.DIV_1, + two: parent.refs.P_P1, + com: parent.refs.P_P1, + }, + // Grandparent across subcomponent boundaries. + { + one: parent.refs.P_P1_C1.refs.DIV_1, + two: parent.refs.P_P1_C2.refs.DIV_1, + com: parent.refs.P_P1, + }, + // Something deep with something one-off. + { + one: parent.refs.P_P1_C1.refs.DIV_1, + two: parent.refs.P_OneOff, + com: parent.refs.P, + }, + ]; + var i; + for (i = 0; i < ancestors.length; i++) { + var plan = ancestors[i]; + var firstCommon = ReactTreeTraversal.getLowestCommonAncestor( + ReactDOMComponentTree.getInstanceFromNode(plan.one), + ReactDOMComponentTree.getInstanceFromNode(plan.two), + ); + expect(firstCommon).toBe(ReactDOMComponentTree.getInstanceFromNode(plan.com)); + } + }); }); diff --git a/packages/react-dom/src/__tests__/ReactTreeTraversal-test.internal.js b/packages/react-dom/src/__tests__/ReactTreeTraversal-test.internal.js deleted file mode 100644 index 7afd0987d590c..0000000000000 --- a/packages/react-dom/src/__tests__/ReactTreeTraversal-test.internal.js +++ /dev/null @@ -1,241 +0,0 @@ -/** - * Copyright (c) 2013-present, Facebook, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @emails react-core - */ - -'use strict'; - -var React = require('react'); -// TODO: can we express this test with only public API? -var ReactDOMComponentTree = require('../client/ReactDOMComponentTree'); -var ReactTestUtils = require('react-dom/test-utils'); - -/** - * Ensure that all callbacks are invoked, passing this unique argument. - */ -var ARG = {arg: true}; -var ARG2 = {arg2: true}; - -class ChildComponent extends React.Component { - render() { - return ( -
-
-
-
- ); - } -} - -class ParentComponent extends React.Component { - render() { - return ( -
-
- - -
-
-
- ); - } -} - -function renderParentIntoDocument() { - return ReactTestUtils.renderIntoDocument(); -} - -describe('ReactTreeTraversal', () => { - var ReactTreeTraversal; - var mockFn = jest.fn(); - - function callback(inst, phase, arg) { - mockFn(ReactDOMComponentTree.getNodeFromInstance(inst).id, phase, arg); - } - - function getInst(node) { - return ReactDOMComponentTree.getInstanceFromNode(node); - } - - beforeEach(() => { - ReactTreeTraversal = require('shared/ReactTreeTraversal'); - mockFn.mockReset(); - }); - - describe('traverseTwoPhase', () => { - it('should not traverse when traversing outside DOM', () => { - ReactTreeTraversal.traverseTwoPhase(null, callback, ARG); - expect(mockFn).not.toHaveBeenCalled(); - }); - - it('should traverse two phase across component boundary', () => { - var parent = renderParentIntoDocument(); - var target = getInst(parent.refs.P_P1_C1.refs.DIV_1); - var expectedCalls = [ - ['P', 'captured', ARG], - ['P_P1', 'captured', ARG], - ['P_P1_C1__DIV', 'captured', ARG], - ['P_P1_C1__DIV_1', 'captured', ARG], - - ['P_P1_C1__DIV_1', 'bubbled', ARG], - ['P_P1_C1__DIV', 'bubbled', ARG], - ['P_P1', 'bubbled', ARG], - ['P', 'bubbled', ARG], - ]; - ReactTreeTraversal.traverseTwoPhase(target, callback, ARG); - expect(mockFn.mock.calls).toEqual(expectedCalls); - }); - - it('should traverse two phase at shallowest node', () => { - var parent = renderParentIntoDocument(); - var target = getInst(parent.refs.P); - var expectedCalls = [['P', 'captured', ARG], ['P', 'bubbled', ARG]]; - ReactTreeTraversal.traverseTwoPhase(target, callback, ARG); - expect(mockFn.mock.calls).toEqual(expectedCalls); - }); - }); - - describe('traverseEnterLeave', () => { - it('should not traverse when enter/leaving outside DOM', () => { - ReactTreeTraversal.traverseEnterLeave(null, null, callback, ARG, ARG2); - expect(mockFn).not.toHaveBeenCalled(); - }); - - it('should not traverse if enter/leave the same node', () => { - var parent = renderParentIntoDocument(); - var leave = getInst(parent.refs.P_P1_C1.refs.DIV_1); - var enter = getInst(parent.refs.P_P1_C1.refs.DIV_1); - ReactTreeTraversal.traverseEnterLeave(leave, enter, callback, ARG, ARG2); - expect(mockFn).not.toHaveBeenCalled(); - }); - - it('should traverse enter/leave to sibling - avoids parent', () => { - var parent = renderParentIntoDocument(); - var leave = getInst(parent.refs.P_P1_C1.refs.DIV_1); - var enter = getInst(parent.refs.P_P1_C1.refs.DIV_2); - var expectedCalls = [ - ['P_P1_C1__DIV_1', 'bubbled', ARG], - // enter/leave shouldn't fire anything on the parent - ['P_P1_C1__DIV_2', 'captured', ARG2], - ]; - ReactTreeTraversal.traverseEnterLeave(leave, enter, callback, ARG, ARG2); - expect(mockFn.mock.calls).toEqual(expectedCalls); - }); - - it('should traverse enter/leave to parent - avoids parent', () => { - var parent = renderParentIntoDocument(); - var leave = getInst(parent.refs.P_P1_C1.refs.DIV_1); - var enter = getInst(parent.refs.P_P1_C1.refs.DIV); - var expectedCalls = [['P_P1_C1__DIV_1', 'bubbled', ARG]]; - ReactTreeTraversal.traverseEnterLeave(leave, enter, callback, ARG, ARG2); - expect(mockFn.mock.calls).toEqual(expectedCalls); - }); - - it('should enter from the window', () => { - var parent = renderParentIntoDocument(); - var leave = null; // From the window or outside of the React sandbox. - var enter = getInst(parent.refs.P_P1_C1.refs.DIV); - var expectedCalls = [ - ['P', 'captured', ARG2], - ['P_P1', 'captured', ARG2], - ['P_P1_C1__DIV', 'captured', ARG2], - ]; - ReactTreeTraversal.traverseEnterLeave(leave, enter, callback, ARG, ARG2); - expect(mockFn.mock.calls).toEqual(expectedCalls); - }); - - it('should enter from the window to the shallowest', () => { - var parent = renderParentIntoDocument(); - var leave = null; // From the window or outside of the React sandbox. - var enter = getInst(parent.refs.P); - var expectedCalls = [['P', 'captured', ARG2]]; - ReactTreeTraversal.traverseEnterLeave(leave, enter, callback, ARG, ARG2); - expect(mockFn.mock.calls).toEqual(expectedCalls); - }); - - it('should leave to the window', () => { - var parent = renderParentIntoDocument(); - var enter = null; // From the window or outside of the React sandbox. - var leave = getInst(parent.refs.P_P1_C1.refs.DIV); - var expectedCalls = [ - ['P_P1_C1__DIV', 'bubbled', ARG], - ['P_P1', 'bubbled', ARG], - ['P', 'bubbled', ARG], - ]; - ReactTreeTraversal.traverseEnterLeave(leave, enter, callback, ARG, ARG2); - expect(mockFn.mock.calls).toEqual(expectedCalls); - }); - - it('should leave to the window from the shallowest', () => { - var parent = renderParentIntoDocument(); - var enter = null; // From the window or outside of the React sandbox. - var leave = getInst(parent.refs.P_P1_C1.refs.DIV); - var expectedCalls = [ - ['P_P1_C1__DIV', 'bubbled', ARG], - ['P_P1', 'bubbled', ARG], - ['P', 'bubbled', ARG], - ]; - ReactTreeTraversal.traverseEnterLeave(leave, enter, callback, ARG, ARG2); - expect(mockFn.mock.calls).toEqual(expectedCalls); - }); - }); - - describe('getFirstCommonAncestor', () => { - it('should determine the first common ancestor correctly', () => { - var parent = renderParentIntoDocument(); - var ancestors = [ - // Common ancestor with self is self. - { - one: parent.refs.P_P1_C1.refs.DIV_1, - two: parent.refs.P_P1_C1.refs.DIV_1, - com: parent.refs.P_P1_C1.refs.DIV_1, - }, - // Common ancestor with self is self - even if topmost DOM. - {one: parent.refs.P, two: parent.refs.P, com: parent.refs.P}, - // Siblings - { - one: parent.refs.P_P1_C1.refs.DIV_1, - two: parent.refs.P_P1_C1.refs.DIV_2, - com: parent.refs.P_P1_C1.refs.DIV, - }, - // Common ancestor with parent is the parent. - { - one: parent.refs.P_P1_C1.refs.DIV_1, - two: parent.refs.P_P1_C1.refs.DIV, - com: parent.refs.P_P1_C1.refs.DIV, - }, - // Common ancestor with grandparent is the grandparent. - { - one: parent.refs.P_P1_C1.refs.DIV_1, - two: parent.refs.P_P1, - com: parent.refs.P_P1, - }, - // Grandparent across subcomponent boundaries. - { - one: parent.refs.P_P1_C1.refs.DIV_1, - two: parent.refs.P_P1_C2.refs.DIV_1, - com: parent.refs.P_P1, - }, - // Something deep with something one-off. - { - one: parent.refs.P_P1_C1.refs.DIV_1, - two: parent.refs.P_OneOff, - com: parent.refs.P, - }, - ]; - var i; - for (i = 0; i < ancestors.length; i++) { - var plan = ancestors[i]; - var firstCommon = ReactTreeTraversal.getLowestCommonAncestor( - getInst(plan.one), - getInst(plan.two), - ); - expect(firstCommon).toBe(getInst(plan.com)); - } - }); - }); -}); diff --git a/packages/react-dom/src/__tests__/ReactTreeTraversal-test.js b/packages/react-dom/src/__tests__/ReactTreeTraversal-test.js new file mode 100644 index 0000000000000..9acb9314fd192 --- /dev/null +++ b/packages/react-dom/src/__tests__/ReactTreeTraversal-test.js @@ -0,0 +1,278 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ + +'use strict'; + +var React; +var ReactDOM; + +const ChildComponent = ({id, eventHandler}) => ( +
eventHandler(e.currentTarget.id, 'captured', e.type)} + onClick={e => eventHandler(e.currentTarget.id, 'bubbled', e.type)} + onMouseEnter={e => eventHandler(e.currentTarget.id, e.type)} + onMouseLeave={e => eventHandler(e.currentTarget.id, e.type)}> +
eventHandler(e.currentTarget.id, 'captured', e.type)} + onClick={e => eventHandler(e.currentTarget.id, 'bubbled', e.type)} + onMouseEnter={e => eventHandler(e.currentTarget.id, e.type)} + onMouseLeave={e => eventHandler(e.currentTarget.id, e.type)} + /> +
eventHandler(e.currentTarget.id, 'captured', e.type)} + onClick={e => eventHandler(e.currentTarget.id, 'bubbled', e.type)} + onMouseEnter={e => eventHandler(e.currentTarget.id, e.type)} + onMouseLeave={e => eventHandler(e.currentTarget.id, e.type)} + /> +
+); + +const ParentComponent = ({eventHandler}) => ( +
eventHandler(e.currentTarget.id, 'captured', e.type)} + onClick={e => eventHandler(e.currentTarget.id, 'bubbled', e.type)} + onMouseEnter={e => eventHandler(e.currentTarget.id, e.type)} + onMouseLeave={e => eventHandler(e.currentTarget.id, e.type)}> +
eventHandler(e.currentTarget.id, 'captured', e.type)} + onClick={e => eventHandler(e.currentTarget.id, 'bubbled', e.type)} + onMouseEnter={e => eventHandler(e.currentTarget.id, e.type)} + onMouseLeave={e => eventHandler(e.currentTarget.id, e.type)}> + + +
+
eventHandler(e.currentTarget.id, 'captured', e.type)} + onClick={e => eventHandler(e.currentTarget.id, 'bubbled', e.type)} + onMouseEnter={e => eventHandler(e.currentTarget.id, e.type)} + onMouseLeave={e => eventHandler(e.currentTarget.id, e.type)} + /> +
+); + +describe('ReactTreeTraversal', () => { + var mockFn = jest.fn(); + var container; + var outerNode1; + var outerNode2; + + beforeEach(() => { + React = require('react'); + ReactDOM = require('react-dom'); + + mockFn.mockReset(); + + container = document.createElement('div'); + outerNode1 = document.createElement('div'); + outerNode2 = document.createElement('div'); + document.body.appendChild(container); + document.body.appendChild(outerNode1); + document.body.appendChild(outerNode2); + + ReactDOM.render(, container); + }); + + afterEach(() => { + document.body.removeChild(container); + document.body.removeChild(outerNode1); + document.body.removeChild(outerNode2); + container = null; + outerNode1 = null; + outerNode2 = null; + }); + + describe('Two phase traversal', () => { + it('should not traverse when target is outside component boundary', () => { + outerNode1.dispatchEvent( + new MouseEvent('click', {bubbles: true, cancelable: true}), + ); + + expect(mockFn).not.toHaveBeenCalled(); + }); + + it('should traverse two phase across component boundary', () => { + var expectedCalls = [ + ['P', 'captured', 'click'], + ['P_P1', 'captured', 'click'], + ['P_P1_C1__DIV', 'captured', 'click'], + ['P_P1_C1__DIV_1', 'captured', 'click'], + + ['P_P1_C1__DIV_1', 'bubbled', 'click'], + ['P_P1_C1__DIV', 'bubbled', 'click'], + ['P_P1', 'bubbled', 'click'], + ['P', 'bubbled', 'click'], + ]; + + var node = document.getElementById('P_P1_C1__DIV_1'); + node.dispatchEvent( + new MouseEvent('click', {bubbles: true, cancelable: true}), + ); + + expect(mockFn.mock.calls).toEqual(expectedCalls); + }); + + it('should traverse two phase at shallowest node', () => { + var node = document.getElementById('P'); + node.dispatchEvent( + new MouseEvent('click', {bubbles: true, cancelable: true}), + ); + + var expectedCalls = [ + ['P', 'captured', 'click'], + ['P', 'bubbled', 'click'], + ]; + expect(mockFn.mock.calls).toEqual(expectedCalls); + }); + }); + + describe('Enter leave traversal', () => { + it('should not traverse when enter/leaving outside DOM', () => { + outerNode1.dispatchEvent( + new MouseEvent('mouseout', { + bubbles: true, + cancelable: true, + relatedTarget: outerNode2, + }), + ); + + expect(mockFn).not.toHaveBeenCalled(); + }); + + it('should not traverse if enter/leave the same node', () => { + var leaveNode = document.getElementById('P_P1_C1__DIV_1'); + var enterNode = document.getElementById('P_P1_C1__DIV_1'); + + leaveNode.dispatchEvent( + new MouseEvent('mouseout', { + bubbles: true, + cancelable: true, + relatedTarget: enterNode, + }), + ); + + expect(mockFn).not.toHaveBeenCalled(); + }); + + it('should traverse enter/leave to sibling - avoids parent', () => { + var leaveNode = document.getElementById('P_P1_C1__DIV_1'); + var enterNode = document.getElementById('P_P1_C1__DIV_2'); + + var expectedCalls = [ + ['P_P1_C1__DIV_1', 'mouseleave'], + // enter/leave shouldn't fire anything on the parent + ['P_P1_C1__DIV_2', 'mouseenter'], + ]; + + leaveNode.dispatchEvent( + new MouseEvent('mouseout', { + bubbles: true, + cancelable: true, + relatedTarget: enterNode, + }), + ); + + expect(mockFn.mock.calls).toEqual(expectedCalls); + }); + + it('should traverse enter/leave to parent - avoids parent', () => { + var leaveNode = document.getElementById('P_P1_C1__DIV_1'); + var enterNode = document.getElementById('P_P1_C1__DIV'); + + var expectedCalls = [['P_P1_C1__DIV_1', 'mouseleave']]; + + leaveNode.dispatchEvent( + new MouseEvent('mouseout', { + bubbles: true, + cancelable: true, + relatedTarget: enterNode, + }), + ); + + expect(mockFn.mock.calls).toEqual(expectedCalls); + }); + + it('should enter from the window', () => { + var enterNode = document.getElementById('P_P1_C1__DIV'); + + var expectedCalls = [ + ['P', 'mouseenter'], + ['P_P1', 'mouseenter'], + ['P_P1_C1__DIV', 'mouseenter'], + ]; + + outerNode1.dispatchEvent( + new MouseEvent('mouseout', { + bubbles: true, + cancelable: true, + relatedTarget: enterNode, + }), + ); + + expect(mockFn.mock.calls).toEqual(expectedCalls); + }); + + it('should enter from the window to the shallowest', () => { + var enterNode = document.getElementById('P'); + + var expectedCalls = [['P', 'mouseenter']]; + + outerNode1.dispatchEvent( + new MouseEvent('mouseout', { + bubbles: true, + cancelable: true, + relatedTarget: enterNode, + }), + ); + + expect(mockFn.mock.calls).toEqual(expectedCalls); + }); + + it('should leave to the window', () => { + var leaveNode = document.getElementById('P_P1_C1__DIV'); + + var expectedCalls = [ + ['P_P1_C1__DIV', 'mouseleave'], + ['P_P1', 'mouseleave'], + ['P', 'mouseleave'], + ]; + + leaveNode.dispatchEvent( + new MouseEvent('mouseout', { + bubbles: true, + cancelable: true, + relatedTarget: outerNode1, + }), + ); + + expect(mockFn.mock.calls).toEqual(expectedCalls); + }); + + it('should leave to the window from the shallowest', () => { + var leaveNode = document.getElementById('P'); + + var expectedCalls = [['P', 'mouseleave']]; + + leaveNode.dispatchEvent( + new MouseEvent('mouseout', { + bubbles: true, + cancelable: true, + relatedTarget: outerNode1, + }), + ); + + expect(mockFn.mock.calls).toEqual(expectedCalls); + }); + }); +});