Skip to content

Commit 917cb01

Browse files
omarsyBrian Vaughn
and
Brian Vaughn
authored
React DevTools: Show symbols used as keys in state (#19786)
Co-authored-by: Brian Vaughn <[email protected]>
1 parent 11ee82d commit 917cb01

File tree

9 files changed

+415
-8
lines changed

9 files changed

+415
-8
lines changed

fixtures/devtools/standalone/index.html

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,70 @@ <h1>List</h1>
208208
return <ChildComponent customObject={new Custom()} />;
209209
}
210210

211+
const baseInheritedKeys = Object.create(Object.prototype, {
212+
enumerableStringBase: {
213+
value: 1,
214+
writable: true,
215+
enumerable: true,
216+
configurable: true,
217+
},
218+
[Symbol('enumerableSymbolBase')]: {
219+
value: 1,
220+
writable: true,
221+
enumerable: true,
222+
configurable: true,
223+
},
224+
nonEnumerableStringBase: {
225+
value: 1,
226+
writable: true,
227+
enumerable: false,
228+
configurable: true,
229+
},
230+
[Symbol('nonEnumerableSymbolBase')]: {
231+
value: 1,
232+
writable: true,
233+
enumerable: false,
234+
configurable: true,
235+
},
236+
});
237+
238+
const inheritedKeys = Object.create(baseInheritedKeys, {
239+
enumerableString: {
240+
value: 2,
241+
writable: true,
242+
enumerable: true,
243+
configurable: true,
244+
},
245+
nonEnumerableString: {
246+
value: 3,
247+
writable: true,
248+
enumerable: false,
249+
configurable: true,
250+
},
251+
123: {
252+
value: 3,
253+
writable: true,
254+
enumerable: true,
255+
configurable: true,
256+
},
257+
[Symbol('nonEnumerableSymbol')]: {
258+
value: 2,
259+
writable: true,
260+
enumerable: false,
261+
configurable: true,
262+
},
263+
[Symbol('enumerableSymbol')]: {
264+
value: 3,
265+
writable: true,
266+
enumerable: true,
267+
configurable: true,
268+
},
269+
});
270+
271+
function InheritedKeys() {
272+
return <ChildComponent data={inheritedKeys} />;
273+
}
274+
211275
const object = {
212276
string: "abc",
213277
longString: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKJLMNOPQRSTUVWXYZ1234567890",
@@ -294,6 +358,7 @@ <h1>List</h1>
294358
<ObjectProps />
295359
<UnserializableProps />
296360
<CustomObject />
361+
<InheritedKeys />
297362
</Fragment>
298363
);
299364
}

