@samitouri / QOS-React-2 / commits / 6d94017c42

DevTools: Make it easier to write tests for inspected elements (#20655)

And also add a few tests for which also cover a recent bugfix that just landed

Brian Vaughn committed Jan 25, 2021 at 11:58 UTC 6d94017c4287195d8d6c5173ea663285820e9f61
1 file changed +323 -642
packages/react-devtools-shared/src/__tests__/inspectedElement-test.js
+323 -642
@@ -33,6 +33,8 @@ describe('InspectedElement', () => {
33 let TestUtilsAct;
34 let TestRendererAct;
35
36 + let testRendererInstance;
37 +
38 beforeEach(() => {
39 utils = require('./utils');
40 utils.beforeEachProfiling();
@@ -61,6 +63,9 @@ describe('InspectedElement', () => {
63 .StoreContext;
64 TreeContextController = require('react-devtools-shared/src/devtools/views/Components/TreeContext')
65 .TreeContextController;
66 +
67 + // Used by inspectElementAtIndex() helper function
68 + testRendererInstance = TestRenderer.create(null);
69 });
70
71 afterEach(() => {
@@ -87,16 +92,48 @@ describe('InspectedElement', () => {
92 </BridgeContext.Provider>
93 );
94
90 - function useInspectedElement(id: number) {
95 + function useInspectedElement() {
96 const {inspectedElement} = React.useContext(InspectedElementContext);
97 return inspectedElement;
98 }
99
95 - function useInspectElementPath(id: number) {
100 + function useInspectElementPath() {
101 const {inspectPaths} = React.useContext(InspectedElementContext);
102 return inspectPaths;
103 }
104
105 + function noop() {}
106 +
107 + async function inspectElementAtIndex(index, useCustomHook = noop) {
108 + let didFinish = false;
109 + let inspectedElement = null;
110 +
111 + function Suspender() {
112 + useCustomHook();
113 + inspectedElement = useInspectedElement();
114 + didFinish = true;
115 + return null;
116 + }
117 +
118 + const id = ((store.getElementIDAtIndex(index): any): number);
119 +
120 + await utils.actAsync(() => {
121 + testRendererInstance.update(
122 + <Contexts
123 + defaultSelectedElementID={id}
124 + defaultSelectedElementIndex={index}>
125 + <React.Suspense fallback={null}>
126 + <Suspender id={id} index={index} />
127 + </React.Suspense>
128 + </Contexts>,
129 + );
130 + }, false);
131 +
132 + expect(didFinish).toBe(true);
133 +
134 + return inspectedElement;
135 + }
136 +
137 it('should inspect the currently selected element', async done => {
138 const Example = () => {
139 const [count] = React.useState(1);
@@ -108,52 +145,29 @@ describe('InspectedElement', () => {
145 ReactDOM.render(<Example a={1} b="abc" />, container),
146 );
147
111 - const id = ((store.getElementIDAtIndex(0): any): number);
112 -
113 - let didFinish = false;
114 -
115 - function Suspender({target}) {
116 - const inspectedElement = useInspectedElement(id);
117 - expect(inspectedElement).toMatchInlineSnapshot(`
118 - Object {
119 - "context": null,
120 - "events": undefined,
121 - "hooks": Array [
122 - Object {
123 - "id": 0,
124 - "isStateEditable": true,
125 - "name": "State",
126 - "subHooks": Array [],
127 - "value": 1,
128 - },
129 - ],
130 - "id": 2,
131 - "owners": null,
132 - "props": Object {
133 - "a": 1,
134 - "b": "abc",
148 + const inspectedElement = await inspectElementAtIndex(0);
149 + expect(inspectedElement).toMatchInlineSnapshot(`
150 + Object {
151 + "context": null,
152 + "events": undefined,
153 + "hooks": Array [
154 + Object {
155 + "id": 0,
156 + "isStateEditable": true,
157 + "name": "State",
158 + "subHooks": Array [],
159 + "value": 1,
160 },
136 - "state": null,
137 - }
138 - `);
139 - didFinish = true;
140 - return null;
141 - }
142 -
143 - await utils.actAsync(
144 - () =>
145 - TestRenderer.create(
146 - <Contexts
147 - defaultSelectedElementID={id}
148 - defaultSelectedElementIndex={0}>
149 - <React.Suspense fallback={null}>
150 - <Suspender target={id} />
151 - </React.Suspense>
152 - </Contexts>,
153 - ),
154 - false,
155 - );
156 - expect(didFinish).toBe(true);
161 + ],
162 + "id": 2,
163 + "owners": null,
164 + "props": Object {
165 + "a": 1,
166 + "b": "abc",
167 + },
168 + "state": null,
169 + }
170 + `);
171
172 done();
173 });
@@ -215,57 +229,42 @@ describe('InspectedElement', () => {
229 ),
230 );
231
218 - const ids = [
232 + const cases = [
233 {
234 // <LegacyContextConsumer />
221 - id: ((store.getElementIDAtIndex(1): any): number),
235 + index: 1,
236 shouldHaveLegacyContext: true,
237 },
238 {
239 // <BoolContext.Consumer>
226 - id: ((store.getElementIDAtIndex(2): any): number),
240 + index: 2,
241 shouldHaveLegacyContext: false,
242 },
243 {
244 // <ModernContextType />
231 - id: ((store.getElementIDAtIndex(3): any): number),
245 + index: 3,
246 shouldHaveLegacyContext: false,
247 },
248 {
249 // <ModernContext.Consumer>
236 - id: ((store.getElementIDAtIndex(5): any): number),
250 + index: 5,
251 shouldHaveLegacyContext: false,
252 },
253 ];
254
241 - function Suspender({target, shouldHaveLegacyContext}) {
242 - const inspectedElement = useInspectedElement(target);
255 + for (let i = 0; i < cases.length; i++) {
256 + const {index, shouldHaveLegacyContext} = cases[i];
257
244 - expect(inspectedElement.context).not.toBe(null);
245 - expect(inspectedElement.hasLegacyContext).toBe(shouldHaveLegacyContext);
258 + // HACK: Recreate TestRenderer instance because we rely on default state values
259 + // from props like defaultSelectedElementID and it's easier to reset here than
260 + // to read the TreeDispatcherContext and update the selected ID that way.
261 + // We're testing the inspected values here, not the context wiring, so that's ok.
262 + testRendererInstance = TestRenderer.create(null);
263
247 - return null;
248 - }
264 + const inspectedElement = await inspectElementAtIndex(index);
265
250 - for (let i = 0; i < ids.length; i++) {
251 - const {id, shouldHaveLegacyContext} = ids[i];
252 -
253 - await utils.actAsync(
254 - () =>
255 - TestRenderer.create(
256 - <Contexts
257 - defaultSelectedElementID={id}
258 - defaultSelectedElementIndex={0}>
259 - <React.Suspense fallback={null}>
260 - <Suspender
261 - target={id}
262 - shouldHaveLegacyContext={shouldHaveLegacyContext}
263 - />
264 - </React.Suspense>
265 - </Contexts>,
266 - ),
267 - false,
268 - );
266 + expect(inspectedElement.context).not.toBe(null);
267 + expect(inspectedElement.hasLegacyContext).toBe(shouldHaveLegacyContext);
268 }
269 done();
270 });
@@ -279,26 +278,7 @@ describe('InspectedElement', () => {
278 false,
279 );
280
282 - const id = ((store.getElementIDAtIndex(0): any): number);
283 -
284 - let inspectedElement = null;
285 -
286 - function Suspender({target}) {
287 - inspectedElement = useInspectedElement(id);
288 - return null;
289 - }
290 -
291 - let renderer;
292 -
293 - await utils.actAsync(() => {
294 - renderer = TestRenderer.create(
295 - <Contexts defaultSelectedElementID={id} defaultSelectedElementIndex={0}>
296 - <React.Suspense fallback={null}>
297 - <Suspender target={id} />
298 - </React.Suspense>
299 - </Contexts>,
300 - );
301 - }, false);
281 + let inspectedElement = await inspectElementAtIndex(0);
282 expect(inspectedElement.props).toMatchInlineSnapshot(`
283 Object {
284 "a": 1,
@@ -321,19 +301,7 @@ describe('InspectedElement', () => {
301 jest.runOnlyPendingTimers();
302 await Promise.resolve();
303
324 - await utils.actAsync(
325 - () =>
326 - renderer.update(
327 - <Contexts
328 - defaultSelectedElementID={id}
329 - defaultSelectedElementIndex={0}>
330 - <React.Suspense fallback={null}>
331 - <Suspender target={id} />
332 - </React.Suspense>
333 - </Contexts>,
334 - ),
335 - false,
336 - );
304 + inspectedElement = await inspectElementAtIndex(0);
305 expect(inspectedElement.props).toMatchInlineSnapshot(`
306 Object {
307 "a": 2,
@@ -365,31 +333,9 @@ describe('InspectedElement', () => {
333 ),
334 );
335
368 - const id = ((store.getElementIDAtIndex(1): any): number);
369 -
370 - let inspectedElement = null;
371 -
372 - function Suspender({target}) {
373 - inspectedElement = useInspectedElement(target);
374 - return null;
375 - }
376 -
336 targetRenderCount = 0;
337
379 - let renderer;
380 - await utils.actAsync(
381 - () =>
382 - (renderer = TestRenderer.create(
383 - <Contexts
384 - defaultSelectedElementID={id}
385 - defaultSelectedElementIndex={1}>
386 - <React.Suspense fallback={null}>
387 - <Suspender target={id} />
388 - </React.Suspense>
389 - </Contexts>,
390 - )),
391 - false,
392 - );
338 + let inspectedElement = await inspectElementAtIndex(1);
339 expect(targetRenderCount).toBe(1);
340 expect(inspectedElement.props).toMatchInlineSnapshot(`
341 Object {
@@ -398,28 +344,14 @@ describe('InspectedElement', () => {
344 }
345 `);
346
401 - const initialInspectedElement = inspectedElement;
347 + const prevInspectedElement = inspectedElement;
348
349 targetRenderCount = 0;
404 - inspectedElement = null;
405 - await utils.actAsync(
406 - () =>
407 - renderer.update(
408 - <Contexts
409 - defaultSelectedElementID={id}
410 - defaultSelectedElementIndex={1}>
411 - <React.Suspense fallback={null}>
412 - <Suspender target={id} />
413 - </React.Suspense>
414 - </Contexts>,
415 - ),
416 - false,
417 - );
350 + inspectedElement = await inspectElementAtIndex(1);
351 expect(targetRenderCount).toBe(0);
419 - expect(inspectedElement).toEqual(initialInspectedElement);
352 + expect(inspectedElement).toEqual(prevInspectedElement);
353
354 targetRenderCount = 0;
422 - inspectedElement = null;
355
356 await utils.actAsync(
357 () =>
@@ -433,19 +365,7 @@ describe('InspectedElement', () => {
365 );
366
367 // Target should have been rendered once (by ReactDOM) and once by DevTools for inspection.
436 - await utils.actAsync(
437 - () =>
438 - renderer.update(
439 - <Contexts
440 - defaultSelectedElementID={id}
441 - defaultSelectedElementIndex={1}>
442 - <React.Suspense fallback={null}>
443 - <Suspender target={id} />
444 - </React.Suspense>
445 - </Contexts>,
446 - ),
447 - false,
448 - );
368 + inspectedElement = await inspectElementAtIndex(1);
369 expect(targetRenderCount).toBe(2);
370 expect(inspectedElement.props).toMatchInlineSnapshot(`
371 Object {
@@ -490,28 +410,7 @@ describe('InspectedElement', () => {
410 expect(console.warn).toHaveBeenCalledTimes(1);
411 expect(console.warn).toHaveBeenCalledWith('warn');
412
493 - const id = ((store.getElementIDAtIndex(0): any): number);
494 -
495 - let inspectedElement = null;
496 -
497 - function Suspender({target}) {
498 - inspectedElement = useInspectedElement(target);
499 - return null;
500 - }
501 -
502 - await utils.actAsync(
503 - () =>
504 - TestRenderer.create(
505 - <Contexts
506 - defaultSelectedElementID={id}
507 - defaultSelectedElementIndex={1}>
508 - <React.Suspense fallback={null}>
509 - <Suspender target={id} />
510 - </React.Suspense>
511 - </Contexts>,
512 - ),
513 - false,
514 - );
413 + const inspectedElement = await inspectElementAtIndex(0);
414
415 expect(inspectedElement).not.toBe(null);
416 expect(targetRenderCount).toBe(2);
@@ -546,27 +445,7 @@ describe('InspectedElement', () => {
445 ),
446 );
447
549 - const id = ((store.getElementIDAtIndex(0): any): number);
550 - let inspectedElement = null;
551 -
552 - function Suspender({target}) {
553 - inspectedElement = useInspectedElement(id);
554 - return null;
555 - }
556 -
557 - await utils.actAsync(
558 - () =>
559 - TestRenderer.create(
560 - <Contexts
561 - defaultSelectedElementID={id}
562 - defaultSelectedElementIndex={0}>
563 - <React.Suspense fallback={null}>
564 - <Suspender target={id} />
565 - </React.Suspense>
566 - </Contexts>,
567 - ),
568 - false,
569 - );
448 + const inspectedElement = await inspectElementAtIndex(0);
449
450 const {props} = (inspectedElement: any);
451 expect(props.boolean_false).toBe(false);
@@ -665,28 +544,7 @@ describe('InspectedElement', () => {
544 ),
545 );
546
668 - const id = ((store.getElementIDAtIndex(0): any): number);
669 -
670 - let inspectedElement = null;
671 -
672 - function Suspender({target}) {
673 - inspectedElement = useInspectedElement(id);
674 - return null;
675 - }
676 -
677 - await utils.actAsync(
678 - () =>
679 - TestRenderer.create(
680 - <Contexts
681 - defaultSelectedElementID={id}
682 - defaultSelectedElementIndex={0}>
683 - <React.Suspense fallback={null}>
684 - <Suspender target={id} />
685 - </React.Suspense>
686 - </Contexts>,
687 - ),
688 - false,
689 - );
547 + const inspectedElement = await inspectElementAtIndex(0);
548
549 const {
550 anonymous_fn,
@@ -875,28 +733,7 @@ describe('InspectedElement', () => {
733 ReactDOM.render(<Example prop={iterable} />, container),
734 );
735
878 - const id = ((store.getElementIDAtIndex(0): any): number);
879 -
880 - let inspectedElement = null;
881 -
882 - function Suspender({target}) {
883 - inspectedElement = useInspectedElement(id);
884 - return null;
885 - }
886 -
887 - await utils.actAsync(
888 - () =>
889 - TestRenderer.create(
890 - <Contexts
891 - defaultSelectedElementID={id}
892 - defaultSelectedElementIndex={0}>
893 - <React.Suspense fallback={null}>
894 - <Suspender target={id} />
895 - </React.Suspense>
896 - </Contexts>,
897 - ),
898 - false,
899 - );
736 + const inspectedElement = await inspectElementAtIndex(0);
737
738 const {prop} = (inspectedElement: any).props;
739 expect(prop[meta.inspectable]).toBe(false);
@@ -921,29 +758,7 @@ describe('InspectedElement', () => {
758 ReactDOM.render(<Example object={object} />, container),
759 );
760
924 - const id = ((store.getElementIDAtIndex(0): any): number);
925 -
926 - let inspectedElement = null;
927 -
928 - function Suspender({target}) {
929 - inspectedElement = useInspectedElement(id);
930 - return null;
931 - }
932 -
933 - await utils.actAsync(
934 - () =>
935 - TestRenderer.create(
936 - <Contexts
937 - defaultSelectedElementID={id}
938 - defaultSelectedElementIndex={0}>
939 - <React.Suspense fallback={null}>
940 - <Suspender target={id} />
941 - </React.Suspense>
942 - </Contexts>,
943 - ),
944 - false,
945 - );
946 -
761 + const inspectedElement = await inspectElementAtIndex(0);
762 expect(inspectedElement.props).toMatchInlineSnapshot(`
763 Object {
764 "object": Object {
@@ -970,28 +785,7 @@ describe('InspectedElement', () => {
785 ReactDOM.render(<Example object={object} />, container),
786 );
787
973 - const id = ((store.getElementIDAtIndex(0): any): number);
974 -
975 - let inspectedElement = null;
976 -
977 - function Suspender({target}) {
978 - inspectedElement = useInspectedElement(id);
979 - return null;
980 - }
981 -
982 - await utils.actAsync(
983 - () =>
984 - TestRenderer.create(
985 - <Contexts
986 - defaultSelectedElementID={id}
987 - defaultSelectedElementIndex={0}>
988 - <React.Suspense fallback={null}>
989 - <Suspender target={id} />
990 - </React.Suspense>
991 - </Contexts>,
992 - ),
993 - false,
994 - );
788 + const inspectedElement = await inspectElementAtIndex(0);
789
790 // TRICKY: Don't use toMatchInlineSnapshot() for this test!
791 // Our snapshot serializer relies on hasOwnProperty() for feature detection.
@@ -1026,38 +820,15 @@ describe('InspectedElement', () => {
820 ReactDOM.render(<Example data={new CustomData()} />, container),
821 );
822
1029 - const id = ((store.getElementIDAtIndex(0): any): number);
1030 -
1031 - let didFinish = false;
1032 -
1033 - function Suspender({target}) {
1034 - const inspectedElement = useInspectedElement(id);
1035 - expect(inspectedElement.props).toMatchInlineSnapshot(`
1036 - Object {
1037 - "data": Object {
1038 - "_number": 42,
1039 - "number": 42,
1040 - },
1041 - }
1042 - `);
1043 - didFinish = true;
1044 - return null;
1045 - }
1046 -
1047 - await utils.actAsync(
1048 - () =>
1049 - TestRenderer.create(
1050 - <Contexts
1051 - defaultSelectedElementID={id}
1052 - defaultSelectedElementIndex={0}>
1053 - <React.Suspense fallback={null}>
1054 - <Suspender target={id} />
1055 - </React.Suspense>
1056 - </Contexts>,
1057 - ),
1058 - false,
1059 - );
1060 - expect(didFinish).toBe(true);
823 + const inspectedElement = await inspectElementAtIndex(0);
824 + expect(inspectedElement.props).toMatchInlineSnapshot(`
825 + Object {
826 + "data": Object {
827 + "_number": 42,
828 + "number": 42,
829 + },
830 + }
831 + `);
832
833 done();
834 });
@@ -1130,29 +901,7 @@ describe('InspectedElement', () => {
901 ReactDOM.render(<Example object={object} />, container),
902 );
903
1133 - const id = ((store.getElementIDAtIndex(0): any): number);
1134 -
1135 - let inspectedElement = null;
1136 -
1137 - function Suspender({target}) {
1138 - inspectedElement = useInspectedElement(id);
1139 - return null;
1140 - }
1141 -
1142 - await utils.actAsync(
1143 - () =>
1144 - TestRenderer.create(
1145 - <Contexts
1146 - defaultSelectedElementID={id}
1147 - defaultSelectedElementIndex={0}>
1148 - <React.Suspense fallback={null}>
1149 - <Suspender target={id} />
1150 - </React.Suspense>
1151 - </Contexts>,
1152 - ),
1153 - false,
1154 - );
1155 -
904 + const inspectedElement = await inspectElementAtIndex(0);
905 expect(inspectedElement.props).toMatchInlineSnapshot(`
906 Object {
907 "object": Object {
@@ -1193,44 +942,23 @@ describe('InspectedElement', () => {
942 value: NaN,
943 writable: true,
944 enumerable: true,
1196 - configurable: true,
1197 - },
1198 - d: {
1199 - value: 'normal',
1200 - writable: true,
1201 - enumerable: true,
1202 - configurable: true,
1203 - },
1204 - },
1205 - );
1206 - const Example = ({data}) => null;
1207 - const container = document.createElement('div');
1208 - await utils.actAsync(() =>
1209 - ReactDOM.render(<Example data={testData} />, container),
1210 - );
1211 -
1212 - const id = ((store.getElementIDAtIndex(0): any): number);
1213 -
1214 - let inspectedElement = null;
1215 -
1216 - function Suspender({target}) {
1217 - inspectedElement = useInspectedElement(target);
1218 - return null;
1219 - }
1220 -
1221 - await utils.actAsync(
1222 - () =>
1223 - TestRenderer.create(
1224 - <Contexts
1225 - defaultSelectedElementID={id}
1226 - defaultSelectedElementIndex={0}>
1227 - <React.Suspense fallback={null}>
1228 - <Suspender target={id} />
1229 - </React.Suspense>
1230 - </Contexts>,
1231 - ),
1232 - false,
945 + configurable: true,
946 + },
947 + d: {
948 + value: 'normal',
949 + writable: true,
950 + enumerable: true,
951 + configurable: true,
952 + },
953 + },
954 + );
955 + const Example = ({data}) => null;
956 + const container = document.createElement('div');
957 + await utils.actAsync(() =>
958 + ReactDOM.render(<Example data={testData} />, container),
959 );
960 +
961 + const inspectedElement = await inspectElementAtIndex(0);
962 expect(inspectedElement.props).toMatchInlineSnapshot(`
963 Object {
964 "data": Object {
@@ -1280,37 +1008,13 @@ describe('InspectedElement', () => {
1008 ),
1009 );
1010
1283 - const id = ((store.getElementIDAtIndex(0): any): number);
1284 -
1011 let inspectedElement = null;
1012 let inspectElementPath = null;
1013
1288 - function Suspender({path, target}) {
1289 - inspectedElement = useInspectedElement(id);
1290 - inspectElementPath = useInspectElementPath(id);
1291 - return null;
1292 - }
1293 -
1294 - const renderer = TestRenderer.create(null);
1295 -
1296 - async function getInspectedElement() {
1297 - await utils.actAsync(
1298 - () =>
1299 - renderer.update(
1300 - <Contexts
1301 - defaultSelectedElementID={id}
1302 - defaultSelectedElementIndex={0}>
1303 - <React.Suspense fallback={null}>
1304 - <Suspender target={id} />
1305 - </React.Suspense>
1306 - </Contexts>,
1307 - ),
1308 - false,
1309 - );
1310 - }
1311 -
1014 // Render once to get a handle on inspectElementPath()
1313 - await getInspectedElement();
1015 + inspectedElement = await inspectElementAtIndex(0, () => {
1016 + inspectElementPath = useInspectElementPath();
1017 + });
1018
1019 async function loadPath(path) {
1020 TestUtilsAct(() => {
@@ -1319,7 +1023,8 @@ describe('InspectedElement', () => {
1023 jest.runOnlyPendingTimers();
1024 });
1025 });
1322 - await getInspectedElement();
1026 +
1027 + inspectedElement = await inspectElementAtIndex(0);
1028 }
1029
1030 expect(inspectedElement.props).toMatchInlineSnapshot(`
@@ -1448,37 +1153,13 @@ describe('InspectedElement', () => {
1153 ),
1154 );
1155
1451 - const id = ((store.getElementIDAtIndex(0): any): number);
1452 -
1156 let inspectedElement = null;
1157 let inspectElementPath = null;
1158
1456 - function Suspender({path, target}) {
1457 - inspectedElement = useInspectedElement(id);
1458 - inspectElementPath = useInspectElementPath(id);
1459 - return null;
1460 - }
1461 -
1462 - const renderer = TestRenderer.create(null);
1463 -
1464 - async function getInspectedElement() {
1465 - await utils.actAsync(
1466 - () =>
1467 - renderer.update(
1468 - <Contexts
1469 - defaultSelectedElementID={id}
1470 - defaultSelectedElementIndex={0}>
1471 - <React.Suspense fallback={null}>
1472 - <Suspender target={id} />
1473 - </React.Suspense>
1474 - </Contexts>,
1475 - ),
1476 - false,
1477 - );
1478 - }
1479 -
1159 // Render once to get a handle on inspectElementPath()
1481 - await getInspectedElement();
1160 + inspectedElement = await inspectElementAtIndex(0, () => {
1161 + inspectElementPath = useInspectElementPath();
1162 + });
1163
1164 async function loadPath(path) {
1165 TestUtilsAct(() => {
@@ -1487,7 +1168,8 @@ describe('InspectedElement', () => {
1168 jest.runOnlyPendingTimers();
1169 });
1170 });
1490 - await getInspectedElement();
1171 +
1172 + inspectedElement = await inspectElementAtIndex(0);
1173 }
1174
1175 expect(inspectedElement.props).toMatchInlineSnapshot(`
@@ -1556,37 +1238,13 @@ describe('InspectedElement', () => {
1238 ),
1239 );
1240
1559 - const id = ((store.getElementIDAtIndex(0): any): number);
1560 -
1241 let inspectedElement = null;
1242 let inspectElementPath = null;
1243
1564 - function Suspender({path, target}) {
1565 - inspectedElement = useInspectedElement(id);
1566 - inspectElementPath = useInspectElementPath(id);
1567 - return null;
1568 - }
1569 -
1570 - const renderer = TestRenderer.create(null);
1571 -
1572 - async function getInspectedElement() {
1573 - await utils.actAsync(
1574 - () =>
1575 - renderer.update(
1576 - <Contexts
1577 - defaultSelectedElementID={id}
1578 - defaultSelectedElementIndex={0}>
1579 - <React.Suspense fallback={null}>
1580 - <Suspender target={id} />
1581 - </React.Suspense>
1582 - </Contexts>,
1583 - ),
1584 - false,
1585 - );
1586 - }
1587 -
1244 // Render once to get a handle on inspectElementPath()
1589 - await getInspectedElement();
1245 + inspectedElement = await inspectElementAtIndex(0, () => {
1246 + inspectElementPath = useInspectElementPath();
1247 + });
1248
1249 async function loadPath(path) {
1250 TestUtilsAct(() => {
@@ -1595,7 +1253,8 @@ describe('InspectedElement', () => {
1253 jest.runOnlyPendingTimers();
1254 });
1255 });
1598 - await getInspectedElement();
1256 +
1257 + inspectedElement = await inspectElementAtIndex(0);
1258 }
1259
1260 expect(inspectedElement.props).toMatchInlineSnapshot(`
@@ -1690,7 +1349,7 @@ describe('InspectedElement', () => {
1349 // Wait for pending poll-for-update and then update inspected element data.
1350 jest.runOnlyPendingTimers();
1351 await Promise.resolve();
1693 - await getInspectedElement();
1352 + inspectedElement = await inspectElementAtIndex(0);
1353
1354 expect(inspectedElement.props).toMatchInlineSnapshot(`
1355 Object {
@@ -1739,37 +1398,13 @@ describe('InspectedElement', () => {
1398 ),
1399 );
1400
1742 - const id = ((store.getElementIDAtIndex(0): any): number);
1743 -
1401 let inspectedElement = null;
1402 let inspectElementPath = null;
1403
1747 - function Suspender({path, target}) {
1748 - inspectedElement = useInspectedElement(id);
1749 - inspectElementPath = useInspectElementPath(id);
1750 - return null;
1751 - }
1752 -
1753 - const renderer = TestRenderer.create(null);
1754 -
1755 - async function getInspectedElement() {
1756 - await utils.actAsync(
1757 - () =>
1758 - renderer.update(
1759 - <Contexts
1760 - defaultSelectedElementID={id}
1761 - defaultSelectedElementIndex={0}>
1762 - <React.Suspense fallback={null}>
1763 - <Suspender target={id} />
1764 - </React.Suspense>
1765 - </Contexts>,
1766 - ),
1767 - false,
1768 - );
1769 - }
1770 -
1404 // Render once to get a handle on inspectElementPath()
1772 - await getInspectedElement();
1405 + inspectedElement = await inspectElementAtIndex(0, () => {
1406 + inspectElementPath = useInspectElementPath();
1407 + });
1408
1409 async function loadPath(path) {
1410 TestUtilsAct(() => {
@@ -1778,7 +1413,8 @@ describe('InspectedElement', () => {
1413 jest.runOnlyPendingTimers();
1414 });
1415 });
1781 - await getInspectedElement();
1416 +
1417 + inspectedElement = await inspectElementAtIndex(0);
1418 }
1419
1420 expect(inspectedElement.props).toMatchInlineSnapshot(`
@@ -1841,52 +1477,29 @@ describe('InspectedElement', () => {
1477 ReactDOM.render(<Example a={1} b="abc" />, container),
1478 );
1479
1844 - const id = ((store.getElementIDAtIndex(0): any): number);
1845 -
1846 - let didFinish = false;
1847 -
1848 - function Suspender({target}) {
1849 - const inspectedElement = useInspectedElement(id);
1850 - expect(inspectedElement).toMatchInlineSnapshot(`
1851 - Object {
1852 - "context": null,
1853 - "events": undefined,
1854 - "hooks": Array [
1855 - Object {
1856 - "id": null,
1857 - "isStateEditable": false,
1858 - "name": "Context",
1859 - "subHooks": Array [],
1860 - "value": true,
1861 - },
1862 - ],
1863 - "id": 2,
1864 - "owners": null,
1865 - "props": Object {
1866 - "a": 1,
1867 - "b": "abc",
1480 + const inspectedElement = await inspectElementAtIndex(0);
1481 + expect(inspectedElement).toMatchInlineSnapshot(`
1482 + Object {
1483 + "context": null,
1484 + "events": undefined,
1485 + "hooks": Array [
1486 + Object {
1487 + "id": null,
1488 + "isStateEditable": false,
1489 + "name": "Context",
1490 + "subHooks": Array [],
1491 + "value": true,
1492 },
1869 - "state": null,
1870 - }
1871 - `);
1872 - didFinish = true;
1873 - return null;
1874 - }
1875 -
1876 - await utils.actAsync(
1877 - () =>
1878 - TestRenderer.create(
1879 - <Contexts
1880 - defaultSelectedElementID={id}
1881 - defaultSelectedElementIndex={0}>
1882 - <React.Suspense fallback={null}>
1883 - <Suspender target={id} />
1884 - </React.Suspense>
1885 - </Contexts>,
1886 - ),
1887 - false,
1888 - );
1889 - expect(didFinish).toBe(true);
1493 + ],
1494 + "id": 2,
1495 + "owners": null,
1496 + "props": Object {
1497 + "a": 1,
1498 + "b": "abc",
1499 + },
1500 + "state": null,
1501 + }
1502 + `);
1503
1504 done();
1505 });
@@ -1913,46 +1526,30 @@ describe('InspectedElement', () => {
1526 ),
1527 );
1528
1916 - const id = ((store.getElementIDAtIndex(0): any): number);
1917 -
1529 let storeAsGlobal: StoreAsGlobal = ((null: any): StoreAsGlobal);
1530
1920 - function Suspender({target}) {
1921 - storeAsGlobal = (elementID: number, path: Array<string | number>) => {
1922 - const rendererID = store.getRendererIDForElement(elementID);
1531 + const id = ((store.getElementIDAtIndex(0): any): number);
1532 + await inspectElementAtIndex(0, () => {
1533 + storeAsGlobal = (path: Array<string | number>) => {
1534 + const rendererID = store.getRendererIDForElement(id);
1535 if (rendererID !== null) {
1536 const {
1537 storeAsGlobal: storeAsGlobalAPI,
1538 } = require('react-devtools-shared/src/backendAPI');
1539 storeAsGlobalAPI({
1540 bridge,
1929 - id: elementID,
1541 + id,
1542 path,
1543 rendererID,
1544 });
1545 }
1546 };
1935 - return null;
1936 - }
1937 -
1938 - await utils.actAsync(
1939 - () =>
1940 - TestRenderer.create(
1941 - <Contexts
1942 - defaultSelectedElementID={id}
1943 - defaultSelectedElementIndex={0}>
1944 - <React.Suspense fallback={null}>
1945 - <Suspender target={id} />
1946 - </React.Suspense>
1947 - </Contexts>,
1948 - ),
1949 - false,
1950 - );
1547 + });
1548
1549 jest.spyOn(console, 'log').mockImplementation(() => {});
1550
1551 // Should store the whole value (not just the hydrated parts)
1955 - storeAsGlobal(id, ['props', 'nestedObject']);
1552 + storeAsGlobal(['props', 'nestedObject']);
1553 jest.runOnlyPendingTimers();
1554 expect(console.log).toHaveBeenCalledWith('$reactTemp0');
1555 expect(global.$reactTemp0).toBe(nestedObject);
@@ -1960,7 +1557,7 @@ describe('InspectedElement', () => {
1557 console.log.mockReset();
1558
1559 // Should store the nested property specified (not just the outer value)
1963 - storeAsGlobal(id, ['props', 'nestedObject', 'a', 'b']);
1560 + storeAsGlobal(['props', 'nestedObject', 'a', 'b']);
1561 jest.runOnlyPendingTimers();
1562 expect(console.log).toHaveBeenCalledWith('$reactTemp1');
1563 expect(global.$reactTemp1).toBe(nestedObject.a.b);
@@ -1990,45 +1587,28 @@ describe('InspectedElement', () => {
1587 ),
1588 );
1589
1993 - const id = ((store.getElementIDAtIndex(0): any): number);
1994 -
1590 let copyPath: CopyInspectedElementPath = ((null: any): CopyInspectedElementPath);
1591
1997 - function Suspender({target}) {
1998 - copyPath = (elementID: number, path: Array<string | number>) => {
1999 - const rendererID = store.getRendererIDForElement(elementID);
1592 + const id = ((store.getElementIDAtIndex(0): any): number);
1593 + await inspectElementAtIndex(0, () => {
1594 + copyPath = (path: Array<string | number>) => {
1595 + const rendererID = store.getRendererIDForElement(id);
1596 if (rendererID !== null) {
1597 const {
1598 copyInspectedElementPath,
1599 } = require('react-devtools-shared/src/backendAPI');
1600 copyInspectedElementPath({
1601 bridge,
2006 - id: elementID,
1602 + id,
1603 path,
1604 rendererID,
1605 });
1606 }
1607 };
2012 - return null;
2013 - }
2014 -
2015 - await utils.actAsync(
2016 - () =>
2017 - TestRenderer.create(
2018 - <Contexts
2019 - defaultSelectedElementID={id}
2020 - defaultSelectedElementIndex={0}>
2021 - <React.Suspense fallback={null}>
2022 - <Suspender target={id} />
2023 - </React.Suspense>
2024 - </Contexts>,
2025 - ),
2026 - false,
2027 - );
2028 - expect(copyPath).not.toBeNull();
1608 + });
1609
1610 // Should copy the whole value (not just the hydrated parts)
2031 - copyPath(id, ['props', 'nestedObject']);
1611 + copyPath(['props', 'nestedObject']);
1612 jest.runOnlyPendingTimers();
1613 expect(global.mockClipboardCopy).toHaveBeenCalledTimes(1);
1614 expect(global.mockClipboardCopy).toHaveBeenCalledWith(
@@ -2038,7 +1618,7 @@ describe('InspectedElement', () => {
1618 global.mockClipboardCopy.mockReset();
1619
1620 // Should copy the nested property specified (not just the outer value)
2041 - copyPath(id, ['props', 'nestedObject', 'a', 'b']);
1621 + copyPath(['props', 'nestedObject', 'a', 'b']);
1622 jest.runOnlyPendingTimers();
1623 expect(global.mockClipboardCopy).toHaveBeenCalledTimes(1);
1624 expect(global.mockClipboardCopy).toHaveBeenCalledWith(
@@ -2098,48 +1678,32 @@ describe('InspectedElement', () => {
1678
1679 let copyPath: CopyInspectedElementPath = ((null: any): CopyInspectedElementPath);
1680
2101 - function Suspender({target}) {
2102 - copyPath = (elementID: number, path: Array<string | number>) => {
2103 - const rendererID = store.getRendererIDForElement(elementID);
1681 + await inspectElementAtIndex(0, () => {
1682 + copyPath = (path: Array<string | number>) => {
1683 + const rendererID = store.getRendererIDForElement(id);
1684 if (rendererID !== null) {
1685 const {
1686 copyInspectedElementPath,
1687 } = require('react-devtools-shared/src/backendAPI');
1688 copyInspectedElementPath({
1689 bridge,
2110 - id: elementID,
1690 + id,
1691 path,
1692 rendererID,
1693 });
1694 }
1695 };
2116 - return null;
2117 - }
2118 -
2119 - await utils.actAsync(
2120 - () =>
2121 - TestRenderer.create(
2122 - <Contexts
2123 - defaultSelectedElementID={id}
2124 - defaultSelectedElementIndex={0}>
2125 - <React.Suspense fallback={null}>
2126 - <Suspender target={id} />
2127 - </React.Suspense>
2128 - </Contexts>,
2129 - ),
2130 - false,
2131 - );
2132 - expect(copyPath).not.toBeNull();
1696 + });
1697
1698 // Should copy the whole value (not just the hydrated parts)
2135 - copyPath(id, ['props']);
1699 + copyPath(['props']);
1700 jest.runOnlyPendingTimers();
1701 // Should not error despite lots of unserialized values.
1702
1703 global.mockClipboardCopy.mockReset();
1704
1705 // Should copy the nested property specified (not just the outer value)
2142 - copyPath(id, ['props', 'bigInt']);
1706 + copyPath(['props', 'bigInt']);
1707 jest.runOnlyPendingTimers();
1708 expect(global.mockClipboardCopy).toHaveBeenCalledTimes(1);
1709 expect(global.mockClipboardCopy).toHaveBeenCalledWith(
@@ -2149,7 +1713,7 @@ describe('InspectedElement', () => {
1713 global.mockClipboardCopy.mockReset();
1714
1715 // Should copy the nested property specified (not just the outer value)
2152 - copyPath(id, ['props', 'typedArray']);
1716 + copyPath(['props', 'typedArray']);
1717 jest.runOnlyPendingTimers();
1718 expect(global.mockClipboardCopy).toHaveBeenCalledTimes(1);
1719 expect(global.mockClipboardCopy).toHaveBeenCalledWith(
@@ -2160,12 +1724,6 @@ describe('InspectedElement', () => {
1724 });
1725
1726 it('should display complex values of useDebugValue', async done => {
2163 - let inspectedElement = null;
2164 - function Suspender({target}) {
2165 - inspectedElement = useInspectedElement(target);
2166 - return null;
2167 - }
2168 -
1727 const container = document.createElement('div');
1728
1729 function useDebuggableHook() {
@@ -2182,23 +1740,7 @@ describe('InspectedElement', () => {
1740 ReactDOM.render(<DisplayedComplexValue />, container),
1741 );
1742
2185 - const ignoredComplexValueIndex = 0;
2186 - const ignoredComplexValueId = ((store.getElementIDAtIndex(
2187 - ignoredComplexValueIndex,
2188 - ): any): number);
2189 - await utils.actAsync(
2190 - () =>
2191 - TestRenderer.create(
2192 - <Contexts
2193 - defaultSelectedElementID={ignoredComplexValueId}
2194 - defaultSelectedElementIndex={ignoredComplexValueIndex}>
2195 - <React.Suspense fallback={null}>
2196 - <Suspender target={ignoredComplexValueId} />
2197 - </React.Suspense>
2198 - </Contexts>,
2199 - ),
2200 - false,
2201 - );
1743 + const inspectedElement = await inspectElementAtIndex(0);
1744 expect(inspectedElement.hooks).toMatchInlineSnapshot(`
1745 Array [
1746 Object {
@@ -2224,6 +1766,145 @@ describe('InspectedElement', () => {
1766 done();
1767 });
1768
1769 + describe('$r', () => {
1770 + it('should support function components', async done => {
1771 + const Example = () => {
1772 + const [count] = React.useState(1);
1773 + return count;
1774 + };
1775 +
1776 + const container = document.createElement('div');
1777 + await utils.actAsync(() =>
1778 + ReactDOM.render(<Example a={1} b="abc" />, container),
1779 + );
1780 +
1781 + await inspectElementAtIndex(0);
1782 +
1783 + expect(global.$r).toMatchInlineSnapshot(`
1784 + Object {
1785 + "hooks": Array [
1786 + Object {
1787 + "id": 0,
1788 + "isStateEditable": true,
1789 + "name": "State",
1790 + "subHooks": Array [],
1791 + "value": 1,
1792 + },
1793 + ],
1794 + "props": Object {
1795 + "a": 1,
1796 + "b": "abc",
1797 + },
1798 + "type": [Function],
1799 + }
1800 + `);
1801 +
1802 + done();
1803 + });
1804 +
1805 + it('should support memoized function components', async done => {
1806 + const Example = React.memo(function Example(props) {
1807 + const [count] = React.useState(1);
1808 + return count;
1809 + });
1810 +
1811 + const container = document.createElement('div');
1812 + await utils.actAsync(() =>
1813 + ReactDOM.render(<Example a={1} b="abc" />, container),
1814 + );
1815 +
1816 + await inspectElementAtIndex(0);
1817 +
1818 + expect(global.$r).toMatchInlineSnapshot(`
1819 + Object {
1820 + "hooks": Array [
1821 + Object {
1822 + "id": 0,
1823 + "isStateEditable": true,
1824 + "name": "State",
1825 + "subHooks": Array [],
1826 + "value": 1,
1827 + },
1828 + ],
1829 + "props": Object {
1830 + "a": 1,
1831 + "b": "abc",
1832 + },
1833 + "type": [Function],
1834 + }
1835 + `);
1836 +
1837 + done();
1838 + });
1839 +
1840 + it('should support forward refs', async done => {
1841 + const Example = React.forwardRef(function Example(props, ref) {
1842 + const [count] = React.useState(1);
1843 + return count;
1844 + });
1845 +
1846 + const container = document.createElement('div');
1847 + await utils.actAsync(() =>
1848 + ReactDOM.render(<Example a={1} b="abc" />, container),
1849 + );
1850 +
1851 + await inspectElementAtIndex(0);
1852 +
1853 + expect(global.$r).toMatchInlineSnapshot(`
1854 + Object {
1855 + "hooks": Array [
1856 + Object {
1857 + "id": 0,
1858 + "isStateEditable": true,
1859 + "name": "State",
1860 + "subHooks": Array [],
1861 + "value": 1,
1862 + },
1863 + ],
1864 + "props": Object {
1865 + "a": 1,
1866 + "b": "abc",
1867 + },
1868 + "type": [Function],
1869 + }
1870 + `);
1871 +
1872 + done();
1873 + });
1874 +
1875 + it('should support class components', async done => {
1876 + class Example extends React.Component {
1877 + state = {
1878 + count: 0,
1879 + };
1880 + render() {
1881 + return null;
1882 + }
1883 + }
1884 +
1885 + const container = document.createElement('div');
1886 + await utils.actAsync(() =>
1887 + ReactDOM.render(<Example a={1} b="abc" />, container),
1888 + );
1889 +
1890 + await inspectElementAtIndex(0);
1891 +
1892 + expect(global.$r.props).toMatchInlineSnapshot(`
1893 + Object {
1894 + "a": 1,
1895 + "b": "abc",
1896 + }
1897 + `);
1898 + expect(global.$r.state).toMatchInlineSnapshot(`
1899 + Object {
1900 + "count": 0,
1901 + }
1902 + `);
1903 +
1904 + done();
1905 + });
1906 + });
1907 +
1908 describe('inline errors and warnings', () => {
1909 // Some actions require the Fiber id.
1910 // In those instances you might want to make assertions based on the ID instead of the index.
@@ -2239,7 +1920,7 @@ describe('InspectedElement', () => {
1920 let warnings = null;
1921
1922 function Suspender({target}) {
2242 - const inspectedElement = useInspectedElement(id);
1923 + const inspectedElement = useInspectedElement();
1924 errors = inspectedElement.errors;
1925 warnings = inspectedElement.warnings;
1926 return null;