From 9301cfbc804b7eb2a4c4508d28140278cb7538dc Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 17 May 2016 20:50:58 +0100 Subject: [PATCH] Refactor ReactComponentTreeDevtool test We extract common logic between DOM and native tests so they don't diverge. Also, rather than build a tree object in advance for testing, we will walk the tree on the go. This lets us have much more specific error messages with a clear path when something goes wrong. --- .../ReactComponentTreeDevtool-test.js | 113 +++++------------- .../ReactComponentTreeDevtool-test.native.js | 104 +++++----------- src/test/ReactComponentTreeTestUtils.js | 92 ++++++++++++++ 3 files changed, 154 insertions(+), 155 deletions(-) create mode 100644 src/test/ReactComponentTreeTestUtils.js diff --git a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js index ed685741e6..203cfcc02d 100644 --- a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js +++ b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js @@ -17,6 +17,7 @@ describe('ReactComponentTreeDevtool', () => { var ReactDOMServer; var ReactInstanceMap; var ReactComponentTreeDevtool; + var ReactComponentTreeTestUtils; beforeEach(() => { jest.resetModuleRegistry(); @@ -26,54 +27,10 @@ describe('ReactComponentTreeDevtool', () => { ReactDOMServer = require('ReactDOMServer'); ReactInstanceMap = require('ReactInstanceMap'); ReactComponentTreeDevtool = require('ReactComponentTreeDevtool'); + ReactComponentTreeTestUtils = require('ReactComponentTreeTestUtils'); }); - function getRootDisplayNames() { - return ReactComponentTreeDevtool.getRootIDs() - .map(ReactComponentTreeDevtool.getDisplayName); - } - - function getRegisteredDisplayNames() { - return ReactComponentTreeDevtool.getRegisteredIDs() - .map(ReactComponentTreeDevtool.getDisplayName); - } - - function getTree(rootID, options = {}) { - var { - includeOwnerDisplayName = false, - includeParentDisplayName = false, - expectedParentID = null, - } = options; - - var result = { - displayName: ReactComponentTreeDevtool.getDisplayName(rootID), - }; - - var ownerID = ReactComponentTreeDevtool.getOwnerID(rootID); - var parentID = ReactComponentTreeDevtool.getParentID(rootID); - expect(parentID).toBe(expectedParentID); - - if (includeParentDisplayName && parentID) { - result.parentDisplayName = ReactComponentTreeDevtool.getDisplayName(parentID); - } - if (includeOwnerDisplayName && ownerID) { - result.ownerDisplayName = ReactComponentTreeDevtool.getDisplayName(ownerID); - } - - var childIDs = ReactComponentTreeDevtool.getChildIDs(rootID); - var text = ReactComponentTreeDevtool.getText(rootID); - if (text != null) { - result.text = text; - } else { - result.children = childIDs.map(childID => - getTree(childID, {...options, expectedParentID: rootID }) - ); - } - - return result; - } - - function assertTreeMatches(pairs, options) { + function assertTreeMatches(pairs) { if (!Array.isArray(pairs[0])) { pairs = [pairs]; } @@ -89,8 +46,15 @@ describe('ReactComponentTreeDevtool', () => { } } - function getActualTree() { - return getTree(rootInstance._debugID, options).children[0]; + function expectWrapperTreeToEqual(expectedTree) { + ReactComponentTreeTestUtils.expectTree(rootInstance._debugID, { + displayName: 'Wrapper', + children: expectedTree ? [expectedTree] : [], + }); + if (!expectedTree) { + expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]); + expect(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]); + } } // Mount once, render updates, then unmount. @@ -100,20 +64,18 @@ describe('ReactComponentTreeDevtool', () => { // Mount a new tree or update the existing tree. ReactDOM.render(, node); - expect(getActualTree()).toEqual(expectedTree); + expectWrapperTreeToEqual(expectedTree); // Purging should have no effect // on the tree we expect to see. ReactComponentTreeDevtool.purgeUnmountedComponents(); - expect(getActualTree()).toEqual(expectedTree); + expectWrapperTreeToEqual(expectedTree); }); // Unmounting the root node should purge // the whole subtree automatically. ReactDOM.unmountComponentAtNode(node); - expect(getActualTree()).toBe(undefined); - expect(getRootDisplayNames()).toEqual([]); - expect(getRegisteredDisplayNames()).toEqual([]); + expectWrapperTreeToEqual(null); // Server render every pair. // Ensure the tree is correct on every step. @@ -123,9 +85,7 @@ describe('ReactComponentTreeDevtool', () => { // Rendering to string should not produce any entries // because ReactDebugTool purges it when the flush ends. ReactDOMServer.renderToString(); - expect(getActualTree()).toBe(undefined); - expect(getRootDisplayNames()).toEqual([]); - expect(getRegisteredDisplayNames()).toEqual([]); + expectWrapperTreeToEqual(null); // To test it, we tell the devtool to ignore next purge // so the cleanup request by ReactDebugTool is ignored. @@ -133,13 +93,11 @@ describe('ReactComponentTreeDevtool', () => { ReactComponentTreeDevtool._preventPurging = true; ReactDOMServer.renderToString(); ReactComponentTreeDevtool._preventPurging = false; - expect(getActualTree()).toEqual(expectedTree); + expectWrapperTreeToEqual(expectedTree); // Purge manually since we skipped the automatic purge. ReactComponentTreeDevtool.purgeUnmountedComponents(); - expect(getActualTree()).toBe(undefined); - expect(getRootDisplayNames()).toEqual([]); - expect(getRegisteredDisplayNames()).toEqual([]); + expectWrapperTreeToEqual(null); }); } @@ -1650,7 +1608,7 @@ describe('ReactComponentTreeDevtool', () => { }], }], }; - assertTreeMatches([element, tree], {includeOwnerDisplayName: true}); + assertTreeMatches([element, tree]); }); it('purges unmounted components automatically', () => { @@ -1674,33 +1632,28 @@ describe('ReactComponentTreeDevtool', () => { } ReactDOM.render(, node); - expect( - getTree(barInstance._debugID, { - includeParentDisplayName: true, - expectedParentID: fooInstance._debugID, - }) - ).toEqual({ + ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { displayName: 'Bar', parentDisplayName: 'Foo', + parentID: fooInstance._debugID, children: [], - }); + }, 'Foo'); renderBar = false; ReactDOM.render(, node); - expect( - getTree(barInstance._debugID, {expectedParentID: null}) - ).toEqual({ + ReactDOM.render(, node); + ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { displayName: 'Unknown', children: [], - }); + parentID: null, + }, 'Foo'); ReactDOM.unmountComponentAtNode(node); - expect( - getTree(barInstance._debugID, {expectedParentID: null}) - ).toEqual({ + ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { displayName: 'Unknown', children: [], - }); + parentID: null, + }, 'Foo'); }); it('reports update counts', () => { @@ -1732,14 +1685,14 @@ describe('ReactComponentTreeDevtool', () => { var node = document.createElement('div'); ReactDOM.render(
, node); - expect(getRootDisplayNames()).toEqual(['div']); + expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']); ReactDOM.render(
, node); - expect(getRootDisplayNames()).toEqual(['div']); + expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']); ReactDOM.unmountComponentAtNode(node); - expect(getRootDisplayNames()).toEqual([]); - expect(getRegisteredDisplayNames()).toEqual([]); + expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]); + expect(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]); }); it('creates stack addenda', () => { diff --git a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.native.js b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.native.js index 6841cbd0c6..b65e4dab80 100644 --- a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.native.js +++ b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.native.js @@ -16,6 +16,7 @@ describe('ReactComponentTreeDevtool', () => { var ReactNative; var ReactInstanceMap; var ReactComponentTreeDevtool; + var ReactComponentTreeTestUtils; var createReactNativeComponentClass; var View; var Image; @@ -28,6 +29,7 @@ describe('ReactComponentTreeDevtool', () => { ReactNative = require('ReactNative'); ReactInstanceMap = require('ReactInstanceMap'); ReactComponentTreeDevtool = require('ReactComponentTreeDevtool'); + ReactComponentTreeTestUtils = require('ReactComponentTreeTestUtils'); View = require('View'); createReactNativeComponentClass = require('createReactNativeComponentClass'); Image = createReactNativeComponentClass({ @@ -51,51 +53,6 @@ describe('ReactComponentTreeDevtool', () => { }); }); - function getRootDisplayNames() { - return ReactComponentTreeDevtool.getRootIDs() - .map(ReactComponentTreeDevtool.getDisplayName); - } - - function getRegisteredDisplayNames() { - return ReactComponentTreeDevtool.getRegisteredIDs() - .map(ReactComponentTreeDevtool.getDisplayName); - } - - function getTree(rootID, options = {}) { - var { - includeOwnerDisplayName = false, - includeParentDisplayName = false, - expectedParentID = null, - } = options; - - var result = { - displayName: ReactComponentTreeDevtool.getDisplayName(rootID), - }; - - var ownerID = ReactComponentTreeDevtool.getOwnerID(rootID); - var parentID = ReactComponentTreeDevtool.getParentID(rootID); - expect(parentID).toBe(expectedParentID); - - if (includeParentDisplayName && parentID) { - result.parentDisplayName = ReactComponentTreeDevtool.getDisplayName(parentID); - } - if (includeOwnerDisplayName && ownerID) { - result.ownerDisplayName = ReactComponentTreeDevtool.getDisplayName(ownerID); - } - - var childIDs = ReactComponentTreeDevtool.getChildIDs(rootID); - var text = ReactComponentTreeDevtool.getText(rootID); - if (text != null) { - result.text = text; - } else { - result.children = childIDs.map(childID => - getTree(childID, {...options, expectedParentID: rootID }) - ); - } - - return result; - } - function assertTreeMatches(pairs, options) { if (!Array.isArray(pairs[0])) { pairs = [pairs]; @@ -111,8 +68,15 @@ describe('ReactComponentTreeDevtool', () => { } } - function getActualTree() { - return getTree(rootInstance._debugID, options).children[0]; + function expectWrapperTreeToEqual(expectedTree) { + ReactComponentTreeTestUtils.expectTree(rootInstance._debugID, { + displayName: 'Wrapper', + children: expectedTree ? [expectedTree] : [], + }); + if (!expectedTree) { + expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]); + expect(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]); + } } // Mount once, render updates, then unmount. @@ -122,20 +86,18 @@ describe('ReactComponentTreeDevtool', () => { // Mount a new tree or update the existing tree. ReactNative.render(, 1); - expect(getActualTree()).toEqual(expectedTree); + expectWrapperTreeToEqual(expectedTree); // Purging should have no effect // on the tree we expect to see. ReactComponentTreeDevtool.purgeUnmountedComponents(); - expect(getActualTree()).toEqual(expectedTree); + expectWrapperTreeToEqual(expectedTree); }); // Unmounting the root node should purge // the whole subtree automatically. ReactNative.unmountComponentAtNode(1); - expect(getActualTree()).toBe(undefined); - expect(getRootDisplayNames()).toEqual([]); - expect(getRegisteredDisplayNames()).toEqual([]); + expectWrapperTreeToEqual(null); // Mount and unmount for every pair. // Ensure the tree is correct on every step. @@ -144,13 +106,11 @@ describe('ReactComponentTreeDevtool', () => { // Mount a new tree. ReactNative.render(, 1); - expect(getActualTree()).toEqual(expectedTree); + expectWrapperTreeToEqual(expectedTree); // Unmounting should clean it up. ReactNative.unmountComponentAtNode(1); - expect(getActualTree()).toBe(undefined); - expect(getRootDisplayNames()).toEqual([]); - expect(getRegisteredDisplayNames()).toEqual([]); + expectWrapperTreeToEqual(null); }); } @@ -1650,33 +1610,27 @@ describe('ReactComponentTreeDevtool', () => { } ReactNative.render(, 1); - expect( - getTree(barInstance._debugID, { - includeParentDisplayName: true, - expectedParentID: fooInstance._debugID, - }) - ).toEqual({ + ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { displayName: 'Bar', parentDisplayName: 'Foo', + parentID: fooInstance._debugID, children: [], - }); + }, 'Foo'); renderBar = false; ReactNative.render(, 1); - expect( - getTree(barInstance._debugID, {expectedParentID: null}) - ).toEqual({ + ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { displayName: 'Unknown', children: [], - }); + parentID: null, + }, 'Foo'); ReactNative.unmountComponentAtNode(1); - expect( - getTree(barInstance._debugID, {expectedParentID: null}) - ).toEqual({ + ReactComponentTreeTestUtils.expectTree(barInstance._debugID, { displayName: 'Unknown', children: [], - }); + parentID: null, + }, 'Foo'); }); it('reports update counts', () => { @@ -1704,13 +1658,13 @@ describe('ReactComponentTreeDevtool', () => { it('does not report top-level wrapper as a root', () => { ReactNative.render(, 1); - expect(getRootDisplayNames()).toEqual(['View']); + expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['View']); ReactNative.render(, 1); - expect(getRootDisplayNames()).toEqual(['View']); + expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['View']); ReactNative.unmountComponentAtNode(1); - expect(getRootDisplayNames()).toEqual([]); - expect(getRegisteredDisplayNames()).toEqual([]); + expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]); + expect(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]); }); }); diff --git a/src/test/ReactComponentTreeTestUtils.js b/src/test/ReactComponentTreeTestUtils.js new file mode 100644 index 0000000000..32ba3b37ca --- /dev/null +++ b/src/test/ReactComponentTreeTestUtils.js @@ -0,0 +1,92 @@ +/** + * Copyright 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactComponentTreeTestUtils + */ + +'use strict'; + +var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool'); + +function getRootDisplayNames() { + return ReactComponentTreeDevtool.getRootIDs() + .map(ReactComponentTreeDevtool.getDisplayName); +} + +function getRegisteredDisplayNames() { + return ReactComponentTreeDevtool.getRegisteredIDs() + .map(ReactComponentTreeDevtool.getDisplayName); +} + +function expectTree(rootID, expectedTree, parentPath = '') { + var displayName = ReactComponentTreeDevtool.getDisplayName(rootID); + var ownerID = ReactComponentTreeDevtool.getOwnerID(rootID); + var parentID = ReactComponentTreeDevtool.getParentID(rootID); + var childIDs = ReactComponentTreeDevtool.getChildIDs(rootID); + var text = ReactComponentTreeDevtool.getText(rootID); + var path = parentPath ? `${parentPath} > ${displayName}` : displayName; + + function expectEqual(actual, expected, name) { + // Get Jasmine to print descriptive error messages. + // We pass path so that we know where the mismatch occurred. + expect({ + path, + [name]: actual, + }).toEqual({ + path, + [name]: expected, + }); + } + + if (expectedTree.parentDisplayName !== undefined) { + expectEqual( + ReactComponentTreeDevtool.getDisplayName(parentID), + expectedTree.parentDisplayName, + 'parentDisplayName' + ); + } + if (expectedTree.ownerDisplayName !== undefined) { + expectEqual( + ReactComponentTreeDevtool.getDisplayName(ownerID), + expectedTree.ownerDisplayName, + 'ownerDisplayName' + ); + } + if (expectedTree.parentID !== undefined) { + expectEqual(parentID, expectedTree.parentID, 'parentID'); + } + if (expectedTree.text !== undefined) { + expectEqual(text, expectedTree.text, 'text'); + } else { + expectEqual(text, null, 'text'); + } + if (expectedTree.children !== undefined) { + expectEqual( + childIDs.length, + expectedTree.children.length, + 'children.length' + ); + for (var i = 0; i < childIDs.length; i++) { + expectTree( + childIDs[i], + {parentID: rootID, ...expectedTree.children[i]}, + path + ); + } + } else { + expectEqual(childIDs, [], 'childIDs'); + } +} + +var ReactComponentTreeTestUtils = { + expectTree, + getRootDisplayNames, + getRegisteredDisplayNames, +}; + +module.exports = ReactComponentTreeTestUtils;