packages/react-devtools-shared/src/__tests__/__snapshots__/inspectedElementContext-test.js.snap

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,9 @@ exports[`InspectedElementContext should support complex data types: 1: Inspected
541541
"object_of_objects": {
542542
"inner": {}
543543
},
544+
"object_with_symbol": {
545+
"Symbol(name)": "hello"
546+
},
544547
"proxy": {},
545548
"react_element": {},
546549
"regexp": {},
@@ -612,6 +615,25 @@ exports[`InspectedElementContext should support objects with overridden hasOwnPr
612615
}
613616
`;
614617

618+
exports[`InspectedElementContext should support objects with with inherited keys: 1: Inspected element 2 1`] = `
619+
{
620+
"id": 2,
621+
"owners": null,
622+
"context": null,
623+
"hooks": null,
624+
"props": {
625+
"object": {
626+
"123": 3,
627+
"enumerableString": 2,
628+
"Symbol(enumerableSymbol)": 3,
629+
"enumerableStringBase": 1,
630+
"Symbol(enumerableSymbolBase)": 1
631+
}
632+
},
633+
"state": null
634+
}
635+
`;
636+
615637
exports[`InspectedElementContext should support simple data types: 1: Initial inspection 1`] = `
616638
{
617639
"id": 2,

packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,9 @@ describe('InspectedElementContext', () => {
537537
const objectOfObjects = {
538538
inner: {string: 'abc', number: 123, boolean: true},
539539
};
540+
const objectWithSymbol = {
541+
[Symbol('name')]: 'hello',
542+
};
540543
const typedArray = Int8Array.from([100, -100, 0]);
541544
const arrayBuffer = typedArray.buffer;
542545
const dataView = new DataView(arrayBuffer);
@@ -580,6 +583,7 @@ describe('InspectedElementContext', () => {
580583
map={mapShallow}
581584
map_of_maps={mapOfMaps}
582585
object_of_objects={objectOfObjects}
586+
object_with_symbol={objectWithSymbol}
583587
proxy={proxyInstance}
584588
react_element={<span />}
585589
regexp={/abc/giu}
@@ -633,6 +637,7 @@ describe('InspectedElementContext', () => {
633637
map,
634638
map_of_maps,
635639
object_of_objects,
640+
object_with_symbol,
636641
proxy,
637642
react_element,
638643
regexp,
@@ -737,6 +742,8 @@ describe('InspectedElementContext', () => {
737742
);
738743
expect(object_of_objects.inner[meta.preview_short]).toBe('{…}');
739744

745+
expect(object_with_symbol['Symbol(name)']).toBe('hello');
746+
740747
expect(proxy[meta.inspectable]).toBe(false);
741748
expect(proxy[meta.name]).toBe('function');
742749
expect(proxy[meta.type]).toBe('function');
@@ -939,6 +946,111 @@ describe('InspectedElementContext', () => {
939946
done();
940947
});
941948

949+
it('should support objects with with inherited keys', async done => {
950+
const Example = () => null;
951+
952+
const base = Object.create(Object.prototype, {
953+
enumerableStringBase: {
954+
value: 1,
955+
writable: true,
956+
enumerable: true,
957+
configurable: true,
958+
},
959+
[Symbol('enumerableSymbolBase')]: {
960+
value: 1,
961+
writable: true,
962+
enumerable: true,
963+
configurable: true,
964+
},
965+
nonEnumerableStringBase: {
966+
value: 1,
967+
writable: true,
968+
enumerable: false,
969+
configurable: true,
970+
},
971+
[Symbol('nonEnumerableSymbolBase')]: {
972+
value: 1,
973+
writable: true,
974+
enumerable: false,
975+
configurable: true,
976+
},
977+
});
978+
979+
const object = Object.create(base, {
980+
enumerableString: {
981+
value: 2,
982+
writable: true,
983+
enumerable: true,
984+
configurable: true,
985+
},
986+
nonEnumerableString: {
987+
value: 3,
988+
writable: true,
989+
enumerable: false,
990+
configurable: true,
991+
},
992+
[123]: {
993+
value: 3,
994+
writable: true,
995+
enumerable: true,
996+
configurable: true,
997+
},
998+
[Symbol('nonEnumerableSymbol')]: {
999+
value: 2,
1000+
writable: true,
1001+
enumerable: false,
1002+
configurable: true,
1003+
},
1004+
[Symbol('enumerableSymbol')]: {
1005+
value: 3,
1006+
writable: true,
1007+
enumerable: true,
1008+
configurable: true,
1009+
},
1010+
});
1011+
1012+
const container = document.createElement('div');
1013+
await utils.actAsync(() =>
1014+
ReactDOM.render(<Example object={object} />, container),
1015+
);
1016+
1017+
const id = ((store.getElementIDAtIndex(0): any): number);
1018+
1019+
let inspectedElement = null;
1020+
1021+
function Suspender({target}) {
1022+
const {getInspectedElement} = React.useContext(InspectedElementContext);
1023+
inspectedElement = getInspectedElement(id);
1024+
return null;
1025+
}
1026+
1027+
await utils.actAsync(
1028+
() =>
1029+
TestRenderer.create(
1030+
<Contexts
1031+
defaultSelectedElementID={id}
1032+
defaultSelectedElementIndex={0}>
1033+
<React.Suspense fallback={null}>
1034+
<Suspender target={id} />
1035+
</React.Suspense>
1036+
</Contexts>,
1037+
),
1038+
false,
1039+
);
1040+
1041+
expect(inspectedElement).not.toBeNull();
1042+
expect(inspectedElement).toMatchSnapshot(`1: Inspected element ${id}`);
1043+
expect(inspectedElement.props.object).toEqual({
1044+
123: 3,
1045+
'Symbol(enumerableSymbol)': 3,
1046+
'Symbol(enumerableSymbolBase)': 1,
1047+
enumerableString: 2,
1048+
enumerableStringBase: 1,
1049+
});
1050+
1051+
done();
1052+
});
1053+
9421054
it('should not dehydrate nested values until explicitly requested', async done => {
9431055
const Example = () => {
9441056
const [state] = React.useState({

packages/react-devtools-shared/src/__tests__/legacy/__snapshots__/inspectElement-test.js.snap

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,29 @@ Object {
236236
}
237237
`;
238238

239+
exports[`InspectedElementContext should support objects with with inherited keys: 1: Initial inspection 1`] = `
240+
Object {
241+
"id": 2,
242+
"type": "full-data",
243+
"value": {
244+
"id": 2,
245+
"owners": null,
246+
"context": {},
247+
"hooks": null,
248+
"props": {
249+
"data": {
250+
"123": 3,
251+
"enumerableString": 2,
252+
"Symbol(enumerableSymbol)": 3,
253+
"enumerableStringBase": 1,
254+
"Symbol(enumerableSymbolBase)": 1
255+
}
256+
},
257+
"state": null
258+
},
259+
}
260+
`;
261+
239262
exports[`InspectedElementContext should support simple data types: 1: Initial inspection 1`] = `
240263
Object {
241264
"id": 2,

packages/react-devtools-shared/src/__tests__/legacy/inspectElement-test.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,81 @@ describe('InspectedElementContext', () => {
432432
done();
433433
});
434434

435+
it('should support objects with with inherited keys', async done => {
436+
const Example = () => null;
437+
438+
const base = Object.create(Object.prototype, {
439+
enumerableStringBase: {
440+
value: 1,
441+
writable: true,
442+
enumerable: true,
443+
configurable: true,
444+
},
445+
[Symbol('enumerableSymbolBase')]: {
446+
value: 1,
447+
writable: true,
448+
enumerable: true,
449+
configurable: true,
450+
},
451+
nonEnumerableStringBase: {
452+
value: 1,
453+
writable: true,
454+
enumerable: false,
455+
configurable: true,
456+
},
457+
[Symbol('nonEnumerableSymbolBase')]: {
458+
value: 1,
459+
writable: true,
460+
enumerable: false,
461+
configurable: true,
462+
},
463+
});
464+
465+
const object = Object.create(base, {
466+
enumerableString: {
467+
value: 2,
468+
writable: true,
469+
enumerable: true,
470+
configurable: true,
471+
},
472+
nonEnumerableString: {
473+
value: 3,
474+
writable: true,
475+
enumerable: false,
476+
configurable: true,
477+
},
478+
[123]: {
479+
value: 3,
480+
writable: true,
481+
enumerable: true,
482+
configurable: true,
483+
},
484+
[Symbol('nonEnumerableSymbol')]: {
485+
value: 2,
486+
writable: true,
487+
enumerable: false,
488+
configurable: true,
489+
},
490+
[Symbol('enumerableSymbol')]: {
491+
value: 3,
492+
writable: true,
493+
enumerable: true,
494+
configurable: true,
495+
},
496+
});
497+
498+
act(() =>
499+
ReactDOM.render(<Example data={object} />, document.createElement('div')),
500+
);
501+
502+
const id = ((store.getElementIDAtIndex(0): any): number);
503+
const inspectedElement = await read(id);
504+
505+
expect(inspectedElement).toMatchSnapshot('1: Initial inspection');
506+
507+
done();
508+
});
509+
435510
it('should not dehydrate nested values until explicitly requested', async done => {
436511
const Example = () => null;
437512

0 commit comments

Comments
 (0)