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