@samitouri / QOS-React / commits / 4b64d7c017

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>