8
*/
9
10
import typeof ReactTestRenderer from 'react-test-renderer';
11
-import type {
12
- CopyInspectedElementPath,
13
- GetInspectedElementPath,
14
- StoreAsGlobal,
15
-} from 'react-devtools-shared/src/devtools/views/Components/InspectedElementContext';
11
+import {withErrorsOrWarningsIgnored} from 'react-devtools-shared/src/__tests__/utils';
12
+
13
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
14
import type Store from 'react-devtools-shared/src/devtools/store';
18
-import {withErrorsOrWarningsIgnored} from 'react-devtools-shared/src/__tests__/utils';
15
20
-describe('InspectedElementContext', () => {
16
+describe('InspectedElement', () => {
17
let React;
18
let ReactDOM;
19
let PropTypes;
77
<TreeContextController
78
defaultSelectedElementID={defaultSelectedElementID}
79
defaultSelectedElementIndex={defaultSelectedElementIndex}>
84
- <InspectedElementContextController>
85
- {children}
86
- </InspectedElementContextController>
80
+ <React.Suspense fallback="Loading...">
81
+ <InspectedElementContextController>
82
+ {children}
83
+ </InspectedElementContextController>
84
+ </React.Suspense>
85
</TreeContextController>
86
</StoreContext.Provider>
87
</BridgeContext.Provider>
88
);
89
90
+ function useInspectedElement(id: number) {
91
+ const {inspectedElement} = React.useContext(InspectedElementContext);
92
+ return inspectedElement;
93
+ }
94
+
95
+ function useInspectElementPath(id: number) {
96
+ const {inspectPaths} = React.useContext(InspectedElementContext);
97
+ return inspectPaths;
98
+ }
99
+
100
it('should inspect the currently selected element', async done => {
101
const Example = () => {
102
const [count] = React.useState(1);
113
let didFinish = false;
114
115
function Suspender({target}) {
108
- const {getInspectedElement} = React.useContext(InspectedElementContext);
109
- const inspectedElement = getInspectedElement(id);
110
- expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
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",
135
+ },
136
+ "state": null,
137
+ }
138
+ `);
139
didFinish = true;
140
return null;
141
}
239
];
240
241
function Suspender({target, shouldHaveLegacyContext}) {
214
- const {getInspectedElement} = React.useContext(InspectedElementContext);
215
- const inspectedElement = getInspectedElement(target);
242
+ const inspectedElement = useInspectedElement(target);
243
244
expect(inspectedElement.context).not.toBe(null);
245
expect(inspectedElement.hasLegacyContext).toBe(shouldHaveLegacyContext);
284
let inspectedElement = null;
285
286
function Suspender({target}) {
260
- const {getInspectedElement} = React.useContext(InspectedElementContext);
261
- inspectedElement = getInspectedElement(id);
287
+ inspectedElement = useInspectedElement(id);
288
return null;
289
}
290
299
</Contexts>,
300
);
301
}, false);
276
- expect(inspectedElement).toMatchSnapshot('1: initial render');
302
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
303
+ Object {
304
+ "a": 1,
305
+ "b": "abc",
306
+ }
307
+ `);
308
309
await utils.actAsync(
310
() => ReactDOM.render(<Example a={2} b="def" />, container),
311
false,
312
);
313
283
- inspectedElement = null;
314
+ // TODO (cache)
315
+ // This test only passes if both the check-for-updates poll AND the test renderer.update() call are included below.
316
+ // It seems like either one of the two should be sufficient but:
317
+ // 1. Running only check-for-updates schedules a transition that React never renders.
318
+ // 2. Running only renderer.update() loads stale data (first props)
319
+
320
+ // Wait for our check-for-updates poll to get the new data.
321
+ jest.runOnlyPendingTimers();
322
+ await Promise.resolve();
323
+
324
await utils.actAsync(
325
() =>
326
renderer.update(
334
),
335
false,
336
);
297
- expect(inspectedElement).toMatchSnapshot('2: updated state');
337
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
338
+ Object {
339
+ "a": 2,
340
+ "b": "def",
341
+ }
342
+ `);
343
344
done();
345
});
350
const Wrapper = ({children}) => children;
351
const Target = React.memo(props => {
352
targetRenderCount++;
353
+ // Even though his hook isn't referenced, it's used to observe backend rendering.
354
React.useState(0);
355
return null;
356
});
370
let inspectedElement = null;
371
372
function Suspender({target}) {
327
- const {getInspectedElement} = React.useContext(InspectedElementContext);
328
- inspectedElement = getInspectedElement(target);
373
+ inspectedElement = useInspectedElement(target);
374
return null;
375
}
376
391
false,
392
);
393
expect(targetRenderCount).toBe(1);
349
- expect(inspectedElement).toMatchSnapshot('1: initial render');
394
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
395
+ Object {
396
+ "a": 1,
397
+ "b": "abc",
398
+ }
399
+ `);
400
401
const initialInspectedElement = inspectedElement;
402
419
expect(inspectedElement).toEqual(initialInspectedElement);
420
421
targetRenderCount = 0;
422
+ inspectedElement = null;
423
424
await utils.actAsync(
425
() =>
433
);
434
435
// 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
+ );
449
expect(targetRenderCount).toBe(2);
386
- expect(inspectedElement).toMatchSnapshot('2: updated state');
450
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
451
+ Object {
452
+ "a": 2,
453
+ "b": "def",
454
+ }
455
+ `);
456
457
done();
458
});
495
let inspectedElement = null;
496
497
function Suspender({target}) {
429
- const {getInspectedElement} = React.useContext(InspectedElementContext);
430
- inspectedElement = getInspectedElement(target);
498
+ inspectedElement = useInspectedElement(target);
499
return null;
500
}
501
550
let inspectedElement = null;
551
552
function Suspender({target}) {
485
- const {getInspectedElement} = React.useContext(InspectedElementContext);
486
- inspectedElement = getInspectedElement(id);
553
+ inspectedElement = useInspectedElement(id);
554
return null;
555
}
556
568
false,
569
);
570
504
- expect(inspectedElement).not.toBeNull();
505
- expect(inspectedElement).toMatchSnapshot('1: Initial inspection');
506
-
571
const {props} = (inspectedElement: any);
572
expect(props.boolean_false).toBe(false);
573
expect(props.boolean_true).toBe(true);
670
let inspectedElement = null;
671
672
function Suspender({target}) {
609
- const {getInspectedElement} = React.useContext(InspectedElementContext);
610
- inspectedElement = getInspectedElement(id);
673
+ inspectedElement = useInspectedElement(id);
674
return null;
675
}
676
688
false,
689
);
690
628
- expect(inspectedElement).not.toBeNull();
629
- expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
630
-
691
const {
692
anonymous_fn,
693
array_buffer,
880
let inspectedElement = null;
881
882
function Suspender({target}) {
823
- const {getInspectedElement} = React.useContext(InspectedElementContext);
824
- inspectedElement = getInspectedElement(id);
883
+ inspectedElement = useInspectedElement(id);
884
return null;
885
}
886
898
false,
899
);
900
842
- expect(inspectedElement).not.toBeNull();
843
- expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
844
-
901
const {prop} = (inspectedElement: any).props;
902
expect(prop[meta.inspectable]).toBe(false);
903
expect(prop[meta.name]).toBe('Generator');
926
let inspectedElement = null;
927
928
function Suspender({target}) {
873
- const {getInspectedElement} = React.useContext(InspectedElementContext);
874
- inspectedElement = getInspectedElement(id);
929
+ inspectedElement = useInspectedElement(id);
930
return null;
931
}
932
944
false,
945
);
946
892
- expect(inspectedElement).not.toBeNull();
893
- expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
894
- expect(inspectedElement.props.object).toEqual({
895
- boolean: true,
896
- number: 123,
897
- string: 'abc',
898
- });
947
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
948
+ Object {
949
+ "object": Object {
950
+ "boolean": true,
951
+ "number": 123,
952
+ "string": "abc",
953
+ },
954
+ }
955
+ `);
956
957
done();
958
});
975
let inspectedElement = null;
976
977
function Suspender({target}) {
921
- const {getInspectedElement} = React.useContext(InspectedElementContext);
922
- inspectedElement = getInspectedElement(id);
978
+ inspectedElement = useInspectedElement(id);
979
return null;
980
}
981
993
false,
994
);
995
940
- expect(inspectedElement).not.toBeNull();
941
- expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
942
- expect(inspectedElement.props.object).toEqual({
943
- name: 'blah',
944
- hasOwnProperty: true,
945
- });
996
+ // TRICKY: Don't use toMatchInlineSnapshot() for this test!
997
+ // Our snapshot serializer relies on hasOwnProperty() for feature detection.
998
+ expect(inspectedElement.props.object.name).toBe('blah');
999
+ expect(inspectedElement.props.object.hasOwnProperty).toBe(true);
1000
1001
done();
1002
});
1031
let didFinish = false;
1032
1033
function Suspender({target}) {
980
- const {getInspectedElement} = React.useContext(InspectedElementContext);
981
- const inspectedElement = getInspectedElement(id);
982
- expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
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
}
1135
let inspectedElement = null;
1136
1137
function Suspender({target}) {
1078
- const {getInspectedElement} = React.useContext(InspectedElementContext);
1079
- inspectedElement = getInspectedElement(id);
1138
+ inspectedElement = useInspectedElement(id);
1139
return null;
1140
}
1141
1153
false,
1154
);
1155
1097
- expect(inspectedElement).not.toBeNull();
1098
- expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
1099
- expect(inspectedElement.props.object).toEqual({
1100
- 123: 3,
1101
- 'Symbol(enumerableSymbol)': 3,
1102
- 'Symbol(enumerableSymbolBase)': 1,
1103
- enumerableString: 2,
1104
- enumerableStringBase: 1,
1105
- });
1156
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
1157
+ Object {
1158
+ "object": Object {
1159
+ "123": 3,
1160
+ "Symbol(enumerableSymbol)": 3,
1161
+ "Symbol(enumerableSymbolBase)": 1,
1162
+ "enumerableString": 2,
1163
+ "enumerableStringBase": 1,
1164
+ },
1165
+ }
1166
+ `);
1167
1168
done();
1169
});
1205
1206
const id = ((store.getElementIDAtIndex(0): any): number);
1207
1147
- let getInspectedElementPath: GetInspectedElementPath = ((null: any): GetInspectedElementPath);
1208
let inspectedElement = null;
1209
+ let inspectElementPath = null;
1210
1150
- function Suspender({target}) {
1151
- const context = React.useContext(InspectedElementContext);
1152
- getInspectedElementPath = context.getInspectedElementPath;
1153
- inspectedElement = context.getInspectedElement(target);
1211
+ function Suspender({path, target}) {
1212
+ inspectedElement = useInspectedElement(id);
1213
+ inspectElementPath = useInspectElementPath(id);
1214
return null;
1215
}
1216
1157
- await utils.actAsync(
1158
- () =>
1159
- TestRenderer.create(
1160
- <Contexts
1161
- defaultSelectedElementID={id}
1162
- defaultSelectedElementIndex={0}>
1163
- <React.Suspense fallback={null}>
1164
- <Suspender target={id} />
1165
- </React.Suspense>
1166
- </Contexts>,
1167
- ),
1168
- false,
1169
- );
1170
- expect(getInspectedElementPath).not.toBeNull();
1171
- expect(inspectedElement).not.toBeNull();
1172
- expect(inspectedElement).toMatchSnapshot('1: Initially inspect element');
1217
+ const renderer = TestRenderer.create(null);
1218
1174
- inspectedElement = null;
1175
- TestUtilsAct(() => {
1176
- TestRendererAct(() => {
1177
- getInspectedElementPath(id, ['props', 'nestedObject', 'a']);
1178
- jest.runOnlyPendingTimers();
1179
- });
1180
- });
1181
- expect(inspectedElement).not.toBeNull();
1182
- expect(inspectedElement).toMatchSnapshot('2: Inspect props.nestedObject.a');
1219
+ async function getInspectedElement() {
1220
+ await utils.actAsync(
1221
+ () =>
1222
+ renderer.update(
1223
+ <Contexts
1224
+ defaultSelectedElementID={id}
1225
+ defaultSelectedElementIndex={0}>
1226
+ <React.Suspense fallback={null}>
1227
+ <Suspender target={id} />
1228
+ </React.Suspense>
1229
+ </Contexts>,
1230
+ ),
1231
+ false,
1232
+ );
1233
+ }
1234
1184
- inspectedElement = null;
1185
- TestUtilsAct(() => {
1186
- TestRendererAct(() => {
1187
- getInspectedElementPath(id, ['props', 'nestedObject', 'a', 'b', 'c']);
1188
- jest.runOnlyPendingTimers();
1189
- });
1190
- });
1191
- expect(inspectedElement).not.toBeNull();
1192
- expect(inspectedElement).toMatchSnapshot(
1193
- '3: Inspect props.nestedObject.a.b.c',
1194
- );
1235
+ // Render once to get a handle on inspectElementPath()
1236
+ await getInspectedElement();
1237
1196
- inspectedElement = null;
1197
- TestUtilsAct(() => {
1198
- TestRendererAct(() => {
1199
- getInspectedElementPath(id, [
1200
- 'props',
1201
- 'nestedObject',
1202
- 'a',
1203
- 'b',
1204
- 'c',
1205
- 0,
1206
- 'd',
1207
- ]);
1208
- jest.runOnlyPendingTimers();
1238
+ async function loadPath(path) {
1239
+ TestUtilsAct(() => {
1240
+ TestRendererAct(() => {
1241
+ inspectElementPath(path);
1242
+ jest.runOnlyPendingTimers();
1243
+ });
1244
});
1210
- });
1211
- expect(inspectedElement).not.toBeNull();
1212
- expect(inspectedElement).toMatchSnapshot(
1213
- '4: Inspect props.nestedObject.a.b.c.0.d',
1214
- );
1245
+ await getInspectedElement();
1246
+ }
1247
1216
- inspectedElement = null;
1217
- TestUtilsAct(() => {
1218
- TestRendererAct(() => {
1219
- getInspectedElementPath(id, ['hooks', 0, 'value']);
1220
- jest.runOnlyPendingTimers();
1221
- });
1222
- });
1223
- expect(inspectedElement).not.toBeNull();
1224
- expect(inspectedElement).toMatchSnapshot('5: Inspect hooks.0.value');
1248
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
1249
+ Object {
1250
+ "nestedObject": Object {
1251
+ "a": Dehydrated {
1252
+ "preview_short": {…},
1253
+ "preview_long": {b: {…}},
1254
+ },
1255
+ },
1256
+ }
1257
+ `);
1258
+
1259
+ await loadPath(['props', 'nestedObject', 'a']);
1260
+
1261
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
1262
+ Object {
1263
+ "nestedObject": Object {
1264
+ "a": Object {
1265
+ "b": Object {
1266
+ "c": Dehydrated {
1267
+ "preview_short": Array(1),
1268
+ "preview_long": [{…}],
1269
+ },
1270
+ },
1271
+ },
1272
+ },
1273
+ }
1274
+ `);
1275
+
1276
+ await loadPath(['props', 'nestedObject', 'a', 'b', 'c']);
1277
+
1278
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
1279
+ Object {
1280
+ "nestedObject": Object {
1281
+ "a": Object {
1282
+ "b": Object {
1283
+ "c": Array [
1284
+ Object {
1285
+ "d": Dehydrated {
1286
+ "preview_short": {…},
1287
+ "preview_long": {e: {…}},
1288
+ },
1289
+ },
1290
+ ],
1291
+ },
1292
+ },
1293
+ },
1294
+ }
1295
+ `);
1296
+
1297
+ await loadPath(['props', 'nestedObject', 'a', 'b', 'c', 0, 'd']);
1298
+
1299
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
1300
+ Object {
1301
+ "nestedObject": Object {
1302
+ "a": Object {
1303
+ "b": Object {
1304
+ "c": Array [
1305
+ Object {
1306
+ "d": Object {
1307
+ "e": Object {},
1308
+ },
1309
+ },
1310
+ ],
1311
+ },
1312
+ },
1313
+ },
1314
+ }
1315
+ `);
1316
1226
- inspectedElement = null;
1227
- TestUtilsAct(() => {
1228
- TestRendererAct(() => {
1229
- getInspectedElementPath(id, ['hooks', 0, 'value', 'foo', 'bar']);
1230
- jest.runOnlyPendingTimers();
1231
- });
1232
- });
1233
- expect(inspectedElement).not.toBeNull();
1234
- expect(inspectedElement).toMatchSnapshot(
1235
- '6: Inspect hooks.0.value.foo.bar',
1236
- );
1317
+ await loadPath(['hooks', 0, 'value']);
1318
+
1319
+ expect(inspectedElement.hooks).toMatchInlineSnapshot(`
1320
+ Array [
1321
+ Object {
1322
+ "id": 0,
1323
+ "isStateEditable": true,
1324
+ "name": "State",
1325
+ "subHooks": Array [],
1326
+ "value": Object {
1327
+ "foo": Object {
1328
+ "bar": Dehydrated {
1329
+ "preview_short": {…},
1330
+ "preview_long": {baz: "hi"},
1331
+ },
1332
+ },
1333
+ },
1334
+ },
1335
+ ]
1336
+ `);
1337
+
1338
+ await loadPath(['hooks', 0, 'value', 'foo', 'bar']);
1339
+
1340
+ expect(inspectedElement.hooks).toMatchInlineSnapshot(`
1341
+ Array [
1342
+ Object {
1343
+ "id": 0,
1344
+ "isStateEditable": true,
1345
+ "name": "State",
1346
+ "subHooks": Array [],
1347
+ "value": Object {
1348
+ "foo": Object {
1349
+ "bar": Object {
1350
+ "baz": "hi",
1351
+ },
1352
+ },
1353
+ },
1354
+ },
1355
+ ]
1356
+ `);
1357
1358
done();
1359
});
1373
1374
const id = ((store.getElementIDAtIndex(0): any): number);
1375
1256
- let getInspectedElementPath: GetInspectedElementPath = ((null: any): GetInspectedElementPath);
1376
let inspectedElement = null;
1377
+ let inspectElementPath = null;
1378
1259
- function Suspender({target}) {
1260
- const context = React.useContext(InspectedElementContext);
1261
- getInspectedElementPath = context.getInspectedElementPath;
1262
- inspectedElement = context.getInspectedElement(target);
1379
+ function Suspender({path, target}) {
1380
+ inspectedElement = useInspectedElement(id);
1381
+ inspectElementPath = useInspectElementPath(id);
1382
return null;
1383
}
1384
1266
- await utils.actAsync(
1267
- () =>
1268
- TestRenderer.create(
1269
- <Contexts
1270
- defaultSelectedElementID={id}
1271
- defaultSelectedElementIndex={0}>
1272
- <React.Suspense fallback={null}>
1273
- <Suspender target={id} />
1274
- </React.Suspense>
1275
- </Contexts>,
1276
- ),
1277
- false,
1278
- );
1279
- expect(getInspectedElementPath).not.toBeNull();
1280
- expect(inspectedElement).not.toBeNull();
1281
- expect(inspectedElement).toMatchSnapshot('1: Initially inspect element');
1385
+ const renderer = TestRenderer.create(null);
1386
1283
- inspectedElement = null;
1284
- TestUtilsAct(() => {
1285
- TestRendererAct(() => {
1286
- getInspectedElementPath(id, ['props', 'set_of_sets', 0]);
1287
- jest.runOnlyPendingTimers();
1387
+ async function getInspectedElement() {
1388
+ await utils.actAsync(
1389
+ () =>
1390
+ renderer.update(
1391
+ <Contexts
1392
+ defaultSelectedElementID={id}
1393
+ defaultSelectedElementIndex={0}>
1394
+ <React.Suspense fallback={null}>
1395
+ <Suspender target={id} />
1396
+ </React.Suspense>
1397
+ </Contexts>,
1398
+ ),
1399
+ false,
1400
+ );
1401
+ }
1402
+
1403
+ // Render once to get a handle on inspectElementPath()
1404
+ await getInspectedElement();
1405
+
1406
+ async function loadPath(path) {
1407
+ TestUtilsAct(() => {
1408
+ TestRendererAct(() => {
1409
+ inspectElementPath(path);
1410
+ jest.runOnlyPendingTimers();
1411
+ });
1412
});
1289
- });
1290
- expect(inspectedElement).not.toBeNull();
1291
- expect(inspectedElement).toMatchSnapshot('2: Inspect props.set_of_sets.0');
1413
+ await getInspectedElement();
1414
+ }
1415
+
1416
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
1417
+ Object {
1418
+ "set_of_sets": Object {
1419
+ "0": Dehydrated {
1420
+ "preview_short": Set(3),
1421
+ "preview_long": Set(3) {1, 2, 3},
1422
+ },
1423
+ "1": Dehydrated {
1424
+ "preview_short": Set(3),
1425
+ "preview_long": Set(3) {"a", "b", "c"},
1426
+ },
1427
+ },
1428
+ }
1429
+ `);
1430
+
1431
+ await loadPath(['props', 'set_of_sets', 0]);
1432
+
1433
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
1434
+ Object {
1435
+ "set_of_sets": Object {
1436
+ "0": Object {
1437
+ "0": 1,
1438
+ "1": 2,
1439
+ "2": 3,
1440
+ },
1441
+ "1": Object {
1442
+ "0": "a",
1443
+ "1": "b",
1444
+ "2": "c",
1445
+ },
1446
+ },
1447
+ }
1448
+ `);
1449
1450
done();
1451
});
1481
1482
const id = ((store.getElementIDAtIndex(0): any): number);
1483
1327
- let getInspectedElementPath: GetInspectedElementPath = ((null: any): GetInspectedElementPath);
1484
let inspectedElement = null;
1485
+ let inspectElementPath = null;
1486
1330
- function Suspender({target}) {
1331
- const context = React.useContext(InspectedElementContext);
1332
- getInspectedElementPath = context.getInspectedElementPath;
1333
- inspectedElement = context.getInspectedElement(id);
1487
+ function Suspender({path, target}) {
1488
+ inspectedElement = useInspectedElement(id);
1489
+ inspectElementPath = useInspectElementPath(id);
1490
return null;
1491
}
1492
1337
- await utils.actAsync(
1338
- () =>
1339
- TestRenderer.create(
1340
- <Contexts
1341
- defaultSelectedElementID={id}
1342
- defaultSelectedElementIndex={0}>
1343
- <React.Suspense fallback={null}>
1344
- <Suspender target={id} />
1345
- </React.Suspense>
1346
- </Contexts>,
1347
- ),
1348
- false,
1349
- );
1350
- expect(getInspectedElementPath).not.toBeNull();
1351
- expect(inspectedElement).not.toBeNull();
1352
- expect(inspectedElement).toMatchSnapshot('1: Initially inspect element');
1493
+ const renderer = TestRenderer.create(null);
1494
1354
- inspectedElement = null;
1355
- TestRendererAct(() => {
1356
- getInspectedElementPath(id, ['props', 'nestedObject', 'a']);
1357
- jest.runOnlyPendingTimers();
1358
- });
1359
- expect(inspectedElement).not.toBeNull();
1360
- expect(inspectedElement).toMatchSnapshot('2: Inspect props.nestedObject.a');
1495
+ async function getInspectedElement() {
1496
+ await utils.actAsync(
1497
+ () =>
1498
+ renderer.update(
1499
+ <Contexts
1500
+ defaultSelectedElementID={id}
1501
+ defaultSelectedElementIndex={0}>
1502
+ <React.Suspense fallback={null}>
1503
+ <Suspender target={id} />
1504
+ </React.Suspense>
1505
+ </Contexts>,
1506
+ ),
1507
+ false,
1508
+ );
1509
+ }
1510
1362
- inspectedElement = null;
1363
- TestRendererAct(() => {
1364
- getInspectedElementPath(id, ['props', 'nestedObject', 'c']);
1365
- jest.runOnlyPendingTimers();
1366
- });
1367
- expect(inspectedElement).not.toBeNull();
1368
- expect(inspectedElement).toMatchSnapshot('3: Inspect props.nestedObject.c');
1511
+ // Render once to get a handle on inspectElementPath()
1512
+ await getInspectedElement();
1513
+
1514
+ async function loadPath(path) {
1515
+ TestUtilsAct(() => {
1516
+ TestRendererAct(() => {
1517
+ inspectElementPath(path);
1518
+ jest.runOnlyPendingTimers();
1519
+ });
1520
+ });
1521
+ await getInspectedElement();
1522
+ }
1523
+
1524
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
1525
+ Object {
1526
+ "nestedObject": Object {
1527
+ "a": Dehydrated {
1528
+ "preview_short": {…},
1529
+ "preview_long": {b: {…}, value: 1},
1530
+ },
1531
+ "c": Dehydrated {
1532
+ "preview_short": {…},
1533
+ "preview_long": {d: {…}, value: 1},
1534
+ },
1535
+ },
1536
+ }
1537
+ `);
1538
+
1539
+ await loadPath(['props', 'nestedObject', 'a']);
1540
+
1541
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
1542
+ Object {
1543
+ "nestedObject": Object {
1544
+ "a": Object {
1545
+ "b": Object {
1546
+ "value": 1,
1547
+ },
1548
+ "value": 1,
1549
+ },
1550
+ "c": Object {
1551
+ "d": Dehydrated {
1552
+ "preview_short": {…},
1553
+ "preview_long": {e: {…}, value: 1},
1554
+ },
1555
+ "value": 1,
1556
+ },
1557
+ },
1558
+ }
1559
+ `);
1560
+
1561
+ await loadPath(['props', 'nestedObject', 'c']);
1562
+
1563
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
1564
+ Object {
1565
+ "nestedObject": Object {
1566
+ "a": Object {
1567
+ "b": Object {
1568
+ "value": 1,
1569
+ },
1570
+ "value": 1,
1571
+ },
1572
+ "c": Object {
1573
+ "d": Object {
1574
+ "e": Dehydrated {
1575
+ "preview_short": {…},
1576
+ "preview_long": {value: 1},
1577
+ },
1578
+ "value": 1,
1579
+ },
1580
+ "value": 1,
1581
+ },
1582
+ },
1583
+ }
1584
+ `);
1585
1586
TestRendererAct(() => {
1587
TestUtilsAct(() => {
1610
});
1611
});
1612
1397
- TestRendererAct(() => {
1398
- inspectedElement = null;
1399
- jest.advanceTimersByTime(1000);
1400
- });
1401
- expect(inspectedElement).not.toBeNull();
1402
- expect(inspectedElement).toMatchSnapshot('4: update inspected element');
1613
+ // Wait for pending poll-for-update and then update inspected element data.
1614
+ jest.runOnlyPendingTimers();
1615
+ await Promise.resolve();
1616
+ await getInspectedElement();
1617
+
1618
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
1619
+ Object {
1620
+ "nestedObject": Object {
1621
+ "a": Object {
1622
+ "b": Object {
1623
+ "value": 2,
1624
+ },
1625
+ "value": 2,
1626
+ },
1627
+ "c": Object {
1628
+ "d": Object {
1629
+ "e": Dehydrated {
1630
+ "preview_short": {…},
1631
+ "preview_long": {value: 2},
1632
+ },
1633
+ "value": 2,
1634
+ },
1635
+ "value": 2,
1636
+ },
1637
+ },
1638
+ }
1639
+ `);
1640
1641
done();
1642
});
1664
1665
const id = ((store.getElementIDAtIndex(0): any): number);
1666
1430
- let getInspectedElementPath: GetInspectedElementPath = ((null: any): GetInspectedElementPath);
1667
let inspectedElement = null;
1668
+ let inspectElementPath = null;
1669
1433
- function Suspender({target}) {
1434
- const context = React.useContext(InspectedElementContext);
1435
- getInspectedElementPath = context.getInspectedElementPath;
1436
- inspectedElement = context.getInspectedElement(id);
1670
+ function Suspender({path, target}) {
1671
+ inspectedElement = useInspectedElement(id);
1672
+ inspectElementPath = useInspectElementPath(id);
1673
return null;
1674
}
1675
1440
- await utils.actAsync(
1441
- () =>
1442
- TestRenderer.create(
1443
- <Contexts
1444
- defaultSelectedElementID={id}
1445
- defaultSelectedElementIndex={0}>
1446
- <React.Suspense fallback={null}>
1447
- <Suspender target={id} />
1448
- </React.Suspense>
1449
- </Contexts>,
1450
- ),
1451
- false,
1452
- );
1453
- expect(getInspectedElementPath).not.toBeNull();
1454
- expect(inspectedElement).not.toBeNull();
1455
- expect(inspectedElement).toMatchSnapshot('1: Initially inspect element');
1676
+ const renderer = TestRenderer.create(null);
1677
+
1678
+ async function getInspectedElement() {
1679
+ await utils.actAsync(
1680
+ () =>
1681
+ renderer.update(
1682
+ <Contexts
1683
+ defaultSelectedElementID={id}
1684
+ defaultSelectedElementIndex={0}>
1685
+ <React.Suspense fallback={null}>
1686
+ <Suspender target={id} />
1687
+ </React.Suspense>
1688
+ </Contexts>,
1689
+ ),
1690
+ false,
1691
+ );
1692
+ }
1693
+
1694
+ // Render once to get a handle on inspectElementPath()
1695
+ await getInspectedElement();
1696
+
1697
+ async function loadPath(path) {
1698
+ TestUtilsAct(() => {
1699
+ TestRendererAct(() => {
1700
+ inspectElementPath(path);
1701
+ jest.runOnlyPendingTimers();
1702
+ });
1703
+ });
1704
+ await getInspectedElement();
1705
+ }
1706
+
1707
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
1708
+ Object {
1709
+ "nestedObject": Object {
1710
+ "a": Dehydrated {
1711
+ "preview_short": {…},
1712
+ "preview_long": {b: {…}, value: 1},
1713
+ },
1714
+ "value": 1,
1715
+ },
1716
+ }
1717
+ `);
1718
1719
TestUtilsAct(() => {
1720
ReactDOM.render(
1733
);
1734
});
1735
1474
- inspectedElement = null;
1736
+ await loadPath(['props', 'nestedObject', 'a']);
1737
1476
- TestRendererAct(() => {
1477
- TestUtilsAct(() => {
1478
- getInspectedElementPath(id, ['props', 'nestedObject', 'a']);
1479
- jest.runOnlyPendingTimers();
1480
- });
1481
- });
1482
- expect(inspectedElement).not.toBeNull();
1483
- expect(inspectedElement).toMatchSnapshot('2: Inspect props.nestedObject.a');
1738
+ expect(inspectedElement.props).toMatchInlineSnapshot(`
1739
+ Object {
1740
+ "nestedObject": Object {
1741
+ "a": Object {
1742
+ "b": Object {
1743
+ "value": 2,
1744
+ },
1745
+ "value": 2,
1746
+ },
1747
+ "value": 2,
1748
+ },
1749
+ }
1750
+ `);
1751
1752
done();
1753
});
1769
let didFinish = false;
1770
1771
function Suspender({target}) {
1505
- const {getInspectedElement} = React.useContext(InspectedElementContext);
1506
- const inspectedElement = getInspectedElement(id);
1507
- expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
1772
+ const inspectedElement = useInspectedElement(id);
1773
+ expect(inspectedElement).toMatchInlineSnapshot(`
1774
+ Object {
1775
+ "context": null,
1776
+ "events": undefined,
1777
+ "hooks": Array [
1778
+ Object {
1779
+ "id": null,
1780
+ "isStateEditable": false,
1781
+ "name": "Context",
1782
+ "subHooks": Array [],
1783
+ "value": true,
1784
+ },
1785
+ ],
1786
+ "id": 2,
1787
+ "owners": null,
1788
+ "props": Object {
1789
+ "a": 1,
1790
+ "b": "abc",
1791
+ },
1792
+ "state": null,
1793
+ }
1794
+ `);
1795
didFinish = true;
1796
return null;
1797
}
1841
let storeAsGlobal: StoreAsGlobal = ((null: any): StoreAsGlobal);
1842
1843
function Suspender({target}) {
1557
- const context = React.useContext(InspectedElementContext);
1558
- storeAsGlobal = context.storeAsGlobal;
1844
+ storeAsGlobal = (elementID: number, path: Array<string | number>) => {
1845
+ const rendererID = store.getRendererIDForElement(elementID);
1846
+ if (rendererID !== null) {
1847
+ const {
1848
+ storeAsGlobal: storeAsGlobalAPI,
1849
+ } = require('react-devtools-shared/src/backendAPI');
1850
+ storeAsGlobalAPI({
1851
+ bridge,
1852
+ id: elementID,
1853
+ path,
1854
+ rendererID,
1855
+ });
1856
+ }
1857
+ };
1858
return null;
1859
}
1860
1871
),
1872
false,
1873
);
1575
- expect(storeAsGlobal).not.toBeNull();
1874
1875
jest.spyOn(console, 'log').mockImplementation(() => {});
1876
1877
// Should store the whole value (not just the hydrated parts)
1878
storeAsGlobal(id, ['props', 'nestedObject']);
1879
jest.runOnlyPendingTimers();
1582
- expect(console.log).toHaveBeenCalledWith('$reactTemp1');
1583
- expect(global.$reactTemp1).toBe(nestedObject);
1880
+ expect(console.log).toHaveBeenCalledWith('$reactTemp0');
1881
+ expect(global.$reactTemp0).toBe(nestedObject);
1882
1883
console.log.mockReset();
1884
1885
// Should store the nested property specified (not just the outer value)
1886
storeAsGlobal(id, ['props', 'nestedObject', 'a', 'b']);
1887
jest.runOnlyPendingTimers();
1590
- expect(console.log).toHaveBeenCalledWith('$reactTemp2');
1591
- expect(global.$reactTemp2).toBe(nestedObject.a.b);
1888
+ expect(console.log).toHaveBeenCalledWith('$reactTemp1');
1889
+ expect(global.$reactTemp1).toBe(nestedObject.a.b);
1890
1891
done();
1892
});
1918
let copyPath: CopyInspectedElementPath = ((null: any): CopyInspectedElementPath);
1919
1920
function Suspender({target}) {
1623
- const context = React.useContext(InspectedElementContext);
1624
- copyPath = context.copyInspectedElementPath;
1921
+ copyPath = (elementID: number, path: Array<string | number>) => {
1922
+ const rendererID = store.getRendererIDForElement(elementID);
1923
+ if (rendererID !== null) {
1924
+ const {
1925
+ copyInspectedElementPath,
1926
+ } = require('react-devtools-shared/src/backendAPI');
1927
+ copyInspectedElementPath({
1928
+ bridge,
1929
+ id: elementID,
1930
+ path,
1931
+ rendererID,
1932
+ });
1933
+ }
1934
+ };
1935
return null;
1936
}
1937
2022
let copyPath: CopyInspectedElementPath = ((null: any): CopyInspectedElementPath);
2023
2024
function Suspender({target}) {
1715
- const context = React.useContext(InspectedElementContext);
1716
- copyPath = context.copyInspectedElementPath;
2025
+ copyPath = (elementID: number, path: Array<string | number>) => {
2026
+ const rendererID = store.getRendererIDForElement(elementID);
2027
+ if (rendererID !== null) {
2028
+ const {
2029
+ copyInspectedElementPath,
2030
+ } = require('react-devtools-shared/src/backendAPI');
2031
+ copyInspectedElementPath({
2032
+ bridge,
2033
+ id: elementID,
2034
+ path,
2035
+ rendererID,
2036
+ });
2037
+ }
2038
+ };
2039
return null;
2040
}
2041
2083
});
2084
2085
it('should display complex values of useDebugValue', async done => {
1764
- let getInspectedElementPath: GetInspectedElementPath = ((null: any): GetInspectedElementPath);
2086
let inspectedElement = null;
2087
function Suspender({target}) {
1767
- const context = React.useContext(InspectedElementContext);
1768
- getInspectedElementPath = context.getInspectedElementPath;
1769
- inspectedElement = context.getInspectedElement(target);
2088
+ inspectedElement = useInspectedElement(target);
2089
return null;
2090
}
2091
2122
),
2123
false,
2124
);
1806
- expect(getInspectedElementPath).not.toBeNull();
1807
- expect(inspectedElement).not.toBeNull();
1808
- expect(inspectedElement).toMatchSnapshot('DisplayedComplexValue');
2125
+ expect(inspectedElement.hooks).toMatchInlineSnapshot(`
2126
+ Array [
2127
+ Object {
2128
+ "id": null,
2129
+ "isStateEditable": false,
2130
+ "name": "DebuggableHook",
2131
+ "subHooks": Array [
2132
+ Object {
2133
+ "id": 0,
2134
+ "isStateEditable": true,
2135
+ "name": "State",
2136
+ "subHooks": Array [],
2137
+ "value": 1,
2138
+ },
2139
+ ],
2140
+ "value": Object {
2141
+ "foo": 2,
2142
+ },
2143
+ },
2144
+ ]
2145
+ `);
2146
2147
done();
2148
});
2162
let warnings = null;
2163
2164
function Suspender({target}) {
1828
- const {getInspectedElement} = React.useContext(InspectedElementContext);
1829
- const inspectedElement = getInspectedElement(id);
2165
+ const inspectedElement = useInspectedElement(id);
2166
errors = inspectedElement.errors;
2167
warnings = inspectedElement.warnings;
2168
return null;
2374
);
2375
});
2376
2041
- store.clearErrorsAndWarnings();
2377
+ const {
2378
+ clearErrorsAndWarnings,
2379
+ } = require('react-devtools-shared/src/backendAPI');
2380
+ clearErrorsAndWarnings({bridge, store});
2381
+
2382
// Flush events to the renderer.
2383
jest.runOnlyPendingTimers();
2384
2391
`);
2392
});
2393
2054
- it('can be cleared for a particular Fiber (only errors)', async () => {
2394
+ it('can be cleared for a particular Fiber (only warnings)', async () => {
2395
const Example = ({id}) => {
2396
console.error(`test-only: render error #${id}`);
2397
console.warn(`test-only: render warning #${id}`);
2411
);
2412
});
2413
2074
- store.clearWarningsForElement(2);
2414
+ let id = ((store.getElementIDAtIndex(1): any): number);
2415
+ const rendererID = store.getRendererIDForElement(id);
2416
+
2417
+ const {
2418
+ clearWarningsForElement,
2419
+ } = require('react-devtools-shared/src/backendAPI');
2420
+ clearWarningsForElement({bridge, id, rendererID});
2421
+
2422
// Flush events to the renderer.
2423
jest.runOnlyPendingTimers();
2424
2454
]
2455
`);
2456
2110
- store.clearWarningsForElement(1);
2457
+ id = ((store.getElementIDAtIndex(0): any): number);
2458
+ clearWarningsForElement({bridge, id, rendererID});
2459
+
2460
// Flush events to the renderer.
2461
jest.runOnlyPendingTimers();
2462
2488
`);
2489
});
2490
2142
- it('can be cleared for a particular Fiber (only warnings)', async () => {
2491
+ it('can be cleared for a particular Fiber (only errors)', async () => {
2492
const Example = ({id}) => {
2493
console.error(`test-only: render error #${id}`);
2494
console.warn(`test-only: render warning #${id}`);
2508
);
2509
});
2510
2162
- store.clearErrorsForElement(2);
2511
+ let id = ((store.getElementIDAtIndex(1): any): number);
2512
+ const rendererID = store.getRendererIDForElement(id);
2513
+
2514
+ const {
2515
+ clearErrorsForElement,
2516
+ } = require('react-devtools-shared/src/backendAPI');
2517
+ clearErrorsForElement({bridge, id, rendererID});
2518
+
2519
// Flush events to the renderer.
2520
jest.runOnlyPendingTimers();
2521
2551
]
2552
`);
2553
2198
- store.clearErrorsForElement(1);
2554
+ id = ((store.getElementIDAtIndex(0): any): number);
2555
+ clearErrorsForElement({bridge, id, rendererID});
2556
+
2557
// Flush events to the renderer.
2558
jest.runOnlyPendingTimers();
2559