main
js 1,170 lines 29.1 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
11 import type Store from 'react-devtools-shared/src/devtools/store';
12
13 import {getVersionedRenderImplementation} from './utils';
14
15 describe('editing interface', () => {
16 let PropTypes;
17 let React;
18 let bridge: FrontendBridge;
19 let store: Store;
20 let utils;
21
22 const flushPendingUpdates = () => {
23 utils.act(() => jest.runOnlyPendingTimers());
24 };
25
26 beforeEach(() => {
27 utils = require('./utils');
28
29 bridge = global.bridge;
30 store = global.store;
31 store.collapseNodesByDefault = false;
32 store.componentFilters = [];
33
34 PropTypes = require('prop-types');
35 React = require('react');
36 });
37
38 const {render} = getVersionedRenderImplementation();
39
40 describe('props', () => {
41 let committedClassProps;
42 let committedFunctionProps;
43 let inputRef;
44 let classID;
45 let functionID;
46 let hostComponentID;
47
48 async function mountTestApp() {
49 class ClassComponent extends React.Component {
50 componentDidMount() {
51 committedClassProps = this.props;
52 }
53 componentDidUpdate() {
54 committedClassProps = this.props;
55 }
56 render() {
57 return null;
58 }
59 }
60
61 function FunctionComponent(props) {
62 React.useLayoutEffect(() => {
63 committedFunctionProps = props;
64 });
65 return null;
66 }
67
68 inputRef = React.createRef(null);
69
70 await utils.actAsync(() =>
71 render(
72 <>
73 <ClassComponent
74 array={[1, 2, 3]}
75 object={{nested: 'initial'}}
76 shallow="initial"
77 />
78 ,
79 <FunctionComponent
80 array={[1, 2, 3]}
81 object={{nested: 'initial'}}
82 shallow="initial"
83 />
84 ,
85 <input
86 ref={inputRef}
87 onChange={jest.fn()}
88 value="initial"
89 data-foo="test"
90 />
91 </>,
92 ),
93 );
94
95 classID = ((store.getElementIDAtIndex(0): any): number);
96 functionID = ((store.getElementIDAtIndex(1): any): number);
97 hostComponentID = ((store.getElementIDAtIndex(2): any): number);
98
99 expect(committedClassProps).toStrictEqual({
100 array: [1, 2, 3],
101 object: {
102 nested: 'initial',
103 },
104 shallow: 'initial',
105 });
106 expect(committedFunctionProps).toStrictEqual({
107 array: [1, 2, 3],
108 object: {
109 nested: 'initial',
110 },
111 shallow: 'initial',
112 });
113 expect(inputRef.current.value).toBe('initial');
114 }
115
116 // @reactVersion >= 16.9
117 it('should have editable values', async () => {
118 await mountTestApp();
119
120 function overrideProps(id, path, value) {
121 const rendererID = utils.getRendererID();
122 bridge.send('overrideValueAtPath', {
123 id,
124 path,
125 rendererID,
126 type: 'props',
127 value,
128 });
129 flushPendingUpdates();
130 }
131
132 overrideProps(classID, ['shallow'], 'updated');
133 expect(committedClassProps).toStrictEqual({
134 array: [1, 2, 3],
135 object: {
136 nested: 'initial',
137 },
138 shallow: 'updated',
139 });
140 overrideProps(classID, ['object', 'nested'], 'updated');
141 expect(committedClassProps).toStrictEqual({
142 array: [1, 2, 3],
143 object: {
144 nested: 'updated',
145 },
146 shallow: 'updated',
147 });
148 overrideProps(classID, ['array', 1], 'updated');
149 expect(committedClassProps).toStrictEqual({
150 array: [1, 'updated', 3],
151 object: {
152 nested: 'updated',
153 },
154 shallow: 'updated',
155 });
156
157 overrideProps(functionID, ['shallow'], 'updated');
158 expect(committedFunctionProps).toStrictEqual({
159 array: [1, 2, 3],
160 object: {
161 nested: 'initial',
162 },
163 shallow: 'updated',
164 });
165 overrideProps(functionID, ['object', 'nested'], 'updated');
166 expect(committedFunctionProps).toStrictEqual({
167 array: [1, 2, 3],
168 object: {
169 nested: 'updated',
170 },
171 shallow: 'updated',
172 });
173 overrideProps(functionID, ['array', 1], 'updated');
174 expect(committedFunctionProps).toStrictEqual({
175 array: [1, 'updated', 3],
176 object: {
177 nested: 'updated',
178 },
179 shallow: 'updated',
180 });
181 });
182
183 // @reactVersion >= 16.9
184 // Tests the combination of older frontend (DevTools UI) with newer backend (embedded within a renderer).
185 it('should still support overriding prop values with legacy backend methods', async () => {
186 await mountTestApp();
187
188 function overrideProps(id, path, value) {
189 const rendererID = utils.getRendererID();
190 bridge.send('overrideProps', {
191 id,
192 path,
193 rendererID,
194 value,
195 });
196 flushPendingUpdates();
197 }
198
199 overrideProps(classID, ['object', 'nested'], 'updated');
200 expect(committedClassProps).toStrictEqual({
201 array: [1, 2, 3],
202 object: {
203 nested: 'updated',
204 },
205 shallow: 'initial',
206 });
207
208 overrideProps(functionID, ['shallow'], 'updated');
209 expect(committedFunctionProps).toStrictEqual({
210 array: [1, 2, 3],
211 object: {
212 nested: 'initial',
213 },
214 shallow: 'updated',
215 });
216 });
217
218 // @reactVersion >= 17.0
219 it('should have editable paths', async () => {
220 await mountTestApp();
221
222 function renamePath(id, oldPath, newPath) {
223 const rendererID = utils.getRendererID();
224 bridge.send('renamePath', {
225 id,
226 oldPath,
227 newPath,
228 rendererID,
229 type: 'props',
230 });
231 flushPendingUpdates();
232 }
233
234 renamePath(classID, ['shallow'], ['after']);
235 expect(committedClassProps).toStrictEqual({
236 array: [1, 2, 3],
237 object: {
238 nested: 'initial',
239 },
240 after: 'initial',
241 });
242 renamePath(classID, ['object', 'nested'], ['object', 'after']);
243 expect(committedClassProps).toStrictEqual({
244 array: [1, 2, 3],
245 object: {
246 after: 'initial',
247 },
248 after: 'initial',
249 });
250
251 renamePath(functionID, ['shallow'], ['after']);
252 expect(committedFunctionProps).toStrictEqual({
253 array: [1, 2, 3],
254 object: {
255 nested: 'initial',
256 },
257 after: 'initial',
258 });
259 renamePath(functionID, ['object', 'nested'], ['object', 'after']);
260 expect(committedFunctionProps).toStrictEqual({
261 array: [1, 2, 3],
262 object: {
263 after: 'initial',
264 },
265 after: 'initial',
266 });
267 renamePath(hostComponentID, ['data-foo'], ['data-bar']);
268 expect({
269 foo: inputRef.current.dataset.foo,
270 bar: inputRef.current.dataset.bar,
271 }).toEqual({
272 foo: undefined,
273 bar: 'test',
274 });
275 });
276
277 // @reactVersion >= 16.9
278 it('should enable adding new object properties and array values', async () => {
279 await mountTestApp();
280
281 function overrideProps(id, path, value) {
282 const rendererID = utils.getRendererID();
283 bridge.send('overrideValueAtPath', {
284 id,
285 path,
286 rendererID,
287 type: 'props',
288 value,
289 });
290 flushPendingUpdates();
291 }
292
293 overrideProps(classID, ['new'], 'value');
294 expect(committedClassProps).toStrictEqual({
295 array: [1, 2, 3],
296 object: {
297 nested: 'initial',
298 },
299 shallow: 'initial',
300 new: 'value',
301 });
302
303 overrideProps(classID, ['object', 'new'], 'value');
304 expect(committedClassProps).toStrictEqual({
305 array: [1, 2, 3],
306 object: {
307 nested: 'initial',
308 new: 'value',
309 },
310 shallow: 'initial',
311 new: 'value',
312 });
313
314 overrideProps(classID, ['array', 3], 'new value');
315 expect(committedClassProps).toStrictEqual({
316 array: [1, 2, 3, 'new value'],
317 object: {
318 nested: 'initial',
319 new: 'value',
320 },
321 shallow: 'initial',
322 new: 'value',
323 });
324
325 overrideProps(functionID, ['new'], 'value');
326 expect(committedFunctionProps).toStrictEqual({
327 array: [1, 2, 3],
328 object: {
329 nested: 'initial',
330 },
331 shallow: 'initial',
332 new: 'value',
333 });
334
335 overrideProps(functionID, ['object', 'new'], 'value');
336 expect(committedFunctionProps).toStrictEqual({
337 array: [1, 2, 3],
338 object: {
339 nested: 'initial',
340 new: 'value',
341 },
342 shallow: 'initial',
343 new: 'value',
344 });
345
346 overrideProps(functionID, ['array', 3], 'new value');
347 expect(committedFunctionProps).toStrictEqual({
348 array: [1, 2, 3, 'new value'],
349 object: {
350 nested: 'initial',
351 new: 'value',
352 },
353 shallow: 'initial',
354 new: 'value',
355 });
356 });
357
358 // @reactVersion >= 17.0
359 it('should have deletable keys', async () => {
360 await mountTestApp();
361
362 function deletePath(id, path) {
363 const rendererID = utils.getRendererID();
364 bridge.send('deletePath', {
365 id,
366 path,
367 rendererID,
368 type: 'props',
369 });
370 flushPendingUpdates();
371 }
372
373 deletePath(classID, ['shallow']);
374 expect(committedClassProps).toStrictEqual({
375 array: [1, 2, 3],
376 object: {
377 nested: 'initial',
378 },
379 });
380 deletePath(classID, ['object', 'nested']);
381 expect(committedClassProps).toStrictEqual({
382 array: [1, 2, 3],
383 object: {},
384 });
385 deletePath(classID, ['array', 1]);
386 expect(committedClassProps).toStrictEqual({
387 array: [1, 3],
388 object: {},
389 });
390
391 deletePath(functionID, ['shallow']);
392 expect(committedFunctionProps).toStrictEqual({
393 array: [1, 2, 3],
394 object: {
395 nested: 'initial',
396 },
397 });
398 deletePath(functionID, ['object', 'nested']);
399 expect(committedFunctionProps).toStrictEqual({
400 array: [1, 2, 3],
401 object: {},
402 });
403 deletePath(functionID, ['array', 1]);
404 expect(committedFunctionProps).toStrictEqual({
405 array: [1, 3],
406 object: {},
407 });
408 });
409
410 // @reactVersion >= 16.9
411 it('should support editing host component values', async () => {
412 await mountTestApp();
413
414 function overrideProps(id, path, value) {
415 const rendererID = utils.getRendererID();
416 bridge.send('overrideValueAtPath', {
417 id,
418 path,
419 rendererID,
420 type: 'props',
421 value,
422 });
423 flushPendingUpdates();
424 }
425
426 overrideProps(hostComponentID, ['value'], 'updated');
427 expect(inputRef.current.value).toBe('updated');
428 });
429 });
430
431 describe('state', () => {
432 let committedState;
433 let id;
434
435 async function mountTestApp() {
436 class ClassComponent extends React.Component {
437 state = {
438 array: [1, 2, 3],
439 object: {
440 nested: 'initial',
441 },
442 shallow: 'initial',
443 };
444 componentDidMount() {
445 committedState = this.state;
446 }
447 componentDidUpdate() {
448 committedState = this.state;
449 }
450 render() {
451 return null;
452 }
453 }
454
455 await utils.actAsync(() =>
456 render(
457 <ClassComponent object={{nested: 'initial'}} shallow="initial" />,
458 ),
459 );
460
461 id = ((store.getElementIDAtIndex(0): any): number);
462
463 expect(committedState).toStrictEqual({
464 array: [1, 2, 3],
465 object: {
466 nested: 'initial',
467 },
468 shallow: 'initial',
469 });
470 }
471
472 // @reactVersion >= 16.9
473 it('should have editable values', async () => {
474 await mountTestApp();
475
476 function overrideState(path, value) {
477 const rendererID = utils.getRendererID();
478 bridge.send('overrideValueAtPath', {
479 id,
480 path,
481 rendererID,
482 type: 'state',
483 value,
484 });
485 flushPendingUpdates();
486 }
487
488 overrideState(['shallow'], 'updated');
489 expect(committedState).toStrictEqual({
490 array: [1, 2, 3],
491 object: {nested: 'initial'},
492 shallow: 'updated',
493 });
494
495 overrideState(['object', 'nested'], 'updated');
496 expect(committedState).toStrictEqual({
497 array: [1, 2, 3],
498 object: {nested: 'updated'},
499 shallow: 'updated',
500 });
501
502 overrideState(['array', 1], 'updated');
503 expect(committedState).toStrictEqual({
504 array: [1, 'updated', 3],
505 object: {nested: 'updated'},
506 shallow: 'updated',
507 });
508 });
509
510 // @reactVersion >= 16.9
511 // Tests the combination of older frontend (DevTools UI) with newer backend (embedded within a renderer).
512 it('should still support overriding state values with legacy backend methods', async () => {
513 await mountTestApp();
514
515 function overrideState(path, value) {
516 const rendererID = utils.getRendererID();
517 bridge.send('overrideState', {
518 id,
519 path,
520 rendererID,
521 value,
522 });
523 flushPendingUpdates();
524 }
525
526 overrideState(['array', 1], 'updated');
527 expect(committedState).toStrictEqual({
528 array: [1, 'updated', 3],
529 object: {nested: 'initial'},
530 shallow: 'initial',
531 });
532 });
533
534 // @reactVersion >= 16.9
535 it('should have editable paths', async () => {
536 await mountTestApp();
537
538 function renamePath(oldPath, newPath) {
539 const rendererID = utils.getRendererID();
540 bridge.send('renamePath', {
541 id,
542 oldPath,
543 newPath,
544 rendererID,
545 type: 'state',
546 });
547 flushPendingUpdates();
548 }
549
550 renamePath(['shallow'], ['after']);
551 expect(committedState).toStrictEqual({
552 array: [1, 2, 3],
553 object: {
554 nested: 'initial',
555 },
556 after: 'initial',
557 });
558
559 renamePath(['object', 'nested'], ['object', 'after']);
560 expect(committedState).toStrictEqual({
561 array: [1, 2, 3],
562 object: {
563 after: 'initial',
564 },
565 after: 'initial',
566 });
567 });
568
569 // @reactVersion >= 16.9
570 it('should enable adding new object properties and array values', async () => {
571 await mountTestApp();
572
573 function overrideState(path, value) {
574 const rendererID = utils.getRendererID();
575 bridge.send('overrideValueAtPath', {
576 id,
577 path,
578 rendererID,
579 type: 'state',
580 value,
581 });
582 flushPendingUpdates();
583 }
584
585 overrideState(['new'], 'value');
586 expect(committedState).toStrictEqual({
587 array: [1, 2, 3],
588 object: {
589 nested: 'initial',
590 },
591 shallow: 'initial',
592 new: 'value',
593 });
594
595 overrideState(['object', 'new'], 'value');
596 expect(committedState).toStrictEqual({
597 array: [1, 2, 3],
598 object: {
599 nested: 'initial',
600 new: 'value',
601 },
602 shallow: 'initial',
603 new: 'value',
604 });
605
606 overrideState(['array', 3], 'new value');
607 expect(committedState).toStrictEqual({
608 array: [1, 2, 3, 'new value'],
609 object: {
610 nested: 'initial',
611 new: 'value',
612 },
613 shallow: 'initial',
614 new: 'value',
615 });
616 });
617
618 // @reactVersion >= 16.9
619 it('should have deletable keys', async () => {
620 await mountTestApp();
621
622 function deletePath(path) {
623 const rendererID = utils.getRendererID();
624 bridge.send('deletePath', {
625 id,
626 path,
627 rendererID,
628 type: 'state',
629 });
630 flushPendingUpdates();
631 }
632
633 deletePath(['shallow']);
634 expect(committedState).toStrictEqual({
635 array: [1, 2, 3],
636 object: {
637 nested: 'initial',
638 },
639 });
640
641 deletePath(['object', 'nested']);
642 expect(committedState).toStrictEqual({
643 array: [1, 2, 3],
644 object: {},
645 });
646
647 deletePath(['array', 1]);
648 expect(committedState).toStrictEqual({
649 array: [1, 3],
650 object: {},
651 });
652 });
653 });
654
655 describe('hooks', () => {
656 let committedState;
657 let hookID;
658 let id;
659
660 async function mountTestApp() {
661 function FunctionComponent() {
662 const [state] = React.useState({
663 array: [1, 2, 3],
664 object: {
665 nested: 'initial',
666 },
667 shallow: 'initial',
668 });
669 React.useLayoutEffect(() => {
670 committedState = state;
671 });
672 return null;
673 }
674
675 await utils.actAsync(() => render(<FunctionComponent />));
676
677 hookID = 0; // index
678 id = ((store.getElementIDAtIndex(0): any): number);
679
680 expect(committedState).toStrictEqual({
681 array: [1, 2, 3],
682 object: {
683 nested: 'initial',
684 },
685 shallow: 'initial',
686 });
687 }
688
689 // @reactVersion >= 16.9
690 it('should have editable values', async () => {
691 await mountTestApp();
692
693 function overrideHookState(path, value) {
694 const rendererID = utils.getRendererID();
695 bridge.send('overrideValueAtPath', {
696 hookID,
697 id,
698 path,
699 rendererID,
700 type: 'hooks',
701 value,
702 });
703 flushPendingUpdates();
704 }
705
706 overrideHookState(['shallow'], 'updated');
707 expect(committedState).toStrictEqual({
708 array: [1, 2, 3],
709 object: {
710 nested: 'initial',
711 },
712 shallow: 'updated',
713 });
714
715 overrideHookState(['object', 'nested'], 'updated');
716 expect(committedState).toStrictEqual({
717 array: [1, 2, 3],
718 object: {
719 nested: 'updated',
720 },
721 shallow: 'updated',
722 });
723
724 overrideHookState(['array', 1], 'updated');
725 expect(committedState).toStrictEqual({
726 array: [1, 'updated', 3],
727 object: {
728 nested: 'updated',
729 },
730 shallow: 'updated',
731 });
732 });
733
734 // @reactVersion >= 16.9
735 // Tests the combination of older frontend (DevTools UI) with newer backend (embedded within a renderer).
736 it('should still support overriding hook values with legacy backend methods', async () => {
737 await mountTestApp();
738
739 function overrideHookState(path, value) {
740 const rendererID = utils.getRendererID();
741 bridge.send('overrideHookState', {
742 hookID,
743 id,
744 path,
745 rendererID,
746 value,
747 });
748 flushPendingUpdates();
749 }
750
751 overrideHookState(['shallow'], 'updated');
752 expect(committedState).toStrictEqual({
753 array: [1, 2, 3],
754 object: {
755 nested: 'initial',
756 },
757 shallow: 'updated',
758 });
759 });
760
761 // @reactVersion >= 17.0
762 it('should have editable paths', async () => {
763 await mountTestApp();
764
765 function renamePath(oldPath, newPath) {
766 const rendererID = utils.getRendererID();
767 bridge.send('renamePath', {
768 id,
769 hookID,
770 oldPath,
771 newPath,
772 rendererID,
773 type: 'hooks',
774 });
775 flushPendingUpdates();
776 }
777
778 renamePath(['shallow'], ['after']);
779 expect(committedState).toStrictEqual({
780 array: [1, 2, 3],
781 object: {
782 nested: 'initial',
783 },
784 after: 'initial',
785 });
786
787 renamePath(['object', 'nested'], ['object', 'after']);
788 expect(committedState).toStrictEqual({
789 array: [1, 2, 3],
790 object: {
791 after: 'initial',
792 },
793 after: 'initial',
794 });
795 });
796
797 // @reactVersion >= 16.9
798 it('should enable adding new object properties and array values', async () => {
799 await mountTestApp();
800
801 function overrideHookState(path, value) {
802 const rendererID = utils.getRendererID();
803 bridge.send('overrideValueAtPath', {
804 hookID,
805 id,
806 path,
807 rendererID,
808 type: 'hooks',
809 value,
810 });
811 flushPendingUpdates();
812 }
813
814 overrideHookState(['new'], 'value');
815 expect(committedState).toStrictEqual({
816 array: [1, 2, 3],
817 object: {
818 nested: 'initial',
819 },
820 shallow: 'initial',
821 new: 'value',
822 });
823
824 overrideHookState(['object', 'new'], 'value');
825 expect(committedState).toStrictEqual({
826 array: [1, 2, 3],
827 object: {
828 nested: 'initial',
829 new: 'value',
830 },
831 shallow: 'initial',
832 new: 'value',
833 });
834
835 overrideHookState(['array', 3], 'new value');
836 expect(committedState).toStrictEqual({
837 array: [1, 2, 3, 'new value'],
838 object: {
839 nested: 'initial',
840 new: 'value',
841 },
842 shallow: 'initial',
843 new: 'value',
844 });
845 });
846
847 // @reactVersion >= 17.0
848 it('should have deletable keys', async () => {
849 await mountTestApp();
850
851 function deletePath(path) {
852 const rendererID = utils.getRendererID();
853 bridge.send('deletePath', {
854 hookID,
855 id,
856 path,
857 rendererID,
858 type: 'hooks',
859 });
860 flushPendingUpdates();
861 }
862
863 deletePath(['shallow']);
864 expect(committedState).toStrictEqual({
865 array: [1, 2, 3],
866 object: {
867 nested: 'initial',
868 },
869 });
870
871 deletePath(['object', 'nested']);
872 expect(committedState).toStrictEqual({
873 array: [1, 2, 3],
874 object: {},
875 });
876
877 deletePath(['array', 1]);
878 expect(committedState).toStrictEqual({
879 array: [1, 3],
880 object: {},
881 });
882 });
883 });
884
885 describe('context', () => {
886 let committedContext;
887 let id;
888
889 async function mountTestApp() {
890 class LegacyContextProvider extends React.Component<any> {
891 static childContextTypes = {
892 array: PropTypes.array,
893 object: PropTypes.object,
894 shallow: PropTypes.string,
895 };
896 getChildContext() {
897 return {
898 array: [1, 2, 3],
899 object: {
900 nested: 'initial',
901 },
902 shallow: 'initial',
903 };
904 }
905 render() {
906 return this.props.children;
907 }
908 }
909
910 class ClassComponent extends React.Component<any> {
911 static contextTypes = {
912 array: PropTypes.array,
913 object: PropTypes.object,
914 shallow: PropTypes.string,
915 };
916 componentDidMount() {
917 committedContext = this.context;
918 }
919 componentDidUpdate() {
920 committedContext = this.context;
921 }
922 render() {
923 return null;
924 }
925 }
926
927 await utils.actAsync(() =>
928 render(
929 <LegacyContextProvider>
930 <ClassComponent />
931 </LegacyContextProvider>,
932 ),
933 );
934
935 // This test only covers Class components.
936 // Function components using legacy context are not editable.
937
938 id = ((store.getElementIDAtIndex(1): any): number);
939
940 expect(committedContext).toStrictEqual({
941 array: [1, 2, 3],
942 object: {
943 nested: 'initial',
944 },
945 shallow: 'initial',
946 });
947 }
948
949 // @reactVersion >= 16.9
950 // @gate !disableLegacyContext
951 it('should have editable values', async () => {
952 await mountTestApp();
953
954 function overrideContext(path, value) {
955 const rendererID = utils.getRendererID();
956
957 // To simplify hydration and display of primitive context values (e.g. number, string)
958 // the inspectElement() method wraps context in a {value: ...} object.
959 path = ['value', ...path];
960
961 bridge.send('overrideValueAtPath', {
962 id,
963 path,
964 rendererID,
965 type: 'context',
966 value,
967 });
968 flushPendingUpdates();
969 }
970
971 overrideContext(['shallow'], 'updated');
972 expect(committedContext).toStrictEqual({
973 array: [1, 2, 3],
974 object: {
975 nested: 'initial',
976 },
977 shallow: 'updated',
978 });
979
980 overrideContext(['object', 'nested'], 'updated');
981 expect(committedContext).toStrictEqual({
982 array: [1, 2, 3],
983 object: {
984 nested: 'updated',
985 },
986 shallow: 'updated',
987 });
988
989 overrideContext(['array', 1], 'updated');
990 expect(committedContext).toStrictEqual({
991 array: [1, 'updated', 3],
992 object: {
993 nested: 'updated',
994 },
995 shallow: 'updated',
996 });
997 });
998
999 // @reactVersion >= 16.9
1000 // @gate !disableLegacyContext
1001 // Tests the combination of older frontend (DevTools UI) with newer backend (embedded within a renderer).
1002 it('should still support overriding context values with legacy backend methods', async () => {
1003 await mountTestApp();
1004
1005 function overrideContext(path, value) {
1006 const rendererID = utils.getRendererID();
1007
1008 // To simplify hydration and display of primitive context values (e.g. number, string)
1009 // the inspectElement() method wraps context in a {value: ...} object.
1010 path = ['value', ...path];
1011
1012 bridge.send('overrideContext', {
1013 id,
1014 path,
1015 rendererID,
1016 value,
1017 });
1018 flushPendingUpdates();
1019 }
1020
1021 overrideContext(['object', 'nested'], 'updated');
1022 expect(committedContext).toStrictEqual({
1023 array: [1, 2, 3],
1024 object: {
1025 nested: 'updated',
1026 },
1027 shallow: 'initial',
1028 });
1029 });
1030
1031 // @reactVersion >= 16.9
1032 // @gate !disableLegacyContext
1033 it('should have editable paths', async () => {
1034 await mountTestApp();
1035
1036 function renamePath(oldPath, newPath) {
1037 const rendererID = utils.getRendererID();
1038
1039 // To simplify hydration and display of primitive context values (e.g. number, string)
1040 // the inspectElement() method wraps context in a {value: ...} object.
1041 oldPath = ['value', ...oldPath];
1042 newPath = ['value', ...newPath];
1043
1044 bridge.send('renamePath', {
1045 id,
1046 oldPath,
1047 newPath,
1048 rendererID,
1049 type: 'context',
1050 });
1051 flushPendingUpdates();
1052 }
1053
1054 renamePath(['shallow'], ['after']);
1055 expect(committedContext).toStrictEqual({
1056 array: [1, 2, 3],
1057 object: {
1058 nested: 'initial',
1059 },
1060 after: 'initial',
1061 });
1062
1063 renamePath(['object', 'nested'], ['object', 'after']);
1064 expect(committedContext).toStrictEqual({
1065 array: [1, 2, 3],
1066 object: {
1067 after: 'initial',
1068 },
1069 after: 'initial',
1070 });
1071 });
1072
1073 // @reactVersion >= 16.9
1074 // @gate !disableLegacyContext
1075 it('should enable adding new object properties and array values', async () => {
1076 await mountTestApp();
1077
1078 function overrideContext(path, value) {
1079 const rendererID = utils.getRendererID();
1080
1081 // To simplify hydration and display of primitive context values (e.g. number, string)
1082 // the inspectElement() method wraps context in a {value: ...} object.
1083 path = ['value', ...path];
1084
1085 bridge.send('overrideValueAtPath', {
1086 id,
1087 path,
1088 rendererID,
1089 type: 'context',
1090 value,
1091 });
1092 flushPendingUpdates();
1093 }
1094
1095 overrideContext(['new'], 'value');
1096 expect(committedContext).toStrictEqual({
1097 array: [1, 2, 3],
1098 object: {
1099 nested: 'initial',
1100 },
1101 shallow: 'initial',
1102 new: 'value',
1103 });
1104
1105 overrideContext(['object', 'new'], 'value');
1106 expect(committedContext).toStrictEqual({
1107 array: [1, 2, 3],
1108 object: {
1109 nested: 'initial',
1110 new: 'value',
1111 },
1112 shallow: 'initial',
1113 new: 'value',
1114 });
1115
1116 overrideContext(['array', 3], 'new value');
1117 expect(committedContext).toStrictEqual({
1118 array: [1, 2, 3, 'new value'],
1119 object: {
1120 nested: 'initial',
1121 new: 'value',
1122 },
1123 shallow: 'initial',
1124 new: 'value',
1125 });
1126 });
1127
1128 // @reactVersion >= 16.9
1129 // @gate !disableLegacyContext
1130 it('should have deletable keys', async () => {
1131 await mountTestApp();
1132
1133 function deletePath(path) {
1134 const rendererID = utils.getRendererID();
1135
1136 // To simplify hydration and display of primitive context values (e.g. number, string)
1137 // the inspectElement() method wraps context in a {value: ...} object.
1138 path = ['value', ...path];
1139
1140 bridge.send('deletePath', {
1141 id,
1142 path,
1143 rendererID,
1144 type: 'context',
1145 });
1146 flushPendingUpdates();
1147 }
1148
1149 deletePath(['shallow']);
1150 expect(committedContext).toStrictEqual({
1151 array: [1, 2, 3],
1152 object: {
1153 nested: 'initial',
1154 },
1155 });
1156
1157 deletePath(['object', 'nested']);
1158 expect(committedContext).toStrictEqual({
1159 array: [1, 2, 3],
1160 object: {},
1161 });
1162
1163 deletePath(['array', 1]);
1164 expect(committedContext).toStrictEqual({
1165 array: [1, 3],
1166 object: {},
1167 });
1168 });
1169 });
1170 });