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;