text: /**
   * Copyright (c) Facebook, Inc. and its affiliates.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the root directory of this source tree.
   *
   * @flow
   */
  
  import typeof ReactTestRenderer from 'react-test-renderer';
  import {withErrorsOrWarningsIgnored} from 'react-devtools-shared/src/__tests__/utils';
  
  import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
  import type Store from 'react-devtools-shared/src/devtools/store';
  
  describe('InspectedElement', () => {
    let React;
    let ReactDOM;
    let PropTypes;
    let TestRenderer: ReactTestRenderer;
    let bridge: FrontendBridge;
    let store: Store;
    let utils;
  
    let BridgeContext;
    let InspectedElementContext;
    let InspectedElementContextController;
    let StoreContext;
    let TreeContextController;
  
    let TestUtilsAct;
    let TestRendererAct;
  
    let testRendererInstance;
  
    beforeEach(() => {
      utils = require('./utils');
      utils.beforeEachProfiling();
  
      bridge = global.bridge;
      store = global.store;
      store.collapseNodesByDefault = false;
  
      React = require('react');
      ReactDOM = require('react-dom');
      PropTypes = require('prop-types');
      TestUtilsAct = require('jest-react').act;
      TestRenderer = utils.requireTestRenderer();
      TestRendererAct = require('jest-react').act;
  
      BridgeContext = require('react-devtools-shared/src/devtools/views/context')
        .BridgeContext;
      InspectedElementContext = require('react-devtools-shared/src/devtools/views/Components/InspectedElementContext')
        .InspectedElementContext;
      InspectedElementContextController = require('react-devtools-shared/src/devtools/views/Components/InspectedElementContext')
        .InspectedElementContextController;
      StoreContext = require('react-devtools-shared/src/devtools/views/context')
        .StoreContext;
      TreeContextController = require('react-devtools-shared/src/devtools/views/Components/TreeContext')
        .TreeContextController;
  
      // Used by inspectElementAtIndex() helper function
      testRendererInstance = TestRenderer.create(null, {
        unstable_isConcurrent: true,
      });
    });
  
    afterEach(() => {
      jest.restoreAllMocks();
    });
  
    const Contexts = ({
      children,
      defaultSelectedElementID = null,
      defaultSelectedElementIndex = null,
    }) => (
      <BridgeContext.Provider value={bridge}>
        <StoreContext.Provider value={store}>
          <TreeContextController
            defaultSelectedElementID={defaultSelectedElementID}
            defaultSelectedElementIndex={defaultSelectedElementIndex}>
            <React.Suspense fallback="Loading...">
              <InspectedElementContextController>
                {children}
              </InspectedElementContextController>
            </React.Suspense>
          </TreeContextController>
        </StoreContext.Provider>
      </BridgeContext.Provider>
    );
  
    function useInspectedElement() {
      const {inspectedElement} = React.useContext(InspectedElementContext);
      return inspectedElement;
    }
  
    function useInspectElementPath() {
      const {inspectPaths} = React.useContext(InspectedElementContext);
      return inspectPaths;
    }
  
    function noop() {}
  
    async function inspectElementAtIndex(index, useCustomHook = noop) {
      let didFinish = false;
      let inspectedElement = null;
  
      function Suspender() {
        useCustomHook();
        inspectedElement = useInspectedElement();
        didFinish = true;
        return null;
      }
  
      const id = ((store.getElementIDAtIndex(index): any): number);
  
      await utils.actAsync(() => {
        testRendererInstance.update(
          <Contexts
            defaultSelectedElementID={id}
            defaultSelectedElementIndex={index}>
            <React.Suspense fallback={null}>
              <Suspender id={id} index={index} />
            </React.Suspense>
          </Contexts>,
        );
      }, false);
  
      expect(didFinish).toBe(true);
  
      return inspectedElement;
    }
  
    it('should inspect the currently selected element', async () => {
      const Example = () => {
        const [count] = React.useState(1);
        return count;
      };
  
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(<Example a={1} b="abc" />, container),
      );
  
      const inspectedElement = await inspectElementAtIndex(0);
      expect(inspectedElement).toMatchInlineSnapshot(`
        Object {
          "context": null,
          "events": undefined,
          "hooks": Array [
            Object {
              "id": 0,
              "isStateEditable": true,
              "name": "State",
              "subHooks": Array [],
              "value": 1,
            },
          ],
          "id": 2,
          "owners": null,
          "props": Object {
            "a": 1,
            "b": "abc",
          },
          "state": null,
        }
      `);
    });
  
    it('should have hasLegacyContext flag set to either "true" or "false" depending on which context API is used.', async () => {
      const contextData = {
        bool: true,
      };
  
      // Legacy Context API.
      class LegacyContextProvider extends React.Component<any> {
        static childContextTypes = {
          bool: PropTypes.bool,
        };
        getChildContext() {
          return contextData;
        }
        render() {
          return this.props.children;
        }
      }
      class LegacyContextConsumer extends React.Component<any> {
        static contextTypes = {
          bool: PropTypes.bool,
        };
        render() {
          return null;
        }
      }
  
      // Modern Context API
      const BoolContext = React.createContext(contextData.bool);
      BoolContext.displayName = 'BoolContext';
  
      class ModernContextType extends React.Component<any> {
        static contextType = BoolContext;
        render() {
          return null;
        }
      }
  
      const ModernContext = React.createContext();
      ModernContext.displayName = 'ModernContext';
  
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(
          <React.Fragment>
            <LegacyContextProvider>
              <LegacyContextConsumer />
            </LegacyContextProvider>
            <BoolContext.Consumer>{value => null}</BoolContext.Consumer>
            <ModernContextType />
            <ModernContext.Provider value={contextData}>
              <ModernContext.Consumer>{value => null}</ModernContext.Consumer>
            </ModernContext.Provider>
          </React.Fragment>,
          container,
        ),
      );
  
      const cases = [
        {
          // <LegacyContextConsumer />
          index: 1,
          shouldHaveLegacyContext: true,
        },
        {
          // <BoolContext.Consumer>
          index: 2,
          shouldHaveLegacyContext: false,
        },
        {
          // <ModernContextType />
          index: 3,
          shouldHaveLegacyContext: false,
        },
        {
          // <ModernContext.Consumer>
          index: 5,
          shouldHaveLegacyContext: false,
        },
      ];
  
      for (let i = 0; i < cases.length; i++) {
        const {index, shouldHaveLegacyContext} = cases[i];
  
        // HACK: Recreate TestRenderer instance because we rely on default state values
        // from props like defaultSelectedElementID and it's easier to reset here than
        // to read the TreeDispatcherContext and update the selected ID that way.
        // We're testing the inspected values here, not the context wiring, so that's ok.
        testRendererInstance = TestRenderer.create(null, {
          unstable_isConcurrent: true,
        });
  
        const inspectedElement = await inspectElementAtIndex(index);
  
        expect(inspectedElement.context).not.toBe(null);
        expect(inspectedElement.hasLegacyContext).toBe(shouldHaveLegacyContext);
      }
    });
  
    it('should poll for updates for the currently selected element', async () => {
      const Example = () => null;
  
      const container = document.createElement('div');
      await utils.actAsync(
        () => ReactDOM.render(<Example a={1} b="abc" />, container),
        false,
      );
  
      let inspectedElement = await inspectElementAtIndex(0);
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "a": 1,
          "b": "abc",
        }
      `);
  
      await utils.actAsync(
        () => ReactDOM.render(<Example a={2} b="def" />, container),
        false,
      );
  
      // TODO (cache)
      // This test only passes if both the check-for-updates poll AND the test renderer.update() call are included below.
      // It seems like either one of the two should be sufficient but:
      // 1. Running only check-for-updates schedules a transition that React never renders.
      // 2. Running only renderer.update() loads stale data (first props)
  
      // Wait for our check-for-updates poll to get the new data.
      jest.runOnlyPendingTimers();
      await Promise.resolve();
  
      inspectedElement = await inspectElementAtIndex(0);
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "a": 2,
          "b": "def",
        }
      `);
    });
  
    it('should not re-render a function with hooks if it did not update since it was last inspected', async () => {
      let targetRenderCount = 0;
  
      const Wrapper = ({children}) => children;
      const Target = React.memo(props => {
        targetRenderCount++;
        // Even though his hook isn't referenced, it's used to observe backend rendering.
        React.useState(0);
        return null;
      });
  
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(
          <Wrapper>
            <Target a={1} b="abc" />
          </Wrapper>,
          container,
        ),
      );
  
      targetRenderCount = 0;
  
      let inspectedElement = await inspectElementAtIndex(1);
      expect(targetRenderCount).toBe(1);
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "a": 1,
          "b": "abc",
        }
      `);
  
      const prevInspectedElement = inspectedElement;
  
      targetRenderCount = 0;
      inspectedElement = await inspectElementAtIndex(1);
      expect(targetRenderCount).toBe(0);
      expect(inspectedElement).toEqual(prevInspectedElement);
  
      targetRenderCount = 0;
  
      await utils.actAsync(
        () =>
          ReactDOM.render(
            <Wrapper>
              <Target a={2} b="def" />
            </Wrapper>,
            container,
          ),
        false,
      );
  
      // Target should have been rendered once (by ReactDOM) and once by DevTools for inspection.
      inspectedElement = await inspectElementAtIndex(1);
      expect(targetRenderCount).toBe(2);
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "a": 2,
          "b": "def",
        }
      `);
    });
  
    it('should temporarily disable console logging when re-running a component to inspect its hooks', async () => {
      let targetRenderCount = 0;
  
      jest.spyOn(console, 'error').mockImplementation(() => {});
      jest.spyOn(console, 'info').mockImplementation(() => {});
      jest.spyOn(console, 'log').mockImplementation(() => {});
      jest.spyOn(console, 'warn').mockImplementation(() => {});
  
      const Target = React.memo(props => {
        targetRenderCount++;
        console.error('error');
        console.info('info');
        console.log('log');
        console.warn('warn');
        React.useState(0);
        return null;
      });
  
      const container = document.createElement('div');
      const root = ReactDOM.createRoot(container);
      await utils.actAsync(() => root.render(<Target a={1} b="abc" />));
  
      expect(targetRenderCount).toBe(1);
      expect(console.error).toHaveBeenCalledTimes(1);
      expect(console.error).toHaveBeenCalledWith('error');
      expect(console.info).toHaveBeenCalledTimes(1);
      expect(console.info).toHaveBeenCalledWith('info');
      expect(console.log).toHaveBeenCalledTimes(1);
      expect(console.log).toHaveBeenCalledWith('log');
      expect(console.warn).toHaveBeenCalledTimes(1);
      expect(console.warn).toHaveBeenCalledWith('warn');
  
      const inspectedElement = await inspectElementAtIndex(0);
  
      expect(inspectedElement).not.toBe(null);
      expect(targetRenderCount).toBe(2);
      expect(console.error).toHaveBeenCalledTimes(1);
      expect(console.info).toHaveBeenCalledTimes(1);
      expect(console.log).toHaveBeenCalledTimes(1);
      expect(console.warn).toHaveBeenCalledTimes(1);
    });
  
    it('should support simple data types', async () => {
      const Example = () => null;
  
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(
          <Example
            boolean_false={false}
            boolean_true={true}
            infinity={Infinity}
            integer_zero={0}
            integer_one={1}
            float={1.23}
            string="abc"
            string_empty=""
            nan={NaN}
            value_null={null}
            value_undefined={undefined}
          />,
          container,
        ),
      );
  
      const inspectedElement = await inspectElementAtIndex(0);
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "boolean_false": false,
          "boolean_true": true,
          "float": 1.23,
          "infinity": Infinity,
          "integer_one": 1,
          "integer_zero": 0,
          "nan": NaN,
          "string": "abc",
          "string_empty": "",
          "value_null": null,
          "value_undefined": undefined,
        }
      `);
    });
  
    it('should support complex data types', async () => {
      const Immutable = require('immutable');
  
      const Example = () => null;
  
      const arrayOfArrays = [[['abc', 123, true], []]];
      const div = document.createElement('div');
      const exampleFunction = () => {};
      const exampleDateISO = '2019-12-31T23:42:42.000Z';
      const setShallow = new Set(['abc', 123]);
      const mapShallow = new Map([
        ['name', 'Brian'],
        ['food', 'sushi'],
      ]);
      const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
      const mapOfMaps = new Map([
        ['first', mapShallow],
        ['second', mapShallow],
      ]);
      const objectOfObjects = {
        inner: {string: 'abc', number: 123, boolean: true},
      };
      const objectWithSymbol = {
        [Symbol('name')]: 'hello',
      };
      const typedArray = Int8Array.from([100, -100, 0]);
      const arrayBuffer = typedArray.buffer;
      const dataView = new DataView(arrayBuffer);
      const immutableMap = Immutable.fromJS({
        a: [{hello: 'there'}, 'fixed', true],
        b: 123,
        c: {
          '1': 'xyz',
          xyz: 1,
        },
      });
  
      class Class {
        anonymousFunction = () => {};
      }
      const instance = new Class();
  
      const proxyInstance = new Proxy(() => {}, {
        get: function(_, name) {
          return function() {
            return null;
          };
        },
      });
  
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(
          <Example
            anonymous_fn={instance.anonymousFunction}
            array_buffer={arrayBuffer}
            array_of_arrays={arrayOfArrays}
            // eslint-disable-next-line no-undef
            big_int={BigInt(123)}
            bound_fn={exampleFunction.bind(this)}
            data_view={dataView}
            date={new Date(exampleDateISO)}
            fn={exampleFunction}
            html_element={div}
            immutable={immutableMap}
            map={mapShallow}
            map_of_maps={mapOfMaps}
            object_of_objects={objectOfObjects}
            object_with_symbol={objectWithSymbol}
            proxy={proxyInstance}
            react_element={<span />}
            regexp={/abc/giu}
            set={setShallow}
            set_of_sets={setOfSets}
            symbol={Symbol('symbol')}
            typed_array={typedArray}
          />,
          container,
        ),
      );
  
      const inspectedElement = await inspectElementAtIndex(0);
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "anonymous_fn": Dehydrated {
            "preview_short": ƒ () {},
            "preview_long": ƒ () {},
          },
          "array_buffer": Dehydrated {
            "preview_short": ArrayBuffer(3),
            "preview_long": ArrayBuffer(3),
          },
          "array_of_arrays": Array [
            Dehydrated {
              "preview_short": Array(2),
              "preview_long": [Array(3), Array(0)],
            },
          ],
          "big_int": Dehydrated {
            "preview_short": 123n,
            "preview_long": 123n,
          },
          "bound_fn": Dehydrated {
            "preview_short": ƒ bound exampleFunction() {},
            "preview_long": ƒ bound exampleFunction() {},
          },
          "data_view": Dehydrated {
            "preview_short": DataView(3),
            "preview_long": DataView(3),
          },
          "date": Dehydrated {
            "preview_short": Tue Dec 31 2019 23:42:42 GMT+0000 (Coordinated Universal Time),
            "preview_long": Tue Dec 31 2019 23:42:42 GMT+0000 (Coordinated Universal Time),
          },
          "fn": Dehydrated {
            "preview_short": ƒ exampleFunction() {},
            "preview_long": ƒ exampleFunction() {},
          },
          "html_element": Dehydrated {
            "preview_short": <div />,
            "preview_long": <div />,
          },
          "immutable": Object {
            "0": Dehydrated {
              "preview_short": Array(2),
              "preview_long": ["a", List(3)],
            },
            "1": Dehydrated {
              "preview_short": Array(2),
              "preview_long": ["b", 123],
            },
            "2": Dehydrated {
              "preview_short": Array(2),
              "preview_long": ["c", Map(2)],
            },
          },
          "map": Object {
            "0": Dehydrated {
              "preview_short": Array(2),
              "preview_long": ["name", "Brian"],
            },
            "1": Dehydrated {
              "preview_short": Array(2),
              "preview_long": ["food", "sushi"],
            },
          },
          "map_of_maps": Object {
            "0": Dehydrated {
              "preview_short": Array(2),
              "preview_long": ["first", Map(2)],
            },
            "1": Dehydrated {
              "preview_short": Array(2),
              "preview_long": ["second", Map(2)],
            },
          },
          "object_of_objects": Object {
            "inner": Dehydrated {
              "preview_short": {…},
              "preview_long": {boolean: true, number: 123, string: "abc"},
            },
          },
          "object_with_symbol": Object {
            "Symbol(name)": "hello",
          },
          "proxy": Dehydrated {
            "preview_short": ƒ () {},
            "preview_long": ƒ () {},
          },
          "react_element": Dehydrated {
            "preview_short": <span />,
            "preview_long": <span />,
          },
          "regexp": Dehydrated {
            "preview_short": /abc/giu,
            "preview_long": /abc/giu,
          },
          "set": Object {
            "0": "abc",
            "1": 123,
          },
          "set_of_sets": Object {
            "0": Dehydrated {
              "preview_short": Set(3),
              "preview_long": Set(3) {"a", "b", "c"},
            },
            "1": Dehydrated {
              "preview_short": Set(3),
              "preview_long": Set(3) {1, 2, 3},
            },
          },
          "symbol": Dehydrated {
            "preview_short": Symbol(symbol),
            "preview_long": Symbol(symbol),
          },
          "typed_array": Object {
            "0": 100,
            "1": -100,
            "2": 0,
          },
        }
      `);
    });
  
    it('should not consume iterables while inspecting', async () => {
      const Example = () => null;
  
      function* generator() {
        throw Error('Should not be consumed!');
      }
  
      const container = document.createElement('div');
  
      const iterable = generator();
      await utils.actAsync(() =>
        ReactDOM.render(<Example prop={iterable} />, container),
      );
  
      const inspectedElement = await inspectElementAtIndex(0);
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "prop": Dehydrated {
            "preview_short": Generator,
            "preview_long": Generator,
          },
        }
      `);
    });
  
    it('should support objects with no prototype', async () => {
      const Example = () => null;
  
      const object = Object.create(null);
      object.string = 'abc';
      object.number = 123;
      object.boolean = true;
  
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(<Example object={object} />, container),
      );
  
      const inspectedElement = await inspectElementAtIndex(0);
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "object": Object {
            "boolean": true,
            "number": 123,
            "string": "abc",
          },
        }
      `);
    });
  
    it('should support objects with overridden hasOwnProperty', async () => {
      const Example = () => null;
  
      const object = {
        name: 'blah',
        hasOwnProperty: true,
      };
  
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(<Example object={object} />, container),
      );
  
      const inspectedElement = await inspectElementAtIndex(0);
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "object": Object {
            "hasOwnProperty": true,
            "name": "blah",
          },
        }
      `);
    });
  
    it('should support custom objects with enumerable properties and getters', async () => {
      class CustomData {
        _number = 42;
        get number() {
          return this._number;
        }
        set number(value) {
          this._number = value;
        }
      }
  
      const descriptor = ((Object.getOwnPropertyDescriptor(
        CustomData.prototype,
        'number',
      ): any): PropertyDescriptor<number>);
      descriptor.enumerable = true;
      Object.defineProperty(CustomData.prototype, 'number', descriptor);
  
      const Example = () => null;
  
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(<Example data={new CustomData()} />, container),
      );
  
      const inspectedElement = await inspectElementAtIndex(0);
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "data": Object {
            "_number": 42,
            "number": 42,
          },
        }
      `);
    });
  
    it('should support objects with with inherited keys', async () => {
      const Example = () => null;
  
      const base = Object.create(Object.prototype, {
        enumerableStringBase: {
          value: 1,
          writable: true,
          enumerable: true,
          configurable: true,
        },
        [Symbol('enumerableSymbolBase')]: {
          value: 1,
          writable: true,
          enumerable: true,
          configurable: true,
        },
        nonEnumerableStringBase: {
          value: 1,
          writable: true,
          enumerable: false,
          configurable: true,
        },
        [Symbol('nonEnumerableSymbolBase')]: {
          value: 1,
          writable: true,
          enumerable: false,
          configurable: true,
        },
      });
  
      const object = Object.create(base, {
        enumerableString: {
          value: 2,
          writable: true,
          enumerable: true,
          configurable: true,
        },
        nonEnumerableString: {
          value: 3,
          writable: true,
          enumerable: false,
          configurable: true,
        },
        123: {
          value: 3,
          writable: true,
          enumerable: true,
          configurable: true,
        },
        [Symbol('nonEnumerableSymbol')]: {
          value: 2,
          writable: true,
          enumerable: false,
          configurable: true,
        },
        [Symbol('enumerableSymbol')]: {
          value: 3,
          writable: true,
          enumerable: true,
          configurable: true,
        },
      });
  
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(<Example object={object} />, container),
      );
  
      const inspectedElement = await inspectElementAtIndex(0);
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "object": Object {
            "123": 3,
            "Symbol(enumerableSymbol)": 3,
            "Symbol(enumerableSymbolBase)": 1,
            "enumerableString": 2,
            "enumerableStringBase": 1,
          },
        }
      `);
    });
  
    it('should allow component prop value and value`s prototype has same name params.', async () => {
      const testData = Object.create(
        {
          a: undefined,
          b: Infinity,
          c: NaN,
          d: 'normal',
        },
        {
          a: {
            value: undefined,
            writable: true,
            enumerable: true,
            configurable: true,
          },
          b: {
            value: Infinity,
            writable: true,
            enumerable: true,
            configurable: true,
          },
          c: {
            value: NaN,
            writable: true,
            enumerable: true,
            configurable: true,
          },
          d: {
            value: 'normal',
            writable: true,
            enumerable: true,
            configurable: true,
          },
        },
      );
      const Example = ({data}) => null;
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(<Example data={testData} />, container),
      );
  
      const inspectedElement = await inspectElementAtIndex(0);
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "data": Object {
            "a": undefined,
            "b": Infinity,
            "c": NaN,
            "d": "normal",
          },
        }
      `);
    });
  
    it('should not dehydrate nested values until explicitly requested', async () => {
      const Example = () => {
        const [state] = React.useState({
          foo: {
            bar: {
              baz: 'hi',
            },
          },
        });
  
        return state.foo.bar.baz;
      };
  
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(
          <Example
            nestedObject={{
              a: {
                b: {
                  c: [
                    {
                      d: {
                        e: {},
                      },
                    },
                  ],
                },
              },
            }}
          />,
          container,
        ),
      );
  
      let inspectedElement = null;
      let inspectElementPath = null;
  
      // Render once to get a handle on inspectElementPath()
      inspectedElement = await inspectElementAtIndex(0, () => {
        inspectElementPath = useInspectElementPath();
      });
  
      async function loadPath(path) {
        TestUtilsAct(() => {
          TestRendererAct(() => {
            inspectElementPath(path);
            jest.runOnlyPendingTimers();
          });
        });
  
        inspectedElement = await inspectElementAtIndex(0);
      }
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "nestedObject": Object {
            "a": Dehydrated {
              "preview_short": {…},
              "preview_long": {b: {…}},
            },
          },
        }
      `);
  
      await loadPath(['props', 'nestedObject', 'a']);
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "nestedObject": Object {
            "a": Object {
              "b": Object {
                "c": Dehydrated {
                  "preview_short": Array(1),
                  "preview_long": [{…}],
                },
              },
            },
          },
        }
      `);
  
      await loadPath(['props', 'nestedObject', 'a', 'b', 'c']);
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "nestedObject": Object {
            "a": Object {
              "b": Object {
                "c": Array [
                  Object {
                    "d": Dehydrated {
                      "preview_short": {…},
                      "preview_long": {e: {…}},
                    },
                  },
                ],
              },
            },
          },
        }
      `);
  
      await loadPath(['props', 'nestedObject', 'a', 'b', 'c', 0, 'd']);
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "nestedObject": Object {
            "a": Object {
              "b": Object {
                "c": Array [
                  Object {
                    "d": Object {
                      "e": Object {},
                    },
                  },
                ],
              },
            },
          },
        }
      `);
  
      await loadPath(['hooks', 0, 'value']);
  
      expect(inspectedElement.hooks).toMatchInlineSnapshot(`
        Array [
          Object {
            "id": 0,
            "isStateEditable": true,
            "name": "State",
            "subHooks": Array [],
            "value": Object {
              "foo": Object {
                "bar": Dehydrated {
                  "preview_short": {…},
                  "preview_long": {baz: "hi"},
                },
              },
            },
          },
        ]
      `);
  
      await loadPath(['hooks', 0, 'value', 'foo', 'bar']);
  
      expect(inspectedElement.hooks).toMatchInlineSnapshot(`
        Array [
          Object {
            "id": 0,
            "isStateEditable": true,
            "name": "State",
            "subHooks": Array [],
            "value": Object {
              "foo": Object {
                "bar": Object {
                  "baz": "hi",
                },
              },
            },
          },
        ]
      `);
    });
  
    it('should dehydrate complex nested values when requested', async () => {
      const Example = () => null;
  
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(
          <Example
            set_of_sets={new Set([new Set([1, 2, 3]), new Set(['a', 'b', 'c'])])}
          />,
          container,
        ),
      );
  
      let inspectedElement = null;
      let inspectElementPath = null;
  
      // Render once to get a handle on inspectElementPath()
      inspectedElement = await inspectElementAtIndex(0, () => {
        inspectElementPath = useInspectElementPath();
      });
  
      async function loadPath(path) {
        TestUtilsAct(() => {
          TestRendererAct(() => {
            inspectElementPath(path);
            jest.runOnlyPendingTimers();
          });
        });
  
        inspectedElement = await inspectElementAtIndex(0);
      }
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "set_of_sets": Object {
            "0": Dehydrated {
              "preview_short": Set(3),
              "preview_long": Set(3) {1, 2, 3},
            },
            "1": Dehydrated {
              "preview_short": Set(3),
              "preview_long": Set(3) {"a", "b", "c"},
            },
          },
        }
      `);
  
      await loadPath(['props', 'set_of_sets', 0]);
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "set_of_sets": Object {
            "0": Object {
              "0": 1,
              "1": 2,
              "2": 3,
            },
            "1": Dehydrated {
              "preview_short": Set(3),
              "preview_long": Set(3) {"a", "b", "c"},
            },
          },
        }
      `);
    });
  
    it('should include updates for nested values that were previously hydrated', async () => {
      const Example = () => null;
  
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(
          <Example
            nestedObject={{
              a: {
                value: 1,
                b: {
                  value: 1,
                },
              },
              c: {
                value: 1,
                d: {
                  value: 1,
                  e: {
                    value: 1,
                  },
                },
              },
            }}
          />,
          container,
        ),
      );
  
      let inspectedElement = null;
      let inspectElementPath = null;
  
      // Render once to get a handle on inspectElementPath()
      inspectedElement = await inspectElementAtIndex(0, () => {
        inspectElementPath = useInspectElementPath();
      });
  
      async function loadPath(path) {
        TestUtilsAct(() => {
          TestRendererAct(() => {
            inspectElementPath(path);
            jest.runOnlyPendingTimers();
          });
        });
  
        inspectedElement = await inspectElementAtIndex(0);
      }
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "nestedObject": Object {
            "a": Dehydrated {
              "preview_short": {…},
              "preview_long": {b: {…}, value: 1},
            },
            "c": Dehydrated {
              "preview_short": {…},
              "preview_long": {d: {…}, value: 1},
            },
          },
        }
      `);
  
      await loadPath(['props', 'nestedObject', 'a']);
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "nestedObject": Object {
            "a": Object {
              "b": Object {
                "value": 1,
              },
              "value": 1,
            },
            "c": Dehydrated {
              "preview_short": {…},
              "preview_long": {d: {…}, value: 1},
            },
          },
        }
      `);
  
      await loadPath(['props', 'nestedObject', 'c']);
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "nestedObject": Object {
            "a": Object {
              "b": Object {
                "value": 1,
              },
              "value": 1,
            },
            "c": Object {
              "d": Object {
                "e": Dehydrated {
                  "preview_short": {…},
                  "preview_long": {value: 1},
                },
                "value": 1,
              },
              "value": 1,
            },
          },
        }
      `);
  
      TestRendererAct(() => {
        TestUtilsAct(() => {
          ReactDOM.render(
            <Example
              nestedObject={{
                a: {
                  value: 2,
                  b: {
                    value: 2,
                  },
                },
                c: {
                  value: 2,
                  d: {
                    value: 2,
                    e: {
                      value: 2,
                    },
                  },
                },
              }}
            />,
            container,
          );
        });
      });
  
      // Wait for pending poll-for-update and then update inspected element data.
      jest.runOnlyPendingTimers();
      await Promise.resolve();
      inspectedElement = await inspectElementAtIndex(0);
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "nestedObject": Object {
            "a": Object {
              "b": Object {
                "value": 2,
              },
              "value": 2,
            },
            "c": Object {
              "d": Object {
                "e": Dehydrated {
                  "preview_short": {…},
                  "preview_long": {value: 2},
                },
                "value": 2,
              },
              "value": 2,
            },
          },
        }
      `);
    });
  
    it('should return a full update if a path is inspected for an object that has other pending changes', async () => {
      const Example = () => null;
  
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(
          <Example
            nestedObject={{
              a: {
                value: 1,
                b: {
                  value: 1,
                },
              },
              c: {
                value: 1,
                d: {
                  value: 1,
                  e: {
                    value: 1,
                  },
                },
              },
            }}
          />,
          container,
        ),
      );
  
      let inspectedElement = null;
      let inspectElementPath = null;
  
      // Render once to get a handle on inspectElementPath()
      inspectedElement = await inspectElementAtIndex(0, () => {
        inspectElementPath = useInspectElementPath();
      });
  
      async function loadPath(path) {
        TestUtilsAct(() => {
          TestRendererAct(() => {
            inspectElementPath(path);
            jest.runOnlyPendingTimers();
          });
        });
  
        inspectedElement = await inspectElementAtIndex(0);
      }
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "nestedObject": Object {
            "a": Dehydrated {
              "preview_short": {…},
              "preview_long": {b: {…}, value: 1},
            },
            "c": Dehydrated {
              "preview_short": {…},
              "preview_long": {d: {…}, value: 1},
            },
          },
        }
      `);
  
      await loadPath(['props', 'nestedObject', 'a']);
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "nestedObject": Object {
            "a": Object {
              "b": Object {
                "value": 1,
              },
              "value": 1,
            },
            "c": Dehydrated {
              "preview_short": {…},
              "preview_long": {d: {…}, value: 1},
            },
          },
        }
      `);
  
      TestRendererAct(() => {
        TestUtilsAct(() => {
          ReactDOM.render(
            <Example
              nestedObject={{
                a: {
                  value: 2,
                  b: {
                    value: 2,
                  },
                },
                c: {
                  value: 2,
                  d: {
                    value: 2,
                    e: {
                      value: 2,
                    },
                  },
                },
              }}
            />,
            container,
          );
        });
      });
  
      await loadPath(['props', 'nestedObject', 'c']);
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "nestedObject": Object {
            "a": Object {
              "b": Object {
                "value": 2,
              },
              "value": 2,
            },
            "c": Object {
              "d": Object {
                "e": Dehydrated {
                  "preview_short": {…},
                  "preview_long": {value: 2},
                },
                "value": 2,
              },
              "value": 2,
            },
          },
        }
      `);
    });
  
    it('should not tear if hydration is requested after an update', async () => {
      const Example = () => null;
  
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(
          <Example
            nestedObject={{
              value: 1,
              a: {
                value: 1,
                b: {
                  value: 1,
                },
              },
            }}
          />,
          container,
        ),
      );
  
      let inspectedElement = null;
      let inspectElementPath = null;
  
      // Render once to get a handle on inspectElementPath()
      inspectedElement = await inspectElementAtIndex(0, () => {
        inspectElementPath = useInspectElementPath();
      });
  
      async function loadPath(path) {
        TestUtilsAct(() => {
          TestRendererAct(() => {
            inspectElementPath(path);
            jest.runOnlyPendingTimers();
          });
        });
  
        inspectedElement = await inspectElementAtIndex(0);
      }
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "nestedObject": Object {
            "a": Dehydrated {
              "preview_short": {…},
              "preview_long": {b: {…}, value: 1},
            },
            "value": 1,
          },
        }
      `);
  
      TestUtilsAct(() => {
        ReactDOM.render(
          <Example
            nestedObject={{
              value: 2,
              a: {
                value: 2,
                b: {
                  value: 2,
                },
              },
            }}
          />,
          container,
        );
      });
  
      await loadPath(['props', 'nestedObject', 'a']);
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "nestedObject": Object {
            "a": Object {
              "b": Object {
                "value": 2,
              },
              "value": 2,
            },
            "value": 2,
          },
        }
      `);
    });
  
    it('should inspect hooks for components that only use context', async () => {
      const Context = React.createContext(true);
      const Example = () => {
        const value = React.useContext(Context);
        return value;
      };
  
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(<Example a={1} b="abc" />, container),
      );
  
      const inspectedElement = await inspectElementAtIndex(0);
      expect(inspectedElement).toMatchInlineSnapshot(`
        Object {
          "context": null,
          "events": undefined,
          "hooks": Array [
            Object {
              "id": null,
              "isStateEditable": false,
              "name": "Context",
              "subHooks": Array [],
              "value": true,
            },
          ],
          "id": 2,
          "owners": null,
          "props": Object {
            "a": 1,
            "b": "abc",
          },
          "state": null,
        }
      `);
    });
  
    it('should enable inspected values to be stored as global variables', async () => {
      const Example = () => null;
  
      const nestedObject = {
        a: {
          value: 1,
          b: {
            value: 1,
            c: {
              value: 1,
            },
          },
        },
      };
  
      await utils.actAsync(() =>
        ReactDOM.render(
          <Example nestedObject={nestedObject} />,
          document.createElement('div'),
        ),
      );
  
      let storeAsGlobal: StoreAsGlobal = ((null: any): StoreAsGlobal);
  
      const id = ((store.getElementIDAtIndex(0): any): number);
      await inspectElementAtIndex(0, () => {
        storeAsGlobal = (path: Array<string | number>) => {
          const rendererID = store.getRendererIDForElement(id);
          if (rendererID !== null) {
            const {
              storeAsGlobal: storeAsGlobalAPI,
            } = require('react-devtools-shared/src/backendAPI');
            storeAsGlobalAPI({
              bridge,
              id,
              path,
              rendererID,
            });
          }
        };
      });
  
      jest.spyOn(console, 'log').mockImplementation(() => {});
  
      // Should store the whole value (not just the hydrated parts)
      storeAsGlobal(['props', 'nestedObject']);
      jest.runOnlyPendingTimers();
      expect(console.log).toHaveBeenCalledWith('$reactTemp0');
      expect(global.$reactTemp0).toBe(nestedObject);
  
      console.log.mockReset();
  
      // Should store the nested property specified (not just the outer value)
      storeAsGlobal(['props', 'nestedObject', 'a', 'b']);
      jest.runOnlyPendingTimers();
      expect(console.log).toHaveBeenCalledWith('$reactTemp1');
      expect(global.$reactTemp1).toBe(nestedObject.a.b);
    });
  
    it('should enable inspected values to be copied to the clipboard', async () => {
      const Example = () => null;
  
      const nestedObject = {
        a: {
          value: 1,
          b: {
            value: 1,
            c: {
              value: 1,
            },
          },
        },
      };
  
      await utils.actAsync(() =>
        ReactDOM.render(
          <Example nestedObject={nestedObject} />,
          document.createElement('div'),
        ),
      );
  
      let copyPath: CopyInspectedElementPath = ((null: any): CopyInspectedElementPath);
  
      const id = ((store.getElementIDAtIndex(0): any): number);
      await inspectElementAtIndex(0, () => {
        copyPath = (path: Array<string | number>) => {
          const rendererID = store.getRendererIDForElement(id);
          if (rendererID !== null) {
            const {
              copyInspectedElementPath,
            } = require('react-devtools-shared/src/backendAPI');
            copyInspectedElementPath({
              bridge,
              id,
              path,
              rendererID,
            });
          }
        };
      });
  
      // Should copy the whole value (not just the hydrated parts)
      copyPath(['props', 'nestedObject']);
      jest.runOnlyPendingTimers();
      expect(global.mockClipboardCopy).toHaveBeenCalledTimes(1);
      expect(global.mockClipboardCopy).toHaveBeenCalledWith(
        JSON.stringify(nestedObject),
      );
  
      global.mockClipboardCopy.mockReset();
  
      // Should copy the nested property specified (not just the outer value)
      copyPath(['props', 'nestedObject', 'a', 'b']);
      jest.runOnlyPendingTimers();
      expect(global.mockClipboardCopy).toHaveBeenCalledTimes(1);
      expect(global.mockClipboardCopy).toHaveBeenCalledWith(
        JSON.stringify(nestedObject.a.b),
      );
    });
  
    it('should enable complex values to be copied to the clipboard', async () => {
      const Immutable = require('immutable');
  
      const Example = () => null;
  
      const set = new Set(['abc', 123]);
      const map = new Map([
        ['name', 'Brian'],
        ['food', 'sushi'],
      ]);
      const setOfSets = new Set([new Set(['a', 'b', 'c']), new Set([1, 2, 3])]);
      const mapOfMaps = new Map([
        ['first', map],
        ['second', map],
      ]);
      const typedArray = Int8Array.from([100, -100, 0]);
      const arrayBuffer = typedArray.buffer;
      const dataView = new DataView(arrayBuffer);
      const immutable = Immutable.fromJS({
        a: [{hello: 'there'}, 'fixed', true],
        b: 123,
        c: {
          '1': 'xyz',
          xyz: 1,
        },
      });
      // $FlowFixMe
      const bigInt = BigInt(123); // eslint-disable-line no-undef
  
      await utils.actAsync(() =>
        ReactDOM.render(
          <Example
            arrayBuffer={arrayBuffer}
            dataView={dataView}
            map={map}
            set={set}
            mapOfMaps={mapOfMaps}
            setOfSets={setOfSets}
            typedArray={typedArray}
            immutable={immutable}
            bigInt={bigInt}
          />,
          document.createElement('div'),
        ),
      );
  
      const id = ((store.getElementIDAtIndex(0): any): number);
  
      let copyPath: CopyInspectedElementPath = ((null: any): CopyInspectedElementPath);
  
      await inspectElementAtIndex(0, () => {
        copyPath = (path: Array<string | number>) => {
          const rendererID = store.getRendererIDForElement(id);
          if (rendererID !== null) {
            const {
              copyInspectedElementPath,
            } = require('react-devtools-shared/src/backendAPI');
            copyInspectedElementPath({
              bridge,
              id,
              path,
              rendererID,
            });
          }
        };
      });
  
      // Should copy the whole value (not just the hydrated parts)
      copyPath(['props']);
      jest.runOnlyPendingTimers();
      // Should not error despite lots of unserialized values.
  
      global.mockClipboardCopy.mockReset();
  
      // Should copy the nested property specified (not just the outer value)
      copyPath(['props', 'bigInt']);
      jest.runOnlyPendingTimers();
      expect(global.mockClipboardCopy).toHaveBeenCalledTimes(1);
      expect(global.mockClipboardCopy).toHaveBeenCalledWith(
        JSON.stringify('123n'),
      );
  
      global.mockClipboardCopy.mockReset();
  
      // Should copy the nested property specified (not just the outer value)
      copyPath(['props', 'typedArray']);
      jest.runOnlyPendingTimers();
      expect(global.mockClipboardCopy).toHaveBeenCalledTimes(1);
      expect(global.mockClipboardCopy).toHaveBeenCalledWith(
        JSON.stringify({0: 100, 1: -100, 2: 0}),
      );
    });
  
    it('should display complex values of useDebugValue', async () => {
      const container = document.createElement('div');
  
      function useDebuggableHook() {
        React.useDebugValue({foo: 2});
        React.useState(1);
        return 1;
      }
      function DisplayedComplexValue() {
        useDebuggableHook();
        return null;
      }
  
      await utils.actAsync(() =>
        ReactDOM.render(<DisplayedComplexValue />, container),
      );
  
      const {hooks} = await inspectElementAtIndex(0);
      expect(hooks).toMatchInlineSnapshot(`
        Array [
          Object {
            "id": null,
            "isStateEditable": false,
            "name": "DebuggableHook",
            "subHooks": Array [
              Object {
                "id": 0,
                "isStateEditable": true,
                "name": "State",
                "subHooks": Array [],
                "value": 1,
              },
            ],
            "value": Object {
              "foo": 2,
            },
          },
        ]
      `);
    });
  
    // See github.com/facebook/react/issues/21654
    it('should support Proxies that dont return an iterator', async () => {
      const Example = () => null;
      const proxy = new Proxy(
        {},
        {
          get: (target, prop, receiver) => {
            target[prop] = value => {};
            return target[prop];
          },
        },
      );
  
      const container = document.createElement('div');
      await utils.actAsync(() =>
        ReactDOM.render(<Example proxy={proxy} />, container),
      );
  
      const inspectedElement = await inspectElementAtIndex(0);
  
      expect(inspectedElement.props).toMatchInlineSnapshot(`
        Object {
          "proxy": Object {
            "$$typeof": Dehydrated {
              "preview_short": ƒ () {},
              "preview_long": ƒ () {},
            },
            "Symbol(Symbol.iterator)": Dehydrated {
              "preview_short": ƒ () {},
              "preview_long": ƒ () {},
            },
            "constructor": Dehydrated {
              "preview_short": ƒ () {},
              "preview_long": ƒ () {},
            },
          },
        }
      `);
    });
  
    describe('$r', () => {
      it('should support function components', async () => {
        const Example = () => {
          const [count] = React.useState(1);
          return count;
        };
  
        const container = document.createElement('div');
        await utils.actAsync(() =>
          ReactDOM.render(<Example a={1} b="abc" />, container),
        );
  
        await inspectElementAtIndex(0);
  
        expect(global.$r).toMatchInlineSnapshot(`
          Object {
            "hooks": Array [
              Object {
                "id": 0,
                "isStateEditable": true,
                "name": "State",
                "subHooks": Array [],
                "value": 1,
              },
            ],
            "props": Object {
              "a": 1,
              "b": "abc",
            },
            "type": [Function],
          }
        `);
      });
  
      it('should support memoized function components', async () => {
        const Example = React.memo(function Example(props) {
          const [count] = React.useState(1);
          return count;
        });
  
        const container = document.createElement('div');
        await utils.actAsync(() =>
          ReactDOM.render(<Example a={1} b="abc" />, container),
        );
  
        await inspectElementAtIndex(0);
  
        expect(global.$r).toMatchInlineSnapshot(`
          Object {
            "hooks": Array [
              Object {
                "id": 0,
                "isStateEditable": true,
                "name": "State",
                "subHooks": Array [],
                "value": 1,
              },
            ],
            "props": Object {
              "a": 1,
              "b": "abc",
            },
            "type": [Function],
          }
        `);
      });
  
      it('should support forward refs', async () => {
        const Example = React.forwardRef(function Example(props, ref) {
          const [count] = React.useState(1);
          return count;
        });
  
        const container = document.createElement('div');
        await utils.actAsync(() =>
          ReactDOM.render(<Example a={1} b="abc" />, container),
        );
  
        await inspectElementAtIndex(0);
  
        expect(global.$r).toMatchInlineSnapshot(`
          Object {
            "hooks": Array [
              Object {
                "id": 0,
                "isStateEditable": true,
                "name": "State",
                "subHooks": Array [],
                "value": 1,
              },
            ],
            "props": Object {
              "a": 1,
              "b": "abc",
            },
            "type": [Function],
          }
        `);
      });
  
      it('should support class components', async () => {
        class Example extends React.Component {
          state = {
            count: 0,
          };
          render() {
            return null;
          }
        }
  
        const container = document.createElement('div');
        await utils.actAsync(() =>
          ReactDOM.render(<Example a={1} b="abc" />, container),
        );
  
        await inspectElementAtIndex(0);
  
        expect(global.$r.props).toMatchInlineSnapshot(`
                Object {
                  "a": 1,
                  "b": "abc",
                }
              `);
        expect(global.$r.state).toMatchInlineSnapshot(`
                Object {
                  "count": 0,
                }
              `);
      });
    });
  
    describe('inline errors and warnings', () => {
      async function getErrorsAndWarningsForElementAtIndex(index) {
        const id = ((store.getElementIDAtIndex(index): any): number);
        if (id == null) {
          throw Error(`Element at index "${index}"" not found in store`);
        }
  
        let errors = null;
        let warnings = null;
  
        function Suspender({target}) {
          const inspectedElement = useInspectedElement();
          errors = inspectedElement.errors;
          warnings = inspectedElement.warnings;
          return null;
        }
  
        let root;
        await utils.actAsync(() => {
          root = TestRenderer.create(
            <Contexts
              defaultSelectedElementID={id}
              defaultSelectedElementIndex={index}>
              <React.Suspense fallback={null}>
                <Suspender target={id} />
              </React.Suspense>
            </Contexts>,
            {unstable_isConcurrent: true},
          );
        }, false);
        await utils.actAsync(() => {
          root.unmount();
        }, false);
  
        return {errors, warnings};
      }
  
      it('during render get recorded', async () => {
        const Example = () => {
          console.error('test-only: render error');
          console.warn('test-only: render warning');
          return null;
        };
  
        const container = document.createElement('div');
  
        await withErrorsOrWarningsIgnored(['test-only: '], async () => {
          await utils.actAsync(() =>
            ReactDOM.render(<Example repeatWarningCount={1} />, container),
          );
        });
  
        const data = await getErrorsAndWarningsForElementAtIndex(0);
        expect(data).toMatchInlineSnapshot(`
          Object {
            "errors": Array [
              Array [
                "test-only: render error",
                1,
              ],
            ],
            "warnings": Array [
              Array [
                "test-only: render warning",
                1,
              ],
            ],
          }
        `);
      });
  
      it('during render get deduped', async () => {
        const Example = () => {
          console.error('test-only: render error');
          console.error('test-only: render error');
          console.warn('test-only: render warning');
          console.warn('test-only: render warning');
          console.warn('test-only: render warning');
          return null;
        };
  
        const container = document.createElement('div');
        await utils.withErrorsOrWarningsIgnored(['test-only:'], async () => {
          await utils.actAsync(() =>
            ReactDOM.render(<Example repeatWarningCount={1} />, container),
          );
        });
        const data = await getErrorsAndWarningsForElementAtIndex(0);
        expect(data).toMatchInlineSnapshot(`
          Object {
            "errors": Array [
              Array [
                "test-only: render error",
                2,
              ],
            ],
            "warnings": Array [
              Array [
                "test-only: render warning",
                3,
              ],
            ],
          }
        `);
      });
  
      it('during layout (mount) get recorded', async () => {
        const Example = () => {
          // Note we only test mount because once the component unmounts,
          // it is no longer in the store and warnings are ignored.
          React.useLayoutEffect(() => {
            console.error('test-only: useLayoutEffect error');
            console.warn('test-only: useLayoutEffect warning');
          }, []);
          return null;
        };
  
        const container = document.createElement('div');
        await utils.withErrorsOrWarningsIgnored(['test-only:'], async () => {
          await utils.actAsync(() =>
            ReactDOM.render(<Example repeatWarningCount={1} />, container),
          );
        });
  
        const data = await getErrorsAndWarningsForElementAtIndex(0);
        expect(data).toMatchInlineSnapshot(`
          Object {
            "errors": Array [
              Array [
                "test-only: useLayoutEffect error",
                1,
              ],
            ],
            "warnings": Array [
              Array [
                "test-only: useLayoutEffect warning",
                1,
              ],
            ],
          }
        `);
      });
  
      it('during passive (mount) get recorded', async () => {
        const Example = () => {
          // Note we only test mount because once the component unmounts,
          // it is no longer in the store and warnings are ignored.
          React.useEffect(() => {
            console.error('test-only: useEffect error');
            console.warn('test-only: useEffect warning');
          }, []);
          return null;
        };
  
        const container = document.createElement('div');
        await utils.withErrorsOrWarningsIgnored(['test-only:'], async () => {
          await utils.actAsync(() =>
            ReactDOM.render(<Example repeatWarningCount={1} />, container),
          );
        });
  
        const data = await getErrorsAndWarningsForElementAtIndex(0);
        expect(data).toMatchInlineSnapshot(`
          Object {
            "errors": Array [
              Array [
                "test-only: useEffect error",
                1,
              ],
            ],
            "warnings": Array [
              Array [
                "test-only: useEffect warning",
                1,
              ],
            ],
          }
        `);
      });
  
      it('from react get recorded without a component stack', async () => {
        const Example = () => {
          return [<div />];
        };
  
        const container = document.createElement('div');
        await utils.withErrorsOrWarningsIgnored(
          ['Warning: Each child in a list should have a unique "key" prop.'],
          async () => {
            await utils.actAsync(() =>
              ReactDOM.render(<Example repeatWarningCount={1} />, container),
            );
          },
        );
  
        const data = await getErrorsAndWarningsForElementAtIndex(0);
        expect(data).toMatchInlineSnapshot(`
          Object {
            "errors": Array [
              Array [
                "Warning: Each child in a list should have a unique \\"key\\" prop. See https://reactjs.org/link/warning-keys for more information.
              at Example",
                1,
              ],
            ],
            "warnings": Array [],
          }
        `);
      });
  
      it('can be cleared for the whole app', async () => {
        const Example = () => {
          console.error('test-only: render error');
          console.warn('test-only: render warning');
          return null;
        };
  
        const container = document.createElement('div');
        await utils.withErrorsOrWarningsIgnored(['test-only:'], async () => {
          await utils.actAsync(() =>
            ReactDOM.render(<Example repeatWarningCount={1} />, container),
          );
        });
  
        const {
          clearErrorsAndWarnings,
        } = require('react-devtools-shared/src/backendAPI');
        clearErrorsAndWarnings({bridge, store});
  
        // Flush events to the renderer.
        jest.runOnlyPendingTimers();
  
        const data = await getErrorsAndWarningsForElementAtIndex(0);
        expect(data).toMatchInlineSnapshot(`
          Object {
            "errors": Array [],
            "warnings": Array [],
          }
        `);
      });
  
      it('can be cleared for a particular Fiber (only warnings)', async () => {
        const Example = ({id}) => {
          console.error(`test-only: render error #${id}`);
          console.warn(`test-only: render warning #${id}`);
          return null;
        };
  
        const container = document.createElement('div');
        await utils.withErrorsOrWarningsIgnored(['test-only:'], async () => {
          await utils.actAsync(() =>
            ReactDOM.render(
              <React.Fragment>
                <Example id={1} />
                <Example id={2} />
              </React.Fragment>,
              container,
            ),
          );
        });
  
        let id = ((store.getElementIDAtIndex(1): any): number);
        const rendererID = store.getRendererIDForElement(id);
  
        const {
          clearWarningsForElement,
        } = require('react-devtools-shared/src/backendAPI');
        clearWarningsForElement({bridge, id, rendererID});
  
        // Flush events to the renderer.
        jest.runOnlyPendingTimers();
  
        let data = [
          await getErrorsAndWarningsForElementAtIndex(0),
          await getErrorsAndWarningsForElementAtIndex(1),
        ];
        expect(data).toMatchInlineSnapshot(`
          Array [
            Object {
              "errors": Array [
                Array [
                  "test-only: render error #1",
                  1,
                ],
              ],
              "warnings": Array [
                Array [
                  "test-only: render warning #1",
                  1,
                ],
              ],
            },
            Object {
              "errors": Array [
                Array [
                  "test-only: render error #2",
                  1,
                ],
              ],
              "warnings": Array [],
            },
          ]
        `);
  
        id = ((store.getElementIDAtIndex(0): any): number);
        clearWarningsForElement({bridge, id, rendererID});
  
        // Flush events to the renderer.
        jest.runOnlyPendingTimers();
  
        data = [
          await getErrorsAndWarningsForElementAtIndex(0),
          await getErrorsAndWarningsForElementAtIndex(1),
        ];
        expect(data).toMatchInlineSnapshot(`
          Array [
            Object {
              "errors": Array [
                Array [
                  "test-only: render error #1",
                  1,
                ],
              ],
              "warnings": Array [],
            },
            Object {
              "errors": Array [
                Array [
                  "test-only: render error #2",
                  1,
                ],
              ],
              "warnings": Array [],
            },
          ]
        `);
      });
  
      it('can be cleared for a particular Fiber (only errors)', async () => {
        const Example = ({id}) => {
          console.error(`test-only: render error #${id}`);
          console.warn(`test-only: render warning #${id}`);
          return null;
        };
  
        const container = document.createElement('div');
        await utils.withErrorsOrWarningsIgnored(['test-only:'], async () => {
          await utils.actAsync(() =>
            ReactDOM.render(
              <React.Fragment>
                <Example id={1} />
                <Example id={2} />
              </React.Fragment>,
              container,
            ),
          );
        });
  
        let id = ((store.getElementIDAtIndex(1): any): number);
        const rendererID = store.getRendererIDForElement(id);
  
        const {
          clearErrorsForElement,
        } = require('react-devtools-shared/src/backendAPI');
        clearErrorsForElement({bridge, id, rendererID});
  
        // Flush events to the renderer.
        jest.runOnlyPendingTimers();
  
        let data = [
          await getErrorsAndWarningsForElementAtIndex(0),
          await getErrorsAndWarningsForElementAtIndex(1),
        ];
        expect(data).toMatchInlineSnapshot(`
          Array [
            Object {
              "errors": Array [
                Array [
                  "test-only: render error #1",
                  1,
                ],
              ],
              "warnings": Array [
                Array [
                  "test-only: render warning #1",
                  1,
                ],
              ],
            },
            Object {
              "errors": Array [],
              "warnings": Array [
                Array [
                  "test-only: render warning #2",
                  1,
                ],
              ],
            },
          ]
        `);
  
        id = ((store.getElementIDAtIndex(0): any): number);
        clearErrorsForElement({bridge, id, rendererID});
  
        // Flush events to the renderer.
        jest.runOnlyPendingTimers();
  
        data = [
          await getErrorsAndWarningsForElementAtIndex(0),
          await getErrorsAndWarningsForElementAtIndex(1),
        ];
        expect(data).toMatchInlineSnapshot(`
          Array [
            Object {
              "errors": Array [],
              "warnings": Array [
                Array [
                  "test-only: render warning #1",
                  1,
                ],
              ],
            },
            Object {
              "errors": Array [],
              "warnings": Array [
                Array [
                  "test-only: render warning #2",
                  1,
                ],
              ],
            },
          ]
        `);
      });
    });
  
    describe('error boundary', () => {
      it('can toggle error', async () => {
        class ErrorBoundary extends React.Component<any> {
          state = {hasError: false};
          static getDerivedStateFromError(error) {
            return {hasError: true};
          }
          render() {
            const {hasError} = this.state;
            return hasError ? 'has-error' : this.props.children;
          }
        }
        const Example = () => 'example';
  
        await utils.actAsync(() =>
          ReactDOM.render(
            <ErrorBoundary>
              <Example />
            </ErrorBoundary>,
            document.createElement('div'),
          ),
        );
  
        const targetErrorBoundaryID = ((store.getElementIDAtIndex(
          0,
        ): any): number);
        const inspect = index => {
          // HACK: Recreate TestRenderer instance so we can inspect different
          // elements
          testRendererInstance = TestRenderer.create(null, {
            unstable_isConcurrent: true,
          });
          return inspectElementAtIndex(index);
        };
        const toggleError = async forceError => {
          await withErrorsOrWarningsIgnored(['ErrorBoundary'], async () => {
            await utils.actAsync(() => {
              bridge.send('overrideError', {
                id: targetErrorBoundaryID,
                rendererID: store.getRendererIDForElement(targetErrorBoundaryID),
                forceError,
              });
            });
          });
  
          TestUtilsAct(() => {
            jest.runOnlyPendingTimers();
          });
        };
  
        // Inspect <ErrorBoundary /> and see that we cannot toggle error state
        // on error boundary itself
        let inspectedElement = await inspect(0);
        expect(inspectedElement.canToggleError).toBe(false);
        expect(inspectedElement.targetErrorBoundaryID).toBe(null);
  
        // Inspect <Example />
        inspectedElement = await inspect(1);
        expect(inspectedElement.canToggleError).toBe(true);
        expect(inspectedElement.isErrored).toBe(false);
        expect(inspectedElement.targetErrorBoundaryID).toBe(
          targetErrorBoundaryID,
        );
  
        // now force error state on <Example />
        await toggleError(true);
  
        // we are in error state now, <Example /> won't show up
        expect(store.getElementIDAtIndex(1)).toBe(null);
  
        // Inpsect <ErrorBoundary /> to toggle off the error state
        inspectedElement = await inspect(0);
        expect(inspectedElement.canToggleError).toBe(true);
        expect(inspectedElement.isErrored).toBe(true);
        // its error boundary ID is itself because it's caught the error
        expect(inspectedElement.targetErrorBoundaryID).toBe(
          targetErrorBoundaryID,
        );
  
        await toggleError(false);
  
        // We can now inspect <Example /> with ability to toggle again
        inspectedElement = await inspect(1);
        expect(inspectedElement.canToggleError).toBe(true);
        expect(inspectedElement.isErrored).toBe(false);
        expect(inspectedElement.targetErrorBoundaryID).toBe(
          targetErrorBoundaryID,
        );
      });
    });
  });
  