11
12
'use strict';
13
14
+var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
15
+var describeStack = ReactDOMFeatureFlags.useFiber ? describe.skip : describe;
16
+
17
describe('ReactComponentTreeHook', () => {
18
var React;
19
var ReactDOM;
33
ReactComponentTreeTestUtils = require('ReactComponentTreeTestUtils');
34
});
35
36
+ // This is the only part used both by Stack and Fiber.
37
+ describe('stack addenda', () => {
38
+ it('gets created', () => {
39
+ function getAddendum(element) {
40
+ var addendum = ReactComponentTreeHook.getCurrentStackAddendum(element);
41
+ return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
42
+ }
43
+
44
+ var Anon = React.createClass({displayName: null, render: () => null});
45
+ var Orange = React.createClass({render: () => null});
46
+
47
+ expectDev(getAddendum()).toBe(
48
+ ''
49
+ );
50
+ expectDev(getAddendum(<div />)).toBe(
51
+ '\n in div (at **)'
52
+ );
53
+ expectDev(getAddendum(<Anon />)).toBe(
54
+ '\n in Unknown (at **)'
55
+ );
56
+ expectDev(getAddendum(<Orange />)).toBe(
57
+ '\n in Orange (at **)'
58
+ );
59
+ expectDev(getAddendum(React.createElement(Orange))).toBe(
60
+ '\n in Orange'
61
+ );
62
+
63
+ var renders = 0;
64
+ var rOwnedByQ;
65
+
66
+ function Q() {
67
+ return (rOwnedByQ = React.createElement(R));
68
+ }
69
+ function R() {
70
+ return <div><S /></div>;
71
+ }
72
+ class S extends React.Component {
73
+ componentDidMount() {
74
+ // Check that the parent path is still fetched when only S itself is on
75
+ // the stack.
76
+ this.forceUpdate();
77
+ }
78
+ render() {
79
+ expectDev(getAddendum()).toBe(
80
+ '\n in S (at **)' +
81
+ '\n in div (at **)' +
82
+ '\n in R (created by Q)' +
83
+ '\n in Q (at **)'
84
+ );
85
+ expectDev(getAddendum(<span />)).toBe(
86
+ '\n in span (at **)' +
87
+ '\n in S (at **)' +
88
+ '\n in div (at **)' +
89
+ '\n in R (created by Q)' +
90
+ '\n in Q (at **)'
91
+ );
92
+ expectDev(getAddendum(React.createElement('span'))).toBe(
93
+ '\n in span (created by S)' +
94
+ '\n in S (at **)' +
95
+ '\n in div (at **)' +
96
+ '\n in R (created by Q)' +
97
+ '\n in Q (at **)'
98
+ );
99
+ renders++;
100
+ return null;
101
+ }
102
+ }
103
+ ReactDOM.render(<Q />, document.createElement('div'));
104
+ expectDev(renders).toBe(2);
105
+
106
+ // Make sure owner is fetched for the top element too.
107
+ expectDev(getAddendum(rOwnedByQ)).toBe(
108
+ '\n in R (created by Q)'
109
+ );
110
+ });
111
+
112
+ // These are features and regression tests that only affect
113
+ // the Stack implementation of the stack addendum.
114
+ if (!ReactDOMFeatureFlags.useFiber) {
115
+ it('can be retrieved by ID', () => {
116
+ function getAddendum(id) {
117
+ var addendum = ReactComponentTreeHook.getStackAddendumByID(id);
118
+ return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
119
+ }
120
+
121
+ class Q extends React.Component {
122
+ render() {
123
+ return null;
124
+ }
125
+ }
126
+
127
+ var q = ReactDOM.render(<Q />, document.createElement('div'));
128
+ expectDev(getAddendum(ReactInstanceMap.get(q)._debugID)).toBe(
129
+ '\n in Q (at **)'
130
+ );
131
+
132
+ spyOn(console, 'error');
133
+ getAddendum(-17);
134
+ expectDev(console.error.calls.count()).toBe(1);
135
+ expectDev(console.error.calls.argsFor(0)[0]).toBe(
136
+ 'Warning: ReactComponentTreeHook: Missing React element for ' +
137
+ 'debugID -17 when building stack'
138
+ );
139
+ });
140
+
141
+ it('is created during mounting', () => {
142
+ // https://github.com/facebook/react/issues/7187
143
+ var el = document.createElement('div');
144
+ var portalEl = document.createElement('div');
145
+ class Foo extends React.Component {
146
+ componentWillMount() {
147
+ ReactDOM.render(<div />, portalEl);
148
+ }
149
+ render() {
150
+ return <div><div /></div>;
151
+ }
152
+ }
153
+ ReactDOM.render(<Foo />, el);
154
+ });
155
+
156
+ it('is created when calling renderToString during render', () => {
157
+ // https://github.com/facebook/react/issues/7190
158
+ var el = document.createElement('div');
159
+ class Foo extends React.Component {
160
+ render() {
161
+ return (
162
+ <div>
163
+ <div>
164
+ {ReactDOMServer.renderToString(<div />)}
165
+ </div>
166
+ </div>
167
+ );
168
+ }
169
+ }
170
+ ReactDOM.render(<Foo />, el);
171
+ });
172
+ }
173
+ });
174
+
175
+ // The rest of this file is not relevant for Fiber.
176
+ // TODO: remove tests below when we delete Stack.
177
+
178
function assertTreeMatches(pairs) {
179
if (!Array.isArray(pairs[0])) {
180
pairs = [pairs];
251
});
252
}
253
109
- describe('mount', () => {
254
+ describeStack('mount', () => {
255
it('uses displayName or Unknown for classic components', () => {
256
class Foo extends React.Component {
257
render() {
665
});
666
});
667
523
- describe('update', () => {
668
+ describeStack('update', () => {
669
describe('host component', () => {
670
it('updates text of a single text child', () => {
671
var elementBefore = <div>Hi.</div>;
1746
});
1747
});
1748
1604
- it('tracks owner correctly', () => {
1605
- class Foo extends React.Component {
1606
- render() {
1607
- return <Bar><h1>Hi.</h1></Bar>;
1749
+ describeStack('misc', () => {
1750
+ it('tracks owner correctly', () => {
1751
+ class Foo extends React.Component {
1752
+ render() {
1753
+ return <Bar><h1>Hi.</h1></Bar>;
1754
+ }
1755
+ }
1756
+ function Bar({children}) {
1757
+ return <div>{children} Mom</div>;
1758
}
1609
- }
1610
- function Bar({children}) {
1611
- return <div>{children} Mom</div>;
1612
- }
1759
1614
- // Note that owner is not calculated for text nodes
1615
- // because they are not created from real elements.
1616
- var element = <article><Foo /></article>;
1617
- var tree = {
1618
- displayName: 'article',
1619
- children: [{
1620
- displayName: 'Foo',
1760
+ // Note that owner is not calculated for text nodes
1761
+ // because they are not created from real elements.
1762
+ var element = <article><Foo /></article>;
1763
+ var tree = {
1764
+ displayName: 'article',
1765
children: [{
1622
- displayName: 'Bar',
1623
- ownerDisplayName: 'Foo',
1766
+ displayName: 'Foo',
1767
children: [{
1625
- displayName: 'div',
1626
- ownerDisplayName: 'Bar',
1768
+ displayName: 'Bar',
1769
+ ownerDisplayName: 'Foo',
1770
children: [{
1628
- displayName: 'h1',
1629
- ownerDisplayName: 'Foo',
1771
+ displayName: 'div',
1772
+ ownerDisplayName: 'Bar',
1773
children: [{
1774
+ displayName: 'h1',
1775
+ ownerDisplayName: 'Foo',
1776
+ children: [{
1777
+ displayName: '#text',
1778
+ text: 'Hi.',
1779
+ }],
1780
+ }, {
1781
displayName: '#text',
1632
- text: 'Hi.',
1782
+ text: ' Mom',
1783
}],
1634
- }, {
1635
- displayName: '#text',
1636
- text: ' Mom',
1784
}],
1785
}],
1786
}],
1640
- }],
1641
- };
1642
- assertTreeMatches([element, tree]);
1643
- });
1787
+ };
1788
+ assertTreeMatches([element, tree]);
1789
+ });
1790
1645
- it('purges unmounted components automatically', () => {
1646
- var node = document.createElement('div');
1647
- var renderBar = true;
1648
- var fooInstance;
1649
- var barInstance;
1791
+ it('purges unmounted components automatically', () => {
1792
+ var node = document.createElement('div');
1793
+ var renderBar = true;
1794
+ var fooInstance;
1795
+ var barInstance;
1796
1651
- class Foo extends React.Component {
1652
- render() {
1653
- fooInstance = ReactInstanceMap.get(this);
1654
- return renderBar ? <Bar /> : null;
1797
+ class Foo extends React.Component {
1798
+ render() {
1799
+ fooInstance = ReactInstanceMap.get(this);
1800
+ return renderBar ? <Bar /> : null;
1801
+ }
1802
}
1656
- }
1803
1658
- class Bar extends React.Component {
1659
- render() {
1660
- barInstance = ReactInstanceMap.get(this);
1661
- return null;
1804
+ class Bar extends React.Component {
1805
+ render() {
1806
+ barInstance = ReactInstanceMap.get(this);
1807
+ return null;
1808
+ }
1809
}
1663
- }
1664
-
1665
- ReactDOM.render(<Foo />, node);
1666
- ReactComponentTreeTestUtils.expectTree(barInstance._debugID, {
1667
- displayName: 'Bar',
1668
- parentDisplayName: 'Foo',
1669
- parentID: fooInstance._debugID,
1670
- children: [],
1671
- }, 'Foo');
1672
-
1673
- renderBar = false;
1674
- ReactDOM.render(<Foo />, node);
1675
- ReactDOM.render(<Foo />, node);
1676
- ReactComponentTreeTestUtils.expectTree(barInstance._debugID, {
1677
- displayName: 'Unknown',
1678
- children: [],
1679
- parentID: null,
1680
- }, 'Foo');
1681
-
1682
- ReactDOM.unmountComponentAtNode(node);
1683
- ReactComponentTreeTestUtils.expectTree(barInstance._debugID, {
1684
- displayName: 'Unknown',
1685
- children: [],
1686
- parentID: null,
1687
- }, 'Foo');
1688
- });
1689
-
1690
- it('reports update counts', () => {
1691
- var node = document.createElement('div');
1810
1693
- ReactDOM.render(<div className="a" />, node);
1694
- var divID = ReactComponentTreeHook.getRootIDs()[0];
1695
- expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0);
1696
-
1697
- ReactDOM.render(<span className="a" />, node);
1698
- var spanID = ReactComponentTreeHook.getRootIDs()[0];
1699
- expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0);
1700
- expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(0);
1701
-
1702
- ReactDOM.render(<span className="b" />, node);
1703
- expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0);
1704
- expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(1);
1705
-
1706
- ReactDOM.render(<span className="c" />, node);
1707
- expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0);
1708
- expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(2);
1709
-
1710
- ReactDOM.unmountComponentAtNode(node);
1711
- expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0);
1712
- expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(0);
1713
- });
1714
-
1715
- it('does not report top-level wrapper as a root', () => {
1716
- var node = document.createElement('div');
1717
-
1718
- ReactDOM.render(<div className="a" />, node);
1719
- expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']);
1720
-
1721
- ReactDOM.render(<div className="b" />, node);
1722
- expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']);
1723
-
1724
- ReactDOM.unmountComponentAtNode(node);
1725
- expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]);
1726
- expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]);
1727
- });
1811
+ ReactDOM.render(<Foo />, node);
1812
+ ReactComponentTreeTestUtils.expectTree(barInstance._debugID, {
1813
+ displayName: 'Bar',
1814
+ parentDisplayName: 'Foo',
1815
+ parentID: fooInstance._debugID,
1816
+ children: [],
1817
+ }, 'Foo');
1818
1729
- it('registers inlined text nodes', () => {
1730
- var node = document.createElement('div');
1819
+ renderBar = false;
1820
+ ReactDOM.render(<Foo />, node);
1821
+ ReactDOM.render(<Foo />, node);
1822
+ ReactComponentTreeTestUtils.expectTree(barInstance._debugID, {
1823
+ displayName: 'Unknown',
1824
+ children: [],
1825
+ parentID: null,
1826
+ }, 'Foo');
1827
1732
- ReactDOM.render(<div>hi</div>, node);
1733
- expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual(['div', '#text']);
1828
+ ReactDOM.unmountComponentAtNode(node);
1829
+ ReactComponentTreeTestUtils.expectTree(barInstance._debugID, {
1830
+ displayName: 'Unknown',
1831
+ children: [],
1832
+ parentID: null,
1833
+ }, 'Foo');
1834
+ });
1835
1735
- ReactDOM.unmountComponentAtNode(node);
1736
- expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]);
1737
- });
1836
+ it('reports update counts', () => {
1837
+ var node = document.createElement('div');
1838
1739
- describe('stack addenda', () => {
1740
- it('gets created', () => {
1741
- function getAddendum(element) {
1742
- var addendum = ReactComponentTreeHook.getCurrentStackAddendum(element);
1743
- return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
1744
- }
1839
+ ReactDOM.render(<div className="a" />, node);
1840
+ var divID = ReactComponentTreeHook.getRootIDs()[0];
1841
+ expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0);
1842
1746
- var Anon = React.createClass({displayName: null, render: () => null});
1747
- var Orange = React.createClass({render: () => null});
1843
+ ReactDOM.render(<span className="a" />, node);
1844
+ var spanID = ReactComponentTreeHook.getRootIDs()[0];
1845
+ expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0);
1846
+ expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(0);
1847
1749
- expectDev(getAddendum()).toBe(
1750
- ''
1751
- );
1752
- expectDev(getAddendum(<div />)).toBe(
1753
- '\n in div (at **)'
1754
- );
1755
- expectDev(getAddendum(<Anon />)).toBe(
1756
- '\n in Unknown (at **)'
1757
- );
1758
- expectDev(getAddendum(<Orange />)).toBe(
1759
- '\n in Orange (at **)'
1760
- );
1761
- expectDev(getAddendum(React.createElement(Orange))).toBe(
1762
- '\n in Orange'
1763
- );
1848
+ ReactDOM.render(<span className="b" />, node);
1849
+ expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0);
1850
+ expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(1);
1851
1765
- var renders = 0;
1766
- var rOwnedByQ;
1852
+ ReactDOM.render(<span className="c" />, node);
1853
+ expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0);
1854
+ expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(2);
1855
1768
- function Q() {
1769
- return (rOwnedByQ = React.createElement(R));
1770
- }
1771
- function R() {
1772
- return <div><S /></div>;
1773
- }
1774
- class S extends React.Component {
1775
- componentDidMount() {
1776
- // Check that the parent path is still fetched when only S itself is on
1777
- // the stack.
1778
- this.forceUpdate();
1779
- }
1780
- render() {
1781
- expectDev(getAddendum()).toBe(
1782
- '\n in S (at **)' +
1783
- '\n in div (at **)' +
1784
- '\n in R (created by Q)' +
1785
- '\n in Q (at **)'
1786
- );
1787
- expectDev(getAddendum(<span />)).toBe(
1788
- '\n in span (at **)' +
1789
- '\n in S (at **)' +
1790
- '\n in div (at **)' +
1791
- '\n in R (created by Q)' +
1792
- '\n in Q (at **)'
1793
- );
1794
- expectDev(getAddendum(React.createElement('span'))).toBe(
1795
- '\n in span (created by S)' +
1796
- '\n in S (at **)' +
1797
- '\n in div (at **)' +
1798
- '\n in R (created by Q)' +
1799
- '\n in Q (at **)'
1800
- );
1801
- renders++;
1802
- return null;
1803
- }
1804
- }
1805
- ReactDOM.render(<Q />, document.createElement('div'));
1806
- expectDev(renders).toBe(2);
1807
-
1808
- // Make sure owner is fetched for the top element too.
1809
- expectDev(getAddendum(rOwnedByQ)).toBe(
1810
- '\n in R (created by Q)'
1811
- );
1856
+ ReactDOM.unmountComponentAtNode(node);
1857
+ expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0);
1858
+ expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(0);
1859
});
1860
1814
- it('can be retrieved by ID', () => {
1815
- function getAddendum(id) {
1816
- var addendum = ReactComponentTreeHook.getStackAddendumByID(id);
1817
- return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
1818
- }
1861
+ it('does not report top-level wrapper as a root', () => {
1862
+ var node = document.createElement('div');
1863
1820
- class Q extends React.Component {
1821
- render() {
1822
- return null;
1823
- }
1824
- }
1864
+ ReactDOM.render(<div className="a" />, node);
1865
+ expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']);
1866
1826
- var q = ReactDOM.render(<Q />, document.createElement('div'));
1827
- expectDev(getAddendum(ReactInstanceMap.get(q)._debugID)).toBe(
1828
- '\n in Q (at **)'
1829
- );
1867
+ ReactDOM.render(<div className="b" />, node);
1868
+ expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual(['div']);
1869
1831
- spyOn(console, 'error');
1832
- getAddendum(-17);
1833
- expectDev(console.error.calls.count()).toBe(1);
1834
- expectDev(console.error.calls.argsFor(0)[0]).toBe(
1835
- 'Warning: ReactComponentTreeHook: Missing React element for ' +
1836
- 'debugID -17 when building stack'
1837
- );
1870
+ ReactDOM.unmountComponentAtNode(node);
1871
+ expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]);
1872
+ expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]);
1873
});
1874
1840
- it('is created during mounting', () => {
1841
- // https://github.com/facebook/react/issues/7187
1842
- var el = document.createElement('div');
1843
- var portalEl = document.createElement('div');
1844
- class Foo extends React.Component {
1845
- componentWillMount() {
1846
- ReactDOM.render(<div />, portalEl);
1847
- }
1848
- render() {
1849
- return <div><div /></div>;
1850
- }
1851
- }
1852
- ReactDOM.render(<Foo />, el);
1853
- });
1875
+ it('registers inlined text nodes', () => {
1876
+ var node = document.createElement('div');
1877
1855
- it('is created when calling renderToString during render', () => {
1856
- // https://github.com/facebook/react/issues/7190
1857
- var el = document.createElement('div');
1858
- class Foo extends React.Component {
1859
- render() {
1860
- return (
1861
- <div>
1862
- <div>
1863
- {ReactDOMServer.renderToString(<div />)}
1864
- </div>
1865
- </div>
1866
- );
1867
- }
1868
- }
1869
- ReactDOM.render(<Foo />, el);
1878
+ ReactDOM.render(<div>hi</div>, node);
1879
+ expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual(['div', '#text']);
1880
+
1881
+ ReactDOM.unmountComponentAtNode(node);
1882
+ expectDev(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]);
1883
});
1884
});
1885
1873
- describe('in environment without Map, Set and Array.from', () => {
1886
+ describeStack('in environment without Map, Set and Array.from', () => {
1887
var realMap;
1888
var realSet;
1889
var realArrayFrom;