@samitouri / QOS-React-2 / commits / 178e89927a

Fixed some edge cases with collapsed by default. Still some bugs existing.

Brian Vaughn committed Apr 17, 2019 at 07:45 UTC 178e89927a4f5322311a1f00ca0e771bd7d358ca
5 files changed +199 -43
src/__tests__/__snapshots__/store-test.js.snap
+54 -9
@@ -179,19 +179,62 @@ exports[`Store collapseNodesByDefault:true should filter DOM nodes from the stor
179 ▸ <Parent>
180 `;
181
182 -exports[`Store collapseNodesByDefault:true should support collapsing parts of the tree: 1: mount 1`] = `
182 +exports[`Store collapseNodesByDefault:true should support expanding deep parts of the tree: 1: mount 1`] = `
183 +[root]
184 + ▸ <Wrapper>
185 +`;
186 +
187 +exports[`Store collapseNodesByDefault:true should support expanding deep parts of the tree: 2: expand deepest node 1`] = `
188 +[root]
189 + ▾ <Wrapper>
190 + ▾ <Nested>
191 + ▾ <Nested>
192 + ▾ <Nested>
193 + <Nested>
194 +`;
195 +
196 +exports[`Store collapseNodesByDefault:true should support expanding deep parts of the tree: 3: collapse root 1`] = `
197 +[root]
198 + ▸ <Wrapper>
199 +`;
200 +
201 +exports[`Store collapseNodesByDefault:true should support expanding deep parts of the tree: 4: expand root 1`] = `
202 +[root]
203 + ▾ <Wrapper>
204 + ▾ <Nested>
205 + ▾ <Nested>
206 + ▾ <Nested>
207 + <Nested>
208 +`;
209 +
210 +exports[`Store collapseNodesByDefault:true should support expanding deep parts of the tree: 5: collapse middle node 1`] = `
211 +[root]
212 + ▾ <Wrapper>
213 + ▸ <Nested>
214 +`;
215 +
216 +exports[`Store collapseNodesByDefault:true should support expanding deep parts of the tree: 6: expand middle node 1`] = `
217 +[root]
218 + ▾ <Wrapper>
219 + ▾ <Nested>
220 + ▾ <Nested>
221 + ▾ <Nested>
222 + <Nested>
223 +`;
224 +
225 +exports[`Store collapseNodesByDefault:true should support expanding parts of the tree: 1: mount 1`] = `
226 [root]
227 ▸ <Grandparent>
228 `;
229
187 -exports[`Store collapseNodesByDefault:true should support collapsing parts of the tree: 2: expand Grandparent 1`] = `
230 +exports[`Store collapseNodesByDefault:true should support expanding parts of the tree: 2: expand Grandparent 1`] = `
231 [root]
232 ▾ <Grandparent>
233 ▸ <Parent>
234 ▸ <Parent>
235 `;
236
194 -exports[`Store collapseNodesByDefault:true should support collapsing parts of the tree: 3: expand first Parent 1`] = `
237 +exports[`Store collapseNodesByDefault:true should support expanding parts of the tree: 3: expand first Parent 1`] = `
238 [root]
239 ▾ <Grandparent>
240 ▾ <Parent>
@@ -200,7 +243,7 @@ exports[`Store collapseNodesByDefault:true should support collapsing parts of th
243 ▸ <Parent>
244 `;
245
203 -exports[`Store collapseNodesByDefault:true should support collapsing parts of the tree: 4: expand second Parent 1`] = `
246 +exports[`Store collapseNodesByDefault:true should support expanding parts of the tree: 4: expand second Parent 1`] = `
247 [root]
248 ▾ <Grandparent>
249 ▾ <Parent>
@@ -211,7 +254,7 @@ exports[`Store collapseNodesByDefault:true should support collapsing parts of th
254 <Child key="1">
255 `;
256
214 -exports[`Store collapseNodesByDefault:true should support collapsing parts of the tree: 5: collapse first Parent 1`] = `
257 +exports[`Store collapseNodesByDefault:true should support expanding parts of the tree: 5: collapse first Parent 1`] = `
258 [root]
259 ▾ <Grandparent>
260 ▸ <Parent>
@@ -220,14 +263,14 @@ exports[`Store collapseNodesByDefault:true should support collapsing parts of th
263 <Child key="1">
264 `;
265
223 -exports[`Store collapseNodesByDefault:true should support collapsing parts of the tree: 6: collapse second Parent 1`] = `
266 +exports[`Store collapseNodesByDefault:true should support expanding parts of the tree: 6: collapse second Parent 1`] = `
267 [root]
268 ▾ <Grandparent>
269 ▸ <Parent>
270 ▸ <Parent>
271 `;
272
230 -exports[`Store collapseNodesByDefault:true should support collapsing parts of the tree: 7: collapse Grandparent 1`] = `
273 +exports[`Store collapseNodesByDefault:true should support expanding parts of the tree: 7: collapse Grandparent 1`] = `
274 [root]
275 ▸ <Grandparent>
276 `;
@@ -255,12 +298,14 @@ exports[`Store collapseNodesByDefault:true should support mount and update opera
298
299 exports[`Store collapseNodesByDefault:true should support mount and update operations: 1: mount 1`] = `
300 [root]
258 - ▸ <Grandparent>
301 + ▸ <Parent>
302 + ▸ <Parent>
303 `;
304
305 exports[`Store collapseNodesByDefault:true should support mount and update operations: 2: update 1`] = `
306 [root]
263 - ▸ <Grandparent>
307 + ▸ <Parent>
308 + ▸ <Parent>
309 `;
310
311 exports[`Store collapseNodesByDefault:true should support mount and update operations: 3: unmount 1`] = ``;
src/__tests__/setupTests.js
+4 -1
@@ -31,8 +31,11 @@ env.beforeEach(() => {
31 const agent = new Agent();
32 agent.addBridge(bridge);
33
34 - initBackend(global.__REACT_DEVTOOLS_GLOBAL_HOOK__, agent, global);
34 + const hook = global.__REACT_DEVTOOLS_GLOBAL_HOOK__;
35
36 + initBackend(hook, agent, global);
37 +
38 + global.agent = agent;
39 global.bridge = bridge;
40 global.store = new Store(bridge);
41 });
src/__tests__/store-test.js
+62 -9
@@ -195,22 +195,32 @@ describe('Store', () => {
195 });
196
197 it('should support mount and update operations', () => {
198 - const Grandparent = ({ count }) => (
199 - <React.Fragment>
200 - <Parent count={count} />
201 - <Parent count={count} />
202 - </React.Fragment>
203 - );
198 const Parent = ({ count }) =>
199 new Array(count).fill(true).map((_, index) => <Child key={index} />);
200 const Child = () => <div>Hi!</div>;
201
202 const container = document.createElement('div');
203
210 - act(() => ReactDOM.render(<Grandparent count={4} />, container));
204 + act(() =>
205 + ReactDOM.render(
206 + <React.Fragment>
207 + <Parent count={1} />
208 + <Parent count={3} />
209 + </React.Fragment>,
210 + container
211 + )
212 + );
213 expect(store).toMatchSnapshot('1: mount');
214
213 - act(() => ReactDOM.render(<Grandparent count={2} />, container));
215 + act(() =>
216 + ReactDOM.render(
217 + <React.Fragment>
218 + <Parent count={2} />
219 + <Parent count={1} />
220 + </React.Fragment>,
221 + container
222 + )
223 + );
224 expect(store).toMatchSnapshot('2: update');
225
226 act(() => ReactDOM.unmountComponentAtNode(container));
@@ -311,7 +321,7 @@ describe('Store', () => {
321 expect(store).toMatchSnapshot('2: resolved');
322 });
323
314 - it('should support collapsing parts of the tree', () => {
324 + it('should support expanding parts of the tree', () => {
325 const Grandparent = ({ count }) => (
326 <React.Fragment>
327 <Parent count={count} />
@@ -353,5 +363,48 @@ describe('Store', () => {
363 act(() => store.toggleIsCollapsed(grandparentID, true));
364 expect(store).toMatchSnapshot('7: collapse Grandparent');
365 });
366 +
367 + it('should support expanding deep parts of the tree', () => {
368 + const Wrapper = ({ forwardedRef }) => (
369 + <Nested depth={3} forwardedRef={forwardedRef} />
370 + );
371 + const Nested = ({ depth, forwardedRef }) =>
372 + depth > 0 ? (
373 + <Nested depth={depth - 1} forwardedRef={forwardedRef} />
374 + ) : (
375 + <div ref={forwardedRef} />
376 + );
377 +
378 + const ref = React.createRef();
379 +
380 + act(() =>
381 + ReactDOM.render(
382 + <Wrapper forwardedRef={ref} />,
383 + document.createElement('div')
384 + )
385 + );
386 + expect(store).toMatchSnapshot('1: mount');
387 +
388 + const deepestedNodeID = global.agent.getIDForNode(ref.current);
389 +
390 + act(() => store.toggleIsCollapsed(deepestedNodeID, false));
391 + expect(store).toMatchSnapshot('2: expand deepest node');
392 +
393 + const rootID = store.getElementIDAtIndex(0);
394 +
395 + act(() => store.toggleIsCollapsed(rootID, true));
396 + expect(store).toMatchSnapshot('3: collapse root');
397 +
398 + act(() => store.toggleIsCollapsed(rootID, false));
399 + expect(store).toMatchSnapshot('4: expand root');
400 +
401 + const id = store.getElementIDAtIndex(1);
402 +
403 + act(() => store.toggleIsCollapsed(id, true));
404 + expect(store).toMatchSnapshot('5: collapse middle node');
405 +
406 + act(() => store.toggleIsCollapsed(id, false));
407 + expect(store).toMatchSnapshot('6: expand middle node');
408 + });
409 });
410 });
src/devtools/store.js
+77 -19
@@ -315,9 +315,8 @@ export default class Store extends EventEmitter {
315
316 // Find the element in the tree using the weight of each node...
317 // Skip over the root itself, because roots aren't visible in the Elements tree.
318 - const firstChildID = ((root: any): Element).children[0];
319 - let currentElement = ((this._idToElement.get(firstChildID): any): Element);
320 - let currentWeight = rootWeight;
318 + let currentElement = ((root: any): Element);
319 + let currentWeight = rootWeight - 1;
320 while (index !== currentWeight) {
321 const numChildren = currentElement.children.length;
322 for (let i = 0; i < numChildren; i++) {
@@ -449,26 +448,67 @@ export default class Store extends EventEmitter {
448 // We do this to avoid mismatches on e.g. CommitTreeBuilder that would cause errors.
449 }
450
451 + // TODO Maybe split this into two methods: expand() and collapse()
452 toggleIsCollapsed(id: number, isCollapsed: boolean): void {
453 const element = this.getElementByID(id);
454 if (element !== null) {
455 - if (element.type === ElementTypeRoot) {
456 - throw Error('Root nodes cannot be collapsed');
457 - }
455 + if (isCollapsed) {
456 + if (element.type === ElementTypeRoot) {
457 + throw Error('Root nodes cannot be collapsed');
458 + }
459 +
460 + if (element.isCollapsed) {
461 + return;
462 + }
463
459 - const oldWeight = element.isCollapsed ? 1 : element.weight;
460 - element.isCollapsed = isCollapsed;
461 - const newWeight = element.isCollapsed ? 1 : element.weight;
462 - const weightDelta = newWeight - oldWeight;
464 + element.isCollapsed = true;
465
464 - this._weightAcrossRoots += weightDelta;
466 + const weightDelta = 1 - element.weight;
467
466 - let parentElement = this._idToElement.get(element.parentID);
467 - while (parentElement != null) {
468 - parentElement.weight += weightDelta;
469 - parentElement = this._idToElement.get(parentElement.parentID);
468 + let parentElement = ((this._idToElement.get(
469 + element.parentID
470 + ): any): Element);
471 + while (parentElement != null) {
472 + parentElement.weight += weightDelta;
473 + parentElement = this._idToElement.get(parentElement.parentID);
474 + }
475 + } else {
476 + let currentElement = element;
477 + while (currentElement != null) {
478 + const oldWeight = currentElement.isCollapsed
479 + ? 1
480 + : currentElement.weight;
481 + currentElement.isCollapsed = false;
482 + const newWeight = currentElement.isCollapsed
483 + ? 1
484 + : currentElement.weight;
485 + const weightDelta = newWeight - oldWeight;
486 +
487 + let parentElement = ((this._idToElement.get(
488 + currentElement.parentID
489 + ): any): Element);
490 + while (parentElement != null) {
491 + parentElement.weight += weightDelta;
492 + if (parentElement.isCollapsed) {
493 + break;
494 + }
495 + parentElement = this._idToElement.get(parentElement.parentID);
496 + }
497 +
498 + currentElement =
499 + currentElement.parentID !== 0
500 + ? this.getElementByID(currentElement.parentID)
501 + : null;
502 + }
503 }
504
505 + let weightAcrossRoots = 0;
506 + this._roots.forEach(rootID => {
507 + const { weight } = ((this.getElementByID(rootID): any): Element);
508 + weightAcrossRoots += weight;
509 + });
510 + this._weightAcrossRoots = weightAcrossRoots;
511 +
512 // The Tree context's search reducer expects an explicit list of ids for nodes that were added or removed.
513 // In this case, we can pass it empty arrays since nodes in a collapsed tree are still there (just hidden).
514 // Updating the selected search index later may require auto-expanding a collapsed subtree though.
@@ -867,19 +907,23 @@ export default class Store extends EventEmitter {
907
908 // Used for Jest snapshot testing.
909 // May also be useful for visually debugging the tree, so it lives on the Store.
870 - __toSnapshot = () => {
910 + __toSnapshot = (includeWeight: boolean = false) => {
911 const snapshotLines = [];
912
913 let rootWeight = 0;
914
915 this._roots.forEach(rootID => {
876 - snapshotLines.push('[root]');
877 -
916 const { weight } = ((this.getElementByID(rootID): any): Element);
917
918 + snapshotLines.push('[root]' + (includeWeight ? ` (${weight})` : ''));
919 +
920 for (let i = rootWeight; i < rootWeight + weight; i++) {
921 const element = ((this.getElementAtIndex(i): any): Element);
922
923 + if (element == null) {
924 + throw Error(`No element for index ${i}`);
925 + }
926 +
927 let prefix = ' ';
928 if (element.children.length > 0) {
929 prefix = element.isCollapsed ? '▸' : '▾';
@@ -890,15 +934,29 @@ export default class Store extends EventEmitter {
934 key = ` key="${element.key}"`;
935 }
936
937 + let suffix = '';
938 + if (includeWeight) {
939 + suffix = ` (${element.isCollapsed ? 1 : element.weight})`;
940 + }
941 +
942 snapshotLines.push(
943 `${' '.repeat(element.depth + 1)}${prefix} <${element.displayName ||
895 - 'null'}${key}>`
944 + 'null'}${key}>${suffix}`
945 );
946 }
947
948 rootWeight += weight;
949 });
950
951 + // Make sure the pretty-printed test align with the Store's reported number of total rows.
952 + if (rootWeight !== this._weightAcrossRoots) {
953 + throw Error(
954 + `Inconsistent store state. Individual root weights (${rootWeight}) do not match total weight (${
955 + this._weightAcrossRoots
956 + })`
957 + );
958 + }
959 +
960 return snapshotLines.join('\n');
961 };
962 }
src/devtools/views/Components/TreeContext.js
+2 -5
@@ -748,11 +748,8 @@ function TreeContextController({ children, viewElementSource }: Props) {
748
749 if (state.selectedElementID !== null) {
750 let element = store.getElementByID(state.selectedElementID);
751 - while (element !== null && element.parentID > 0) {
752 - element = ((store.getElementByID(element.parentID): any): Element);
753 - if (element.isCollapsed) {
754 - store.toggleIsCollapsed(element.id, false);
755 - }
751 + if (element !== null && element.parentID > 0) {
752 + store.toggleIsCollapsed(element.parentID, false);
753 }
754 }
755 }