@samitouri / QOS-React / commits / 9301cfbc80

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;