Support configurable node/tree collapsed by default behavior
Brian Vaughn committed
Apr 16, 2019 at 13:59 UTC
4b64d7c01715b9ed6fc70b757cdf8cdc0b03b503
6 files changed
+521
-151
src/__tests__/__snapshots__/store-test.js.snap
+143
-16
@@ -1,6 +1,6 @@
1
// Jest Snapshot v1, https://goo.gl/fbAQLP
2
3
-exports[`Store should display Suspense nodes properly in various states: 1: loading 1`] = `
3
+exports[`Store collapseNodesByDefault:false should display Suspense nodes properly in various states: 1: loading 1`] = `
4
[root]
5
▾ <Wrapper>
6
<Component key="Outside">
@@ -8,7 +8,7 @@ exports[`Store should display Suspense nodes properly in various states: 1: load
8
<Loading>
9
`;
10
11
-exports[`Store should display Suspense nodes properly in various states: 2: resolved 1`] = `
11
+exports[`Store collapseNodesByDefault:false should display Suspense nodes properly in various states: 2: resolved 1`] = `
12
[root]
13
▾ <Wrapper>
14
<Component key="Outside">
@@ -16,7 +16,7 @@ exports[`Store should display Suspense nodes properly in various states: 2: reso
16
<Component key="Inside">
17
`;
18
19
-exports[`Store should filter DOM nodes from the store tree: 1: mount 1`] = `
19
+exports[`Store collapseNodesByDefault:false should filter DOM nodes from the store tree: 1: mount 1`] = `
20
[root]
21
▾ <Grandparent>
22
▾ <Parent>
@@ -25,7 +25,7 @@ exports[`Store should filter DOM nodes from the store tree: 1: mount 1`] = `
25
<Child>
26
`;
27
28
-exports[`Store should support collapsing parts of the tree: 1: mount 1`] = `
28
+exports[`Store collapseNodesByDefault:false should support collapsing parts of the tree: 1: mount 1`] = `
29
[root]
30
▾ <Grandparent>
31
▾ <Parent>
@@ -36,7 +36,7 @@ exports[`Store should support collapsing parts of the tree: 1: mount 1`] = `
36
<Child key="1">
37
`;
38
39
-exports[`Store should support collapsing parts of the tree: 2: collapse first Parent 1`] = `
39
+exports[`Store collapseNodesByDefault:false should support collapsing parts of the tree: 2: collapse first Parent 1`] = `
40
[root]
41
▾ <Grandparent>
42
▸ <Parent>
@@ -45,14 +45,14 @@ exports[`Store should support collapsing parts of the tree: 2: collapse first Pa
45
<Child key="1">
46
`;
47
48
-exports[`Store should support collapsing parts of the tree: 3: collapse second Parent 1`] = `
48
+exports[`Store collapseNodesByDefault:false should support collapsing parts of the tree: 3: collapse second Parent 1`] = `
49
[root]
50
▾ <Grandparent>
51
▸ <Parent>
52
▸ <Parent>
53
`;
54
55
-exports[`Store should support collapsing parts of the tree: 4: expand first Parent 1`] = `
55
+exports[`Store collapseNodesByDefault:false should support collapsing parts of the tree: 4: expand first Parent 1`] = `
56
[root]
57
▾ <Grandparent>
58
▾ <Parent>
@@ -61,12 +61,12 @@ exports[`Store should support collapsing parts of the tree: 4: expand first Pare
61
▸ <Parent>
62
`;
63
64
-exports[`Store should support collapsing parts of the tree: 5: collapse Grandparent 1`] = `
64
+exports[`Store collapseNodesByDefault:false should support collapsing parts of the tree: 5: collapse Grandparent 1`] = `
65
[root]
66
▸ <Grandparent>
67
`;
68
69
-exports[`Store should support collapsing parts of the tree: 6: expand Grandparent 1`] = `
69
+exports[`Store collapseNodesByDefault:false should support collapsing parts of the tree: 6: expand Grandparent 1`] = `
70
[root]
71
▾ <Grandparent>
72
▾ <Parent>
@@ -75,7 +75,7 @@ exports[`Store should support collapsing parts of the tree: 6: expand Grandparen
75
▸ <Parent>
76
`;
77
78
-exports[`Store should support mount and update operations for multiple roots: 1: mount 1`] = `
78
+exports[`Store collapseNodesByDefault:false should support mount and update operations for multiple roots: 1: mount 1`] = `
79
[root]
80
▾ <Parent key="A">
81
<Child key="0">
@@ -87,7 +87,7 @@ exports[`Store should support mount and update operations for multiple roots: 1:
87
<Child key="1">
88
`;
89
90
-exports[`Store should support mount and update operations for multiple roots: 2: update 1`] = `
90
+exports[`Store collapseNodesByDefault:false should support mount and update operations for multiple roots: 2: update 1`] = `
91
[root]
92
▾ <Parent key="A">
93
<Child key="0">
@@ -99,7 +99,7 @@ exports[`Store should support mount and update operations for multiple roots: 2:
99
<Child key="0">
100
`;
101
102
-exports[`Store should support mount and update operations for multiple roots: 3: unmount B 1`] = `
102
+exports[`Store collapseNodesByDefault:false should support mount and update operations for multiple roots: 3: unmount B 1`] = `
103
[root]
104
▾ <Parent key="A">
105
<Child key="0">
@@ -108,9 +108,9 @@ exports[`Store should support mount and update operations for multiple roots: 3:
108
<Child key="3">
109
`;
110
111
-exports[`Store should support mount and update operations for multiple roots: 4: unmount A 1`] = ``;
111
+exports[`Store collapseNodesByDefault:false should support mount and update operations for multiple roots: 4: unmount A 1`] = ``;
112
113
-exports[`Store should support mount and update operations: 1: mount 1`] = `
113
+exports[`Store collapseNodesByDefault:false should support mount and update operations: 1: mount 1`] = `
114
[root]
115
▾ <Grandparent>
116
▾ <Parent>
@@ -125,7 +125,7 @@ exports[`Store should support mount and update operations: 1: mount 1`] = `
125
<Child key="3">
126
`;
127
128
-exports[`Store should support mount and update operations: 2: update 1`] = `
128
+exports[`Store collapseNodesByDefault:false should support mount and update operations: 2: update 1`] = `
129
[root]
130
▾ <Grandparent>
131
▾ <Parent>
@@ -136,4 +136,131 @@ exports[`Store should support mount and update operations: 2: update 1`] = `
136
<Child key="1">
137
`;
138
139
-exports[`Store should support mount and update operations: 3: unmount 1`] = ``;
139
+exports[`Store collapseNodesByDefault:false should support mount and update operations: 3: unmount 1`] = ``;
140
+
141
+exports[`Store collapseNodesByDefault:true should display Suspense nodes properly in various states: 1: loading 1`] = `
142
+[root]
143
+ ▸ <Wrapper>
144
+`;
145
+
146
+exports[`Store collapseNodesByDefault:true should display Suspense nodes properly in various states: 2: expand Wrapper and Suspense 1`] = `
147
+[root]
148
+ ▾ <Wrapper>
149
+ <Component key="Outside">
150
+ ▾ <Suspense>
151
+ <Loading>
152
+`;
153
+
154
+exports[`Store collapseNodesByDefault:true should display Suspense nodes properly in various states: 2: resolved 1`] = `
155
+[root]
156
+ ▾ <Wrapper>
157
+ <Component key="Outside">
158
+ ▾ <Suspense>
159
+ <Component key="Inside">
160
+`;
161
+
162
+exports[`Store collapseNodesByDefault:true should filter DOM nodes from the store tree: 1: mount 1`] = `
163
+[root]
164
+ ▸ <Grandparent>
165
+`;
166
+
167
+exports[`Store collapseNodesByDefault:true should filter DOM nodes from the store tree: 2: expand Grandparent 1`] = `
168
+[root]
169
+ ▾ <Grandparent>
170
+ ▸ <Parent>
171
+ ▸ <Parent>
172
+`;
173
+
174
+exports[`Store collapseNodesByDefault:true should filter DOM nodes from the store tree: 3: expand Parent 1`] = `
175
+[root]
176
+ ▾ <Grandparent>
177
+ ▾ <Parent>
178
+ <Child>
179
+ ▸ <Parent>
180
+`;
181
+
182
+exports[`Store collapseNodesByDefault:true should support collapsing parts of the tree: 1: mount 1`] = `
183
+[root]
184
+ ▸ <Grandparent>
185
+`;
186
+
187
+exports[`Store collapseNodesByDefault:true should support collapsing parts of the tree: 2: expand Grandparent 1`] = `
188
+[root]
189
+ ▾ <Grandparent>
190
+ ▸ <Parent>
191
+ ▸ <Parent>
192
+`;
193
+
194
+exports[`Store collapseNodesByDefault:true should support collapsing parts of the tree: 3: expand first Parent 1`] = `
195
+[root]
196
+ ▾ <Grandparent>
197
+ ▾ <Parent>
198
+ <Child key="0">
199
+ <Child key="1">
200
+ ▸ <Parent>
201
+`;
202
+
203
+exports[`Store collapseNodesByDefault:true should support collapsing parts of the tree: 4: expand second Parent 1`] = `
204
+[root]
205
+ ▾ <Grandparent>
206
+ ▾ <Parent>
207
+ <Child key="0">
208
+ <Child key="1">
209
+ ▾ <Parent>
210
+ <Child key="0">
211
+ <Child key="1">
212
+`;
213
+
214
+exports[`Store collapseNodesByDefault:true should support collapsing parts of the tree: 5: collapse first Parent 1`] = `
215
+[root]
216
+ ▾ <Grandparent>
217
+ ▸ <Parent>
218
+ ▾ <Parent>
219
+ <Child key="0">
220
+ <Child key="1">
221
+`;
222
+
223
+exports[`Store collapseNodesByDefault:true should support collapsing parts of the tree: 6: collapse second Parent 1`] = `
224
+[root]
225
+ ▾ <Grandparent>
226
+ ▸ <Parent>
227
+ ▸ <Parent>
228
+`;
229
+
230
+exports[`Store collapseNodesByDefault:true should support collapsing parts of the tree: 7: collapse Grandparent 1`] = `
231
+[root]
232
+ ▸ <Grandparent>
233
+`;
234
+
235
+exports[`Store collapseNodesByDefault:true should support mount and update operations for multiple roots: 1: mount 1`] = `
236
+[root]
237
+ ▸ <Parent key="A">
238
+[root]
239
+ ▸ <Parent key="B">
240
+`;
241
+
242
+exports[`Store collapseNodesByDefault:true should support mount and update operations for multiple roots: 2: update 1`] = `
243
+[root]
244
+ ▸ <Parent key="A">
245
+[root]
246
+ ▸ <Parent key="B">
247
+`;
248
+
249
+exports[`Store collapseNodesByDefault:true should support mount and update operations for multiple roots: 3: unmount B 1`] = `
250
+[root]
251
+ ▸ <Parent key="A">
252
+`;
253
+
254
+exports[`Store collapseNodesByDefault:true should support mount and update operations for multiple roots: 4: unmount A 1`] = ``;
255
+
256
+exports[`Store collapseNodesByDefault:true should support mount and update operations: 1: mount 1`] = `
257
+[root]
258
+ ▸ <Grandparent>
259
+`;
260
+
261
+exports[`Store collapseNodesByDefault:true should support mount and update operations: 2: update 1`] = `
262
+[root]
263
+ ▸ <Grandparent>
264
+`;
265
+
266
+exports[`Store collapseNodesByDefault:true should support mount and update operations: 3: unmount 1`] = ``;
src/__tests__/store-test.js
+295
-113
@@ -21,142 +21,324 @@ describe('Store', () => {
21
TestUtils = require('react-dom/test-utils');
22
});
23
24
- it('should support mount and update operations', () => {
25
- const Grandparent = ({ count }) => (
26
- <React.Fragment>
27
- <Parent count={count} />
28
- <Parent count={count} />
29
- </React.Fragment>
30
- );
31
- const Parent = ({ count }) =>
32
- new Array(count).fill(true).map((_, index) => <Child key={index} />);
33
- const Child = () => <div>Hi!</div>;
34
-
35
- const container = document.createElement('div');
36
-
37
- act(() => ReactDOM.render(<Grandparent count={4} />, container));
38
- expect(store).toMatchSnapshot('1: mount');
39
-
40
- act(() => ReactDOM.render(<Grandparent count={2} />, container));
41
- expect(store).toMatchSnapshot('2: update');
42
-
43
- act(() => ReactDOM.unmountComponentAtNode(container));
44
- expect(store).toMatchSnapshot('3: unmount');
24
+ it('should not allow a root node to be collapsed', () => {
25
+ // TODO
26
});
27
47
- it('should support mount and update operations for multiple roots', () => {
48
- const Parent = ({ count }) =>
49
- new Array(count).fill(true).map((_, index) => <Child key={index} />);
50
- const Child = () => <div>Hi!</div>;
28
+ describe('collapseNodesByDefault:false', () => {
29
+ beforeEach(() => {
30
+ store.collapseNodesByDefault = false;
31
+ });
32
+
33
+ it('should support mount and update operations', () => {
34
+ const Grandparent = ({ count }) => (
35
+ <React.Fragment>
36
+ <Parent count={count} />
37
+ <Parent count={count} />
38
+ </React.Fragment>
39
+ );
40
+ const Parent = ({ count }) =>
41
+ new Array(count).fill(true).map((_, index) => <Child key={index} />);
42
+ const Child = () => <div>Hi!</div>;
43
+
44
+ const container = document.createElement('div');
45
+
46
+ act(() => ReactDOM.render(<Grandparent count={4} />, container));
47
+ expect(store).toMatchSnapshot('1: mount');
48
52
- const containerA = document.createElement('div');
53
- const containerB = document.createElement('div');
49
+ act(() => ReactDOM.render(<Grandparent count={2} />, container));
50
+ expect(store).toMatchSnapshot('2: update');
51
55
- act(() => {
56
- ReactDOM.render(<Parent key="A" count={3} />, containerA);
57
- ReactDOM.render(<Parent key="B" count={2} />, containerB);
52
+ act(() => ReactDOM.unmountComponentAtNode(container));
53
+ expect(store).toMatchSnapshot('3: unmount');
54
});
59
- expect(store).toMatchSnapshot('1: mount');
55
61
- act(() => {
62
- ReactDOM.render(<Parent key="A" count={4} />, containerA);
63
- ReactDOM.render(<Parent key="B" count={1} />, containerB);
56
+ it('should support mount and update operations for multiple roots', () => {
57
+ const Parent = ({ count }) =>
58
+ new Array(count).fill(true).map((_, index) => <Child key={index} />);
59
+ const Child = () => <div>Hi!</div>;
60
+
61
+ const containerA = document.createElement('div');
62
+ const containerB = document.createElement('div');
63
+
64
+ act(() => {
65
+ ReactDOM.render(<Parent key="A" count={3} />, containerA);
66
+ ReactDOM.render(<Parent key="B" count={2} />, containerB);
67
+ });
68
+ expect(store).toMatchSnapshot('1: mount');
69
+
70
+ act(() => {
71
+ ReactDOM.render(<Parent key="A" count={4} />, containerA);
72
+ ReactDOM.render(<Parent key="B" count={1} />, containerB);
73
+ });
74
+ expect(store).toMatchSnapshot('2: update');
75
+
76
+ act(() => ReactDOM.unmountComponentAtNode(containerB));
77
+ expect(store).toMatchSnapshot('3: unmount B');
78
+
79
+ act(() => ReactDOM.unmountComponentAtNode(containerA));
80
+ expect(store).toMatchSnapshot('4: unmount A');
81
});
65
- expect(store).toMatchSnapshot('2: update');
82
67
- act(() => ReactDOM.unmountComponentAtNode(containerB));
68
- expect(store).toMatchSnapshot('3: unmount B');
83
+ it('should filter DOM nodes from the store tree', () => {
84
+ const Grandparent = () => (
85
+ <div>
86
+ <div>
87
+ <Parent />
88
+ </div>
89
+ <Parent />
90
+ </div>
91
+ );
92
+ const Parent = () => (
93
+ <div>
94
+ <Child />
95
+ </div>
96
+ );
97
+ const Child = () => <div>Hi!</div>;
98
70
- act(() => ReactDOM.unmountComponentAtNode(containerA));
71
- expect(store).toMatchSnapshot('4: unmount A');
99
+ act(() =>
100
+ ReactDOM.render(
101
+ <Grandparent count={4} />,
102
+ document.createElement('div')
103
+ )
104
+ );
105
+ expect(store).toMatchSnapshot('1: mount');
106
+ });
107
+
108
+ it('should display Suspense nodes properly in various states', () => {
109
+ const Loading = () => <div>Loading...</div>;
110
+ const SuspendingComponent = () => {
111
+ throw new Promise(() => {});
112
+ };
113
+ const Component = () => {
114
+ return <div>Hello</div>;
115
+ };
116
+ const Wrapper = ({ shouldSuspense }) => (
117
+ <React.Fragment>
118
+ <Component key="Outside" />
119
+ <React.Suspense fallback={<Loading />}>
120
+ {shouldSuspense ? (
121
+ <SuspendingComponent />
122
+ ) : (
123
+ <Component key="Inside" />
124
+ )}
125
+ </React.Suspense>
126
+ </React.Fragment>
127
+ );
128
+
129
+ const container = document.createElement('div');
130
+ act(() => ReactDOM.render(<Wrapper shouldSuspense={true} />, container));
131
+ expect(store).toMatchSnapshot('1: loading');
132
+
133
+ act(() => {
134
+ ReactDOM.render(<Wrapper shouldSuspense={false} />, container);
135
+ });
136
+ expect(store).toMatchSnapshot('2: resolved');
137
+ });
138
+
139
+ it('should support collapsing parts of the tree', () => {
140
+ const Grandparent = ({ count }) => (
141
+ <React.Fragment>
142
+ <Parent count={count} />
143
+ <Parent count={count} />
144
+ </React.Fragment>
145
+ );
146
+ const Parent = ({ count }) =>
147
+ new Array(count).fill(true).map((_, index) => <Child key={index} />);
148
+ const Child = () => <div>Hi!</div>;
149
+
150
+ act(() =>
151
+ ReactDOM.render(
152
+ <Grandparent count={2} />,
153
+ document.createElement('div')
154
+ )
155
+ );
156
+ expect(store).toMatchSnapshot('1: mount');
157
+
158
+ const grandparentID = store.getElementIDAtIndex(0);
159
+ const parentOneID = store.getElementIDAtIndex(1);
160
+ const parentTwoID = store.getElementIDAtIndex(4);
161
+
162
+ act(() => store.toggleIsCollapsed(parentOneID, true));
163
+ expect(store).toMatchSnapshot('2: collapse first Parent');
164
+
165
+ act(() => store.toggleIsCollapsed(parentTwoID, true));
166
+ expect(store).toMatchSnapshot('3: collapse second Parent');
167
+
168
+ act(() => store.toggleIsCollapsed(parentOneID, false));
169
+ expect(store).toMatchSnapshot('4: expand first Parent');
170
+
171
+ act(() => store.toggleIsCollapsed(grandparentID, true));
172
+ expect(store).toMatchSnapshot('5: collapse Grandparent');
173
+
174
+ act(() => store.toggleIsCollapsed(grandparentID, false));
175
+ expect(store).toMatchSnapshot('6: expand Grandparent');
176
+ });
177
});
178
74
- it('should filter DOM nodes from the store tree', () => {
75
- const Grandparent = () => (
76
- <div>
179
+ describe('collapseNodesByDefault:true', () => {
180
+ beforeEach(() => {
181
+ store.collapseNodesByDefault = true;
182
+ });
183
+
184
+ it('should support mount and update operations', () => {
185
+ const Grandparent = ({ count }) => (
186
+ <React.Fragment>
187
+ <Parent count={count} />
188
+ <Parent count={count} />
189
+ </React.Fragment>
190
+ );
191
+ const Parent = ({ count }) =>
192
+ new Array(count).fill(true).map((_, index) => <Child key={index} />);
193
+ const Child = () => <div>Hi!</div>;
194
+
195
+ const container = document.createElement('div');
196
+
197
+ act(() => ReactDOM.render(<Grandparent count={4} />, container));
198
+ expect(store).toMatchSnapshot('1: mount');
199
+
200
+ act(() => ReactDOM.render(<Grandparent count={2} />, container));
201
+ expect(store).toMatchSnapshot('2: update');
202
+
203
+ act(() => ReactDOM.unmountComponentAtNode(container));
204
+ expect(store).toMatchSnapshot('3: unmount');
205
+ });
206
+
207
+ it('should support mount and update operations for multiple roots', () => {
208
+ const Parent = ({ count }) =>
209
+ new Array(count).fill(true).map((_, index) => <Child key={index} />);
210
+ const Child = () => <div>Hi!</div>;
211
+
212
+ const containerA = document.createElement('div');
213
+ const containerB = document.createElement('div');
214
+
215
+ act(() => {
216
+ ReactDOM.render(<Parent key="A" count={3} />, containerA);
217
+ ReactDOM.render(<Parent key="B" count={2} />, containerB);
218
+ });
219
+ expect(store).toMatchSnapshot('1: mount');
220
+
221
+ act(() => {
222
+ ReactDOM.render(<Parent key="A" count={4} />, containerA);
223
+ ReactDOM.render(<Parent key="B" count={1} />, containerB);
224
+ });
225
+ expect(store).toMatchSnapshot('2: update');
226
+
227
+ act(() => ReactDOM.unmountComponentAtNode(containerB));
228
+ expect(store).toMatchSnapshot('3: unmount B');
229
+
230
+ act(() => ReactDOM.unmountComponentAtNode(containerA));
231
+ expect(store).toMatchSnapshot('4: unmount A');
232
+ });
233
+
234
+ it('should filter DOM nodes from the store tree', () => {
235
+ const Grandparent = () => (
236
<div>
237
+ <div>
238
+ <Parent />
239
+ </div>
240
<Parent />
241
</div>
80
- <Parent />
81
- </div>
82
- );
83
- const Parent = () => (
84
- <div>
85
- <Child />
86
- </div>
87
- );
88
- const Child = () => <div>Hi!</div>;
89
-
90
- act(() =>
91
- ReactDOM.render(<Grandparent count={4} />, document.createElement('div'))
92
- );
93
- expect(store).toMatchSnapshot('1: mount');
94
- });
242
+ );
243
+ const Parent = () => (
244
+ <div>
245
+ <Child />
246
+ </div>
247
+ );
248
+ const Child = () => <div>Hi!</div>;
249
+
250
+ act(() =>
251
+ ReactDOM.render(
252
+ <Grandparent count={4} />,
253
+ document.createElement('div')
254
+ )
255
+ );
256
+ expect(store).toMatchSnapshot('1: mount');
257
96
- it('should display Suspense nodes properly in various states', () => {
97
- const Loading = () => <div>Loading...</div>;
98
- const SuspendingComponent = () => {
99
- throw new Promise(() => {});
100
- };
101
- const Component = () => {
102
- return <div>Hello</div>;
103
- };
104
- const Wrapper = ({ shouldSuspense }) => (
105
- <React.Fragment>
106
- <Component key="Outside" />
107
- <React.Suspense fallback={<Loading />}>
108
- {shouldSuspense ? (
109
- <SuspendingComponent />
110
- ) : (
111
- <Component key="Inside" />
112
- )}
113
- </React.Suspense>
114
- </React.Fragment>
115
- );
116
-
117
- const container = document.createElement('div');
118
- act(() => ReactDOM.render(<Wrapper shouldSuspense={true} />, container));
119
- expect(store).toMatchSnapshot('1: loading');
120
-
121
- act(() => {
122
- ReactDOM.render(<Wrapper shouldSuspense={false} />, container);
258
+ act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
259
+ expect(store).toMatchSnapshot('2: expand Grandparent');
260
+
261
+ act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(1), false));
262
+ expect(store).toMatchSnapshot('3: expand Parent');
263
});
124
- expect(store).toMatchSnapshot('2: resolved');
125
- });
264
127
- it('should support collapsing parts of the tree', () => {
128
- const Grandparent = ({ count }) => (
129
- <React.Fragment>
130
- <Parent count={count} />
131
- <Parent count={count} />
132
- </React.Fragment>
133
- );
134
- const Parent = ({ count }) =>
135
- new Array(count).fill(true).map((_, index) => <Child key={index} />);
136
- const Child = () => <div>Hi!</div>;
265
+ it('should display Suspense nodes properly in various states', () => {
266
+ const Loading = () => <div>Loading...</div>;
267
+ const SuspendingComponent = () => {
268
+ throw new Promise(() => {});
269
+ };
270
+ const Component = () => {
271
+ return <div>Hello</div>;
272
+ };
273
+ const Wrapper = ({ shouldSuspense }) => (
274
+ <React.Fragment>
275
+ <Component key="Outside" />
276
+ <React.Suspense fallback={<Loading />}>
277
+ {shouldSuspense ? (
278
+ <SuspendingComponent />
279
+ ) : (
280
+ <Component key="Inside" />
281
+ )}
282
+ </React.Suspense>
283
+ </React.Fragment>
284
+ );
285
+
286
+ const container = document.createElement('div');
287
+ act(() => ReactDOM.render(<Wrapper shouldSuspense={true} />, container));
288
+ expect(store).toMatchSnapshot('1: loading');
289
138
- act(() =>
139
- ReactDOM.render(<Grandparent count={2} />, document.createElement('div'))
140
- );
141
- expect(store).toMatchSnapshot('1: mount');
290
+ // This test isn't meaningful unless we expand the suspended tree
291
+ act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
292
+ act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(2), false));
293
+ expect(store).toMatchSnapshot('2: expand Wrapper and Suspense');
294
143
- const grandparentID = store.getElementIDAtIndex(0);
144
- const parentOneID = store.getElementIDAtIndex(1);
145
- const parentTwoID = store.getElementIDAtIndex(4);
295
+ act(() => {
296
+ ReactDOM.render(<Wrapper shouldSuspense={false} />, container);
297
+ });
298
+ expect(store).toMatchSnapshot('2: resolved');
299
+ });
300
+
301
+ it('should support collapsing parts of the tree', () => {
302
+ const Grandparent = ({ count }) => (
303
+ <React.Fragment>
304
+ <Parent count={count} />
305
+ <Parent count={count} />
306
+ </React.Fragment>
307
+ );
308
+ const Parent = ({ count }) =>
309
+ new Array(count).fill(true).map((_, index) => <Child key={index} />);
310
+ const Child = () => <div>Hi!</div>;
311
+
312
+ act(() =>
313
+ ReactDOM.render(
314
+ <Grandparent count={2} />,
315
+ document.createElement('div')
316
+ )
317
+ );
318
+ expect(store).toMatchSnapshot('1: mount');
319
+
320
+ const grandparentID = store.getElementIDAtIndex(0);
321
147
- act(() => store.toggleIsCollapsed(parentOneID, true));
148
- expect(store).toMatchSnapshot('2: collapse first Parent');
322
+ act(() => store.toggleIsCollapsed(grandparentID, false));
323
+ expect(store).toMatchSnapshot('2: expand Grandparent');
324
150
- act(() => store.toggleIsCollapsed(parentTwoID, true));
151
- expect(store).toMatchSnapshot('3: collapse second Parent');
325
+ const parentOneID = store.getElementIDAtIndex(1);
326
+ const parentTwoID = store.getElementIDAtIndex(2);
327
153
- act(() => store.toggleIsCollapsed(parentOneID, false));
154
- expect(store).toMatchSnapshot('4: expand first Parent');
328
+ act(() => store.toggleIsCollapsed(parentOneID, false));
329
+ expect(store).toMatchSnapshot('3: expand first Parent');
330
156
- act(() => store.toggleIsCollapsed(grandparentID, true));
157
- expect(store).toMatchSnapshot('5: collapse Grandparent');
331
+ act(() => store.toggleIsCollapsed(parentTwoID, false));
332
+ expect(store).toMatchSnapshot('4: expand second Parent');
333
159
- act(() => store.toggleIsCollapsed(grandparentID, false));
160
- expect(store).toMatchSnapshot('6: expand Grandparent');
334
+ act(() => store.toggleIsCollapsed(parentOneID, true));
335
+ expect(store).toMatchSnapshot('5: collapse first Parent');
336
+
337
+ act(() => store.toggleIsCollapsed(parentTwoID, true));
338
+ expect(store).toMatchSnapshot('6: collapse second Parent');
339
+
340
+ act(() => store.toggleIsCollapsed(grandparentID, true));
341
+ expect(store).toMatchSnapshot('7: collapse Grandparent');
342
+ });
343
});
344
});
src/__tests__/storeStress-test.js
+1
@@ -18,6 +18,7 @@ describe('StoreStress', () => {
18
beforeEach(() => {
19
bridge = global.bridge;
20
store = global.store;
21
+ store.collapseNodesByDefault = false;
22
23
React = require('react');
24
ReactDOM = require('react-dom');
src/devtools/store.js
+26
-2
@@ -36,6 +36,8 @@ const debug = (methodName, ...args) => {
36
37
const LOCAL_STORAGE_CAPTURE_SCREENSHOTS_KEY =
38
'React::DevTools::captureScreenshots';
39
+const LOCAL_STORAGE_COLLAPSE_ROOTS_BY_DEFAULT_KEY =
40
+ 'React::DevTools::collapseNodesByDefault';
41
42
const THROTTLE_CAPTURE_SCREENSHOT_DURATION = 500;
43
@@ -61,6 +63,9 @@ export default class Store extends EventEmitter {
63
64
_captureScreenshots: boolean = false;
65
66
+ // Should new nodes be collapsed by default when added to the tree?
67
+ _collapseNodesByDefault: boolean = true;
68
+
69
// At least one of the injected renderers contains (DEV only) owner metadata.
70
_hasOwnerMetadata: boolean = false;
71
@@ -123,6 +128,11 @@ export default class Store extends EventEmitter {
128
debug('constructor', 'subscribing to Bridge');
129
}
130
131
+ // Default this setting to true unless otherwise specified.
132
+ this._collapseNodesByDefault =
133
+ localStorage.getItem(LOCAL_STORAGE_COLLAPSE_ROOTS_BY_DEFAULT_KEY) !==
134
+ 'false';
135
+
136
if (config != null) {
137
const {
138
isProfiling,
@@ -178,6 +188,20 @@ export default class Store extends EventEmitter {
188
this.emit('captureScreenshots');
189
}
190
191
+ get collapseNodesByDefault(): boolean {
192
+ return this._collapseNodesByDefault;
193
+ }
194
+ set collapseNodesByDefault(value: boolean): void {
195
+ this._collapseNodesByDefault = value;
196
+
197
+ localStorage.setItem(
198
+ LOCAL_STORAGE_COLLAPSE_ROOTS_BY_DEFAULT_KEY,
199
+ value ? 'true' : 'false'
200
+ );
201
+
202
+ this.emit('collapseNodesByDefault');
203
+ }
204
+
205
get hasOwnerMetadata(): boolean {
206
return this._hasOwnerMetadata;
207
}
@@ -552,7 +576,7 @@ export default class Store extends EventEmitter {
576
depth: -1,
577
displayName: null,
578
id,
555
- isCollapsed: false,
579
+ isCollapsed: false, // Never collapse roots
580
key: null,
581
ownerID: 0,
582
parentID: 0,
@@ -601,7 +625,7 @@ export default class Store extends EventEmitter {
625
depth: parentElement.depth + 1,
626
displayName,
627
id,
604
- isCollapsed: false,
628
+ isCollapsed: this._collapseNodesByDefault,
629
key,
630
ownerID,
631
parentID: parentElement.id,
src/devtools/views/Settings/Settings.css
+3
-2
@@ -14,13 +14,14 @@
14
15
.Section {
16
display: flex;
17
- flex-direction: column;
17
+ flex-direction: row;
18
+ align-items: center;
19
margin-right: 0.5rem;
20
margin-bottom: 0.5rem;
21
}
22
23
.Header {
23
- margin-bottom: 0.5rem;
24
+ margin-right: 0.5rem;
25
font-size: var(--font-size-sans-large);
26
}
27
src/devtools/views/Settings/Settings.js
+53
-18
@@ -15,7 +15,7 @@ function Settings(_: {||}) {
15
SettingsContext
16
);
17
18
- const subscription = useMemo(
18
+ const captureScreenshotsSubscription = useMemo(
19
() => ({
20
getCurrentValue: () => store.captureScreenshots,
21
subscribe: (callback: Function) => {
@@ -25,7 +25,23 @@ function Settings(_: {||}) {
25
}),
26
[store]
27
);
28
- const captureScreenshots = useSubscription<boolean, Store>(subscription);
28
+ const captureScreenshots = useSubscription<boolean, Store>(
29
+ captureScreenshotsSubscription
30
+ );
31
+
32
+ const collapseNodesByDefaultSubscription = useMemo(
33
+ () => ({
34
+ getCurrentValue: () => store.collapseNodesByDefault,
35
+ subscribe: (callback: Function) => {
36
+ store.addListener('collapseNodesByDefault', callback);
37
+ return () => store.removeListener('collapseNodesByDefault', callback);
38
+ },
39
+ }),
40
+ [store]
41
+ );
42
+ const collapseNodesByDefault = useSubscription<boolean, Store>(
43
+ collapseNodesByDefaultSubscription
44
+ );
45
46
const updateDisplayDensity = useCallback(
47
({ currentTarget }) => {
@@ -47,6 +63,12 @@ function Settings(_: {||}) {
63
},
64
[store]
65
);
66
+ const updateCollapseNodesByDefault = useCallback(
67
+ ({ currentTarget }) => {
68
+ store.collapseNodesByDefault = currentTarget.checked;
69
+ },
70
+ [store]
71
+ );
72
73
return (
74
<div className={styles.Settings}>
@@ -85,6 +107,17 @@ function Settings(_: {||}) {
107
</label>
108
</div>
109
</div>
110
+ <div className={styles.Section}>
111
+ <div className={styles.Header}>Components tree</div>
112
+ <label>
113
+ <input
114
+ type="checkbox"
115
+ checked={collapseNodesByDefault}
116
+ onChange={updateCollapseNodesByDefault}
117
+ />{' '}
118
+ Collapse tree by default
119
+ </label>
120
+ </div>
121
<div className={styles.Section}>
122
<div className={styles.Header}>Display density</div>
123
<div className={styles.OptionGroup}>
@@ -111,22 +144,24 @@ function Settings(_: {||}) {
144
</div>
145
</div>
146
{store.supportsCaptureScreenshots && (
114
- <div className={styles.Section}>
115
- <div className={styles.Header}>Profiler</div>
116
- <label>
117
- <input
118
- type="checkbox"
119
- checked={captureScreenshots}
120
- onChange={updateCaptureScreenshotsWhileProfiling}
121
- />{' '}
122
- Capture screenshots while profiling
123
- {captureScreenshots && (
124
- <p className={styles.ScreenshotThrottling}>
125
- Screenshots will be throttled in order to reduce the negative
126
- impact on performance.
127
- </p>
128
- )}
129
- </label>
147
+ <div>
148
+ <div className={styles.Section}>
149
+ <div className={styles.Header}>Profiler</div>
150
+ <label>
151
+ <input
152
+ type="checkbox"
153
+ checked={captureScreenshots}
154
+ onChange={updateCaptureScreenshotsWhileProfiling}
155
+ />{' '}
156
+ Capture screenshots while profiling
157
+ </label>
158
+ </div>
159
+ {captureScreenshots && (
160
+ <div className={styles.ScreenshotThrottling}>
161
+ Screenshots will be throttled in order to reduce the negative
162
+ impact on performance.
163
+ </div>
164
+ )}
165
</div>
166
)}
167
</div>