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.
This commit is contained in:
Dan Abramov
2016-05-17 20:51:00 +01:00
parent 151e1d7014
commit 9301cfbc80
3 changed files with 154 additions and 155 deletions
@@ -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(<Wrapper />, 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(<Wrapper />);
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(<Wrapper />);
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(<Foo />, 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(<Foo />, node);
expect(
getTree(barInstance._debugID, {expectedParentID: null})
).toEqual({
ReactDOM.render(<Foo />, 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(<div className="a" />, node);
expect(getRootDisplayNames()).toEqual(['div']);
expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']);
ReactDOM.render(<div className="b" />, 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', () => {
@@ -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(<Wrapper />, 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(<Wrapper />, 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(<Foo />, 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(<Foo />, 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(<View><Image /></View>, 1);
expect(getRootDisplayNames()).toEqual(['View']);
expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['View']);
ReactNative.render(<View><Text /></View>, 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([]);
});
});
+92
View File
@@ -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;