From c1b2a347be05de45a9395ebafb5504bd74ffb593 Mon Sep 17 00:00:00 2001 From: Tim Jacobi Date: Wed, 29 Nov 2017 14:04:18 +0000 Subject: [PATCH] Rewrite ReactTreeTraversal-test.js using public APIs (#11664) * rewrite two phase traversal tests with public APIs * rewrite enter/leave tests * lift render into beforeEach, organise variables * move getLowestCommonAncestor test * remove internal tree traversal test * fix linter errors * move creation of outer nodes into {before,after}Each * explain why getLowestCommonAncestor test was moved * remove unnessecary ARG and ARG2 token these were used for testing the internal API to simulate synthetic events passed to traverseEnterLeave. since we're now dealing with actual synthetic events we can remove them. * run prettier --- .../ResponderEventPlugin-test.internal.js | 86 ++++++ .../ReactTreeTraversal-test.internal.js | 241 --------------- .../src/__tests__/ReactTreeTraversal-test.js | 278 ++++++++++++++++++ 3 files changed, 364 insertions(+), 241 deletions(-) delete mode 100644 packages/react-dom/src/__tests__/ReactTreeTraversal-test.internal.js create mode 100644 packages/react-dom/src/__tests__/ReactTreeTraversal-test.js diff --git a/packages/events/__tests__/ResponderEventPlugin-test.internal.js b/packages/events/__tests__/ResponderEventPlugin-test.internal.js index ea3d7325e6..15cdd32617 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 7afd0987d5..0000000000 --- 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 0000000000..9acb9314fd --- /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); + }); + }); +});