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.
Dan Abramov committed
May 17, 2016 at 20:50 UTC
9301cfbc804b7eb2a4c4508d28140278cb7538dc
3 files changed
+154
-155
src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js
+33
-80
@@ -17,6 +17,7 @@ describe('ReactComponentTreeDevtool', () => {
17
var ReactDOMServer;
18
var ReactInstanceMap;
19
var ReactComponentTreeDevtool;
20
+ var ReactComponentTreeTestUtils;
21
22
beforeEach(() => {
23
jest.resetModuleRegistry();
@@ -26,54 +27,10 @@ describe('ReactComponentTreeDevtool', () => {
27
ReactDOMServer = require('ReactDOMServer');
28
ReactInstanceMap = require('ReactInstanceMap');
29
ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
30
+ ReactComponentTreeTestUtils = require('ReactComponentTreeTestUtils');
31
});
32
31
- function getRootDisplayNames() {
32
- return ReactComponentTreeDevtool.getRootIDs()
33
- .map(ReactComponentTreeDevtool.getDisplayName);
34
- }
35
-
36
- function getRegisteredDisplayNames() {
37
- return ReactComponentTreeDevtool.getRegisteredIDs()
38
- .map(ReactComponentTreeDevtool.getDisplayName);
39
- }
40
-
41
- function getTree(rootID, options = {}) {
42
- var {
43
- includeOwnerDisplayName = false,
44
- includeParentDisplayName = false,
45
- expectedParentID = null,
46
- } = options;
47
-
48
- var result = {
49
- displayName: ReactComponentTreeDevtool.getDisplayName(rootID),
50
- };
51
-
52
- var ownerID = ReactComponentTreeDevtool.getOwnerID(rootID);
53
- var parentID = ReactComponentTreeDevtool.getParentID(rootID);
54
- expect(parentID).toBe(expectedParentID);
55
-
56
- if (includeParentDisplayName && parentID) {
57
- result.parentDisplayName = ReactComponentTreeDevtool.getDisplayName(parentID);
58
- }
59
- if (includeOwnerDisplayName && ownerID) {
60
- result.ownerDisplayName = ReactComponentTreeDevtool.getDisplayName(ownerID);
61
- }
62
-
63
- var childIDs = ReactComponentTreeDevtool.getChildIDs(rootID);
64
- var text = ReactComponentTreeDevtool.getText(rootID);
65
- if (text != null) {
66
- result.text = text;
67
- } else {
68
- result.children = childIDs.map(childID =>
69
- getTree(childID, {...options, expectedParentID: rootID })
70
- );
71
- }
72
-
73
- return result;
74
- }
75
-
76
- function assertTreeMatches(pairs, options) {
33
+ function assertTreeMatches(pairs) {
34
if (!Array.isArray(pairs[0])) {
35
pairs = [pairs];
36
}
@@ -89,8 +46,15 @@ describe('ReactComponentTreeDevtool', () => {
46
}
47
}
48
92
- function getActualTree() {
93
- return getTree(rootInstance._debugID, options).children[0];
49
+ function expectWrapperTreeToEqual(expectedTree) {
50
+ ReactComponentTreeTestUtils.expectTree(rootInstance._debugID, {
51
+ displayName: 'Wrapper',
52
+ children: expectedTree ? [expectedTree] : [],
53
+ });
54
+ if (!expectedTree) {
55
+ expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]);
56
+ expect(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]);
57
+ }
58
}
59
60
// Mount once, render updates, then unmount.
@@ -100,20 +64,18 @@ describe('ReactComponentTreeDevtool', () => {
64
65
// Mount a new tree or update the existing tree.
66
ReactDOM.render(<Wrapper />, node);
103
- expect(getActualTree()).toEqual(expectedTree);
67
+ expectWrapperTreeToEqual(expectedTree);
68
69
// Purging should have no effect
70
// on the tree we expect to see.
71
ReactComponentTreeDevtool.purgeUnmountedComponents();
108
- expect(getActualTree()).toEqual(expectedTree);
72
+ expectWrapperTreeToEqual(expectedTree);
73
});
74
75
// Unmounting the root node should purge
76
// the whole subtree automatically.
77
ReactDOM.unmountComponentAtNode(node);
114
- expect(getActualTree()).toBe(undefined);
115
- expect(getRootDisplayNames()).toEqual([]);
116
- expect(getRegisteredDisplayNames()).toEqual([]);
78
+ expectWrapperTreeToEqual(null);
79
80
// Server render every pair.
81
// Ensure the tree is correct on every step.
@@ -123,9 +85,7 @@ describe('ReactComponentTreeDevtool', () => {
85
// Rendering to string should not produce any entries
86
// because ReactDebugTool purges it when the flush ends.
87
ReactDOMServer.renderToString(<Wrapper />);
126
- expect(getActualTree()).toBe(undefined);
127
- expect(getRootDisplayNames()).toEqual([]);
128
- expect(getRegisteredDisplayNames()).toEqual([]);
88
+ expectWrapperTreeToEqual(null);
89
90
// To test it, we tell the devtool to ignore next purge
91
// so the cleanup request by ReactDebugTool is ignored.
@@ -133,13 +93,11 @@ describe('ReactComponentTreeDevtool', () => {
93
ReactComponentTreeDevtool._preventPurging = true;
94
ReactDOMServer.renderToString(<Wrapper />);
95
ReactComponentTreeDevtool._preventPurging = false;
136
- expect(getActualTree()).toEqual(expectedTree);
96
+ expectWrapperTreeToEqual(expectedTree);
97
98
// Purge manually since we skipped the automatic purge.
99
ReactComponentTreeDevtool.purgeUnmountedComponents();
140
- expect(getActualTree()).toBe(undefined);
141
- expect(getRootDisplayNames()).toEqual([]);
142
- expect(getRegisteredDisplayNames()).toEqual([]);
100
+ expectWrapperTreeToEqual(null);
101
});
102
}
103
@@ -1650,7 +1608,7 @@ describe('ReactComponentTreeDevtool', () => {
1608
}],
1609
}],
1610
};
1653
- assertTreeMatches([element, tree], {includeOwnerDisplayName: true});
1611
+ assertTreeMatches([element, tree]);
1612
});
1613
1614
it('purges unmounted components automatically', () => {
@@ -1674,33 +1632,28 @@ describe('ReactComponentTreeDevtool', () => {
1632
}
1633
1634
ReactDOM.render(<Foo />, node);
1677
- expect(
1678
- getTree(barInstance._debugID, {
1679
- includeParentDisplayName: true,
1680
- expectedParentID: fooInstance._debugID,
1681
- })
1682
- ).toEqual({
1635
+ ReactComponentTreeTestUtils.expectTree(barInstance._debugID, {
1636
displayName: 'Bar',
1637
parentDisplayName: 'Foo',
1638
+ parentID: fooInstance._debugID,
1639
children: [],
1686
- });
1640
+ }, 'Foo');
1641
1642
renderBar = false;
1643
ReactDOM.render(<Foo />, node);
1690
- expect(
1691
- getTree(barInstance._debugID, {expectedParentID: null})
1692
- ).toEqual({
1644
+ ReactDOM.render(<Foo />, node);
1645
+ ReactComponentTreeTestUtils.expectTree(barInstance._debugID, {
1646
displayName: 'Unknown',
1647
children: [],
1695
- });
1648
+ parentID: null,
1649
+ }, 'Foo');
1650
1651
ReactDOM.unmountComponentAtNode(node);
1698
- expect(
1699
- getTree(barInstance._debugID, {expectedParentID: null})
1700
- ).toEqual({
1652
+ ReactComponentTreeTestUtils.expectTree(barInstance._debugID, {
1653
displayName: 'Unknown',
1654
children: [],
1703
- });
1655
+ parentID: null,
1656
+ }, 'Foo');
1657
});
1658
1659
it('reports update counts', () => {
@@ -1732,14 +1685,14 @@ describe('ReactComponentTreeDevtool', () => {
1685
var node = document.createElement('div');
1686
1687
ReactDOM.render(<div className="a" />, node);
1735
- expect(getRootDisplayNames()).toEqual(['div']);
1688
+ expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']);
1689
1690
ReactDOM.render(<div className="b" />, node);
1738
- expect(getRootDisplayNames()).toEqual(['div']);
1691
+ expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']);
1692
1693
ReactDOM.unmountComponentAtNode(node);
1741
- expect(getRootDisplayNames()).toEqual([]);
1742
- expect(getRegisteredDisplayNames()).toEqual([]);
1694
+ expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]);
1695
+ expect(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]);
1696
});
1697
1698
it('creates stack addenda', () => {
src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.native.js
+29
-75
@@ -16,6 +16,7 @@ describe('ReactComponentTreeDevtool', () => {
16
var ReactNative;
17
var ReactInstanceMap;
18
var ReactComponentTreeDevtool;
19
+ var ReactComponentTreeTestUtils;
20
var createReactNativeComponentClass;
21
var View;
22
var Image;
@@ -28,6 +29,7 @@ describe('ReactComponentTreeDevtool', () => {
29
ReactNative = require('ReactNative');
30
ReactInstanceMap = require('ReactInstanceMap');
31
ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
32
+ ReactComponentTreeTestUtils = require('ReactComponentTreeTestUtils');
33
View = require('View');
34
createReactNativeComponentClass = require('createReactNativeComponentClass');
35
Image = createReactNativeComponentClass({
@@ -51,51 +53,6 @@ describe('ReactComponentTreeDevtool', () => {
53
});
54
});
55
54
- function getRootDisplayNames() {
55
- return ReactComponentTreeDevtool.getRootIDs()
56
- .map(ReactComponentTreeDevtool.getDisplayName);
57
- }
58
-
59
- function getRegisteredDisplayNames() {
60
- return ReactComponentTreeDevtool.getRegisteredIDs()
61
- .map(ReactComponentTreeDevtool.getDisplayName);
62
- }
63
-
64
- function getTree(rootID, options = {}) {
65
- var {
66
- includeOwnerDisplayName = false,
67
- includeParentDisplayName = false,
68
- expectedParentID = null,
69
- } = options;
70
-
71
- var result = {
72
- displayName: ReactComponentTreeDevtool.getDisplayName(rootID),
73
- };
74
-
75
- var ownerID = ReactComponentTreeDevtool.getOwnerID(rootID);
76
- var parentID = ReactComponentTreeDevtool.getParentID(rootID);
77
- expect(parentID).toBe(expectedParentID);
78
-
79
- if (includeParentDisplayName && parentID) {
80
- result.parentDisplayName = ReactComponentTreeDevtool.getDisplayName(parentID);
81
- }
82
- if (includeOwnerDisplayName && ownerID) {
83
- result.ownerDisplayName = ReactComponentTreeDevtool.getDisplayName(ownerID);
84
- }
85
-
86
- var childIDs = ReactComponentTreeDevtool.getChildIDs(rootID);
87
- var text = ReactComponentTreeDevtool.getText(rootID);
88
- if (text != null) {
89
- result.text = text;
90
- } else {
91
- result.children = childIDs.map(childID =>
92
- getTree(childID, {...options, expectedParentID: rootID })
93
- );
94
- }
95
-
96
- return result;
97
- }
98
-
56
function assertTreeMatches(pairs, options) {
57
if (!Array.isArray(pairs[0])) {
58
pairs = [pairs];
@@ -111,8 +68,15 @@ describe('ReactComponentTreeDevtool', () => {
68
}
69
}
70
114
- function getActualTree() {
115
- return getTree(rootInstance._debugID, options).children[0];
71
+ function expectWrapperTreeToEqual(expectedTree) {
72
+ ReactComponentTreeTestUtils.expectTree(rootInstance._debugID, {
73
+ displayName: 'Wrapper',
74
+ children: expectedTree ? [expectedTree] : [],
75
+ });
76
+ if (!expectedTree) {
77
+ expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]);
78
+ expect(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]);
79
+ }
80
}
81
82
// Mount once, render updates, then unmount.
@@ -122,20 +86,18 @@ describe('ReactComponentTreeDevtool', () => {
86
87
// Mount a new tree or update the existing tree.
88
ReactNative.render(<Wrapper />, 1);
125
- expect(getActualTree()).toEqual(expectedTree);
89
+ expectWrapperTreeToEqual(expectedTree);
90
91
// Purging should have no effect
92
// on the tree we expect to see.
93
ReactComponentTreeDevtool.purgeUnmountedComponents();
130
- expect(getActualTree()).toEqual(expectedTree);
94
+ expectWrapperTreeToEqual(expectedTree);
95
});
96
97
// Unmounting the root node should purge
98
// the whole subtree automatically.
99
ReactNative.unmountComponentAtNode(1);
136
- expect(getActualTree()).toBe(undefined);
137
- expect(getRootDisplayNames()).toEqual([]);
138
- expect(getRegisteredDisplayNames()).toEqual([]);
100
+ expectWrapperTreeToEqual(null);
101
102
// Mount and unmount for every pair.
103
// Ensure the tree is correct on every step.
@@ -144,13 +106,11 @@ describe('ReactComponentTreeDevtool', () => {
106
107
// Mount a new tree.
108
ReactNative.render(<Wrapper />, 1);
147
- expect(getActualTree()).toEqual(expectedTree);
109
+ expectWrapperTreeToEqual(expectedTree);
110
111
// Unmounting should clean it up.
112
ReactNative.unmountComponentAtNode(1);
151
- expect(getActualTree()).toBe(undefined);
152
- expect(getRootDisplayNames()).toEqual([]);
153
- expect(getRegisteredDisplayNames()).toEqual([]);
113
+ expectWrapperTreeToEqual(null);
114
});
115
}
116
@@ -1650,33 +1610,27 @@ describe('ReactComponentTreeDevtool', () => {
1610
}
1611
1612
ReactNative.render(<Foo />, 1);
1653
- expect(
1654
- getTree(barInstance._debugID, {
1655
- includeParentDisplayName: true,
1656
- expectedParentID: fooInstance._debugID,
1657
- })
1658
- ).toEqual({
1613
+ ReactComponentTreeTestUtils.expectTree(barInstance._debugID, {
1614
displayName: 'Bar',
1615
parentDisplayName: 'Foo',
1616
+ parentID: fooInstance._debugID,
1617
children: [],
1662
- });
1618
+ }, 'Foo');
1619
1620
renderBar = false;
1621
ReactNative.render(<Foo />, 1);
1666
- expect(
1667
- getTree(barInstance._debugID, {expectedParentID: null})
1668
- ).toEqual({
1622
+ ReactComponentTreeTestUtils.expectTree(barInstance._debugID, {
1623
displayName: 'Unknown',
1624
children: [],
1671
- });
1625
+ parentID: null,
1626
+ }, 'Foo');
1627
1628
ReactNative.unmountComponentAtNode(1);
1674
- expect(
1675
- getTree(barInstance._debugID, {expectedParentID: null})
1676
- ).toEqual({
1629
+ ReactComponentTreeTestUtils.expectTree(barInstance._debugID, {
1630
displayName: 'Unknown',
1631
children: [],
1679
- });
1632
+ parentID: null,
1633
+ }, 'Foo');
1634
});
1635
1636
it('reports update counts', () => {
@@ -1704,13 +1658,13 @@ describe('ReactComponentTreeDevtool', () => {
1658
1659
it('does not report top-level wrapper as a root', () => {
1660
ReactNative.render(<View><Image /></View>, 1);
1707
- expect(getRootDisplayNames()).toEqual(['View']);
1661
+ expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['View']);
1662
1663
ReactNative.render(<View><Text /></View>, 1);
1710
- expect(getRootDisplayNames()).toEqual(['View']);
1664
+ expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['View']);
1665
1666
ReactNative.unmountComponentAtNode(1);
1713
- expect(getRootDisplayNames()).toEqual([]);
1714
- expect(getRegisteredDisplayNames()).toEqual([]);
1667
+ expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]);
1668
+ expect(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]);
1669
});
1670
});
src/test/ReactComponentTreeTestUtils.js
new
+92
@@ -0,0 +1,92 @@
1
+/**
2
+ * Copyright 2016-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule ReactComponentTreeTestUtils
10
+ */
11
+
12
+'use strict';
13
+
14
+var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
15
+
16
+function getRootDisplayNames() {
17
+ return ReactComponentTreeDevtool.getRootIDs()
18
+ .map(ReactComponentTreeDevtool.getDisplayName);
19
+}
20
+
21
+function getRegisteredDisplayNames() {
22
+ return ReactComponentTreeDevtool.getRegisteredIDs()
23
+ .map(ReactComponentTreeDevtool.getDisplayName);
24
+}
25
+
26
+function expectTree(rootID, expectedTree, parentPath = '') {
27
+ var displayName = ReactComponentTreeDevtool.getDisplayName(rootID);
28
+ var ownerID = ReactComponentTreeDevtool.getOwnerID(rootID);
29
+ var parentID = ReactComponentTreeDevtool.getParentID(rootID);
30
+ var childIDs = ReactComponentTreeDevtool.getChildIDs(rootID);
31
+ var text = ReactComponentTreeDevtool.getText(rootID);
32
+ var path = parentPath ? `${parentPath} > ${displayName}` : displayName;
33
+
34
+ function expectEqual(actual, expected, name) {
35
+ // Get Jasmine to print descriptive error messages.
36
+ // We pass path so that we know where the mismatch occurred.
37
+ expect({
38
+ path,
39
+ [name]: actual,
40
+ }).toEqual({
41
+ path,
42
+ [name]: expected,
43
+ });
44
+ }
45
+
46
+ if (expectedTree.parentDisplayName !== undefined) {
47
+ expectEqual(
48
+ ReactComponentTreeDevtool.getDisplayName(parentID),
49
+ expectedTree.parentDisplayName,
50
+ 'parentDisplayName'
51
+ );
52
+ }
53
+ if (expectedTree.ownerDisplayName !== undefined) {
54
+ expectEqual(
55
+ ReactComponentTreeDevtool.getDisplayName(ownerID),
56
+ expectedTree.ownerDisplayName,
57
+ 'ownerDisplayName'
58
+ );
59
+ }
60
+ if (expectedTree.parentID !== undefined) {
61
+ expectEqual(parentID, expectedTree.parentID, 'parentID');
62
+ }
63
+ if (expectedTree.text !== undefined) {
64
+ expectEqual(text, expectedTree.text, 'text');
65
+ } else {
66
+ expectEqual(text, null, 'text');
67
+ }
68
+ if (expectedTree.children !== undefined) {
69
+ expectEqual(
70
+ childIDs.length,
71
+ expectedTree.children.length,
72
+ 'children.length'
73
+ );
74
+ for (var i = 0; i < childIDs.length; i++) {
75
+ expectTree(
76
+ childIDs[i],
77
+ {parentID: rootID, ...expectedTree.children[i]},
78
+ path
79
+ );
80
+ }
81
+ } else {
82
+ expectEqual(childIDs, [], 'childIDs');
83
+ }
84
+}
85
+
86
+var ReactComponentTreeTestUtils = {
87
+ expectTree,
88
+ getRootDisplayNames,
89
+ getRegisteredDisplayNames,
90
+};
91
+
92
+module.exports = ReactComponentTreeTestUtils;