code: /**
   * 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 type {FrontendBridge} from 'react-devtools-shared/src/bridge';
  import type Store from 'react-devtools-shared/src/devtools/store';
  import type {
    DispatcherContext,
    StateContext,
  } from 'react-devtools-shared/src/devtools/views/Components/TreeContext';
  
  describe('TreeListContext', () => {
    let React;
    let ReactDOM;
    let TestRenderer: ReactTestRenderer;
    let bridge: FrontendBridge;
    let store: Store;
    let utils;
    let withErrorsOrWarningsIgnored;
  
    let BridgeContext;
    let StoreContext;
    let TreeContext;
  
    let dispatch: DispatcherContext;
    let state: StateContext;
  
    beforeEach(() => {
      utils = require('./utils');
      utils.beforeEachProfiling();
  
      withErrorsOrWarningsIgnored = utils.withErrorsOrWarningsIgnored;
  
      bridge = global.bridge;
      store = global.store;
      store.collapseNodesByDefault = false;
  
      React = require('react');
      ReactDOM = require('react-dom');
      TestRenderer = utils.requireTestRenderer();
  
      BridgeContext = require('react-devtools-shared/src/devtools/views/context')
        .BridgeContext;
      StoreContext = require('react-devtools-shared/src/devtools/views/context')
        .StoreContext;
      TreeContext = require('react-devtools-shared/src/devtools/views/Components/TreeContext');
    });
  
    afterEach(() => {
      // Reset between tests
      dispatch = ((null: any): DispatcherContext);
      state = ((null: any): StateContext);
    });
  
    const Capture = () => {
      dispatch = React.useContext(TreeContext.TreeDispatcherContext);
      state = React.useContext(TreeContext.TreeStateContext);
      return null;
    };
  
    const Contexts = () => {
      return (
        <BridgeContext.Provider value={bridge}>
          <StoreContext.Provider value={store}>
            <TreeContext.TreeContextController>
              <Capture />
            </TreeContext.TreeContextController>
          </StoreContext.Provider>
        </BridgeContext.Provider>
      );
    };
  
    describe('tree state', () => {
      it('should select the next and previous elements in the tree', () => {
        const Grandparent = () => <Parent />;
        const Parent = () => (
          <React.Fragment>
            <Child />
            <Child />
          </React.Fragment>
        );
        const Child = () => null;
  
        utils.act(() =>
          ReactDOM.render(<Grandparent />, document.createElement('div')),
        );
  
        let renderer;
        utils.act(() => (renderer = TestRenderer.create(<Contexts />)));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
                   <Child>
                   <Child>
        `);
  
        // Test stepping through to the end
  
        utils.act(() => dispatch({type: 'SELECT_NEXT_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
          →  ▾ <Grandparent>
               ▾ <Parent>
                   <Child>
                   <Child>
        `);
  
        utils.act(() => dispatch({type: 'SELECT_NEXT_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
          →    ▾ <Parent>
                   <Child>
                   <Child>
        `);
  
        utils.act(() => dispatch({type: 'SELECT_NEXT_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
          →        <Child>
                   <Child>
        `);
  
        utils.act(() => dispatch({type: 'SELECT_NEXT_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
                   <Child>
          →        <Child>
        `);
  
        // Test stepping back to the beginning
  
        utils.act(() => dispatch({type: 'SELECT_PREVIOUS_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
          →        <Child>
                   <Child>
        `);
  
        utils.act(() => dispatch({type: 'SELECT_PREVIOUS_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
          →    ▾ <Parent>
                   <Child>
                   <Child>
        `);
  
        utils.act(() => dispatch({type: 'SELECT_PREVIOUS_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
          →  ▾ <Grandparent>
               ▾ <Parent>
                   <Child>
                   <Child>
        `);
  
        // Test wrap around behavior
  
        utils.act(() => dispatch({type: 'SELECT_PREVIOUS_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
                   <Child>
          →        <Child>
        `);
  
        utils.act(() => dispatch({type: 'SELECT_NEXT_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
          →  ▾ <Grandparent>
               ▾ <Parent>
                   <Child>
                   <Child>
        `);
      });
  
      it('should select child elements', () => {
        const Grandparent = () => (
          <React.Fragment>
            <Parent />
            <Parent />
          </React.Fragment>
        );
        const Parent = () => (
          <React.Fragment>
            <Child />
            <Child />
          </React.Fragment>
        );
        const Child = () => null;
  
        utils.act(() =>
          ReactDOM.render(<Grandparent />, document.createElement('div')),
        );
  
        let renderer;
        utils.act(() => (renderer = TestRenderer.create(<Contexts />)));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
                   <Child>
                   <Child>
               ▾ <Parent>
                   <Child>
                   <Child>
        `);
  
        utils.act(() => dispatch({type: 'SELECT_ELEMENT_AT_INDEX', payload: 0}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
          →  ▾ <Grandparent>
               ▾ <Parent>
                   <Child>
                   <Child>
               ▾ <Parent>
                   <Child>
                   <Child>
        `);
  
        utils.act(() => dispatch({type: 'SELECT_CHILD_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
          →    ▾ <Parent>
                   <Child>
                   <Child>
               ▾ <Parent>
                   <Child>
                   <Child>
        `);
  
        utils.act(() => dispatch({type: 'SELECT_CHILD_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
          →        <Child>
                   <Child>
               ▾ <Parent>
                   <Child>
                   <Child>
        `);
  
        // There are no more children to select, so this should be a no-op
        utils.act(() => dispatch({type: 'SELECT_CHILD_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
          →        <Child>
                   <Child>
               ▾ <Parent>
                   <Child>
                   <Child>
        `);
      });
  
      it('should select parent elements and then collapse', () => {
        const Grandparent = () => (
          <React.Fragment>
            <Parent />
            <Parent />
          </React.Fragment>
        );
        const Parent = () => (
          <React.Fragment>
            <Child />
            <Child />
          </React.Fragment>
        );
        const Child = () => null;
  
        utils.act(() =>
          ReactDOM.render(<Grandparent />, document.createElement('div')),
        );
  
        let renderer;
        utils.act(() => (renderer = TestRenderer.create(<Contexts />)));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
                   <Child>
                   <Child>
               ▾ <Parent>
                   <Child>
                   <Child>
        `);
  
        const lastChildID = store.getElementIDAtIndex(store.numElements - 1);
  
        // Select the last child
        utils.act(() =>
          dispatch({type: 'SELECT_ELEMENT_BY_ID', payload: lastChildID}),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
                   <Child>
                   <Child>
               ▾ <Parent>
                   <Child>
          →        <Child>
        `);
  
        // Select its parent
        utils.act(() => dispatch({type: 'SELECT_PARENT_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
                   <Child>
                   <Child>
          →    ▾ <Parent>
                   <Child>
                   <Child>
        `);
  
        // Select grandparent
        utils.act(() => dispatch({type: 'SELECT_PARENT_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
          →  ▾ <Grandparent>
               ▾ <Parent>
                   <Child>
                   <Child>
               ▾ <Parent>
                   <Child>
                   <Child>
        `);
  
        // No-op
        utils.act(() => dispatch({type: 'SELECT_PARENT_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
          →  ▾ <Grandparent>
               ▾ <Parent>
                   <Child>
                   <Child>
               ▾ <Parent>
                   <Child>
                   <Child>
        `);
  
        const previousState = state;
  
        // There are no more ancestors to select, so this should be a no-op
        utils.act(() => dispatch({type: 'SELECT_PARENT_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toEqual(previousState);
      });
  
      it('should clear selection if the selected element is unmounted', async () => {
        const Grandparent = props => props.children || null;
        const Parent = props => props.children || null;
        const Child = () => null;
  
        const container = document.createElement('div');
        utils.act(() =>
          ReactDOM.render(
            <Grandparent>
              <Parent>
                <Child />
                <Child />
              </Parent>
            </Grandparent>,
            container,
          ),
        );
  
        let renderer;
        utils.act(() => (renderer = TestRenderer.create(<Contexts />)));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
                   <Child>
                   <Child>
        `);
  
        // Select the second child
        utils.act(() => dispatch({type: 'SELECT_ELEMENT_AT_INDEX', payload: 3}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
                   <Child>
          →        <Child>
        `);
  
        // Remove the child (which should auto-select the parent)
        await utils.actAsync(() =>
          ReactDOM.render(
            <Grandparent>
              <Parent />
            </Grandparent>,
            container,
          ),
        );
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
          →      <Parent>
        `);
  
        // Unmount the root (so that nothing is selected)
        await utils.actAsync(() => ReactDOM.unmountComponentAtNode(container));
        expect(state).toMatchInlineSnapshot(``);
      });
  
      it('should navigate next/previous sibling and skip over children in between', () => {
        const Grandparent = () => (
          <React.Fragment>
            <Parent numChildren={1} />
            <Parent numChildren={3} />
            <Parent numChildren={2} />
          </React.Fragment>
        );
        const Parent = ({numChildren}) =>
          new Array(numChildren)
            .fill(true)
            .map((_, index) => <Child key={index} />);
        const Child = () => null;
  
        utils.act(() =>
          ReactDOM.render(<Grandparent />, document.createElement('div')),
        );
  
        let renderer;
        utils.act(() => (renderer = TestRenderer.create(<Contexts />)));
  
        const firstParentID = ((store.getElementIDAtIndex(1): any): number);
  
        utils.act(() =>
          dispatch({type: 'SELECT_ELEMENT_BY_ID', payload: firstParentID}),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
          →    ▾ <Parent>
                   <Child key="0">
               ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
                   <Child key="2">
               ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
        `);
  
        utils.act(() => dispatch({type: 'SELECT_NEXT_SIBLING_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
                   <Child key="0">
          →    ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
                   <Child key="2">
               ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
        `);
  
        utils.act(() => dispatch({type: 'SELECT_NEXT_SIBLING_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
                   <Child key="0">
               ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
                   <Child key="2">
          →    ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
        `);
  
        utils.act(() => dispatch({type: 'SELECT_NEXT_SIBLING_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
          →    ▾ <Parent>
                   <Child key="0">
               ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
                   <Child key="2">
               ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
        `);
  
        utils.act(() => dispatch({type: 'SELECT_PREVIOUS_SIBLING_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
                   <Child key="0">
               ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
                   <Child key="2">
          →    ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
        `);
  
        utils.act(() => dispatch({type: 'SELECT_PREVIOUS_SIBLING_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
                   <Child key="0">
          →    ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
                   <Child key="2">
               ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
        `);
  
        utils.act(() => dispatch({type: 'SELECT_PREVIOUS_SIBLING_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
          →    ▾ <Parent>
                   <Child key="0">
               ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
                   <Child key="2">
               ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
        `);
  
        utils.act(() => dispatch({type: 'SELECT_PREVIOUS_SIBLING_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
                   <Child key="0">
               ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
                   <Child key="2">
          →    ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
        `);
      });
  
      it('should navigate the owner hierarchy', () => {
        const Wrapper = ({children}) => children;
        const Grandparent = () => (
          <React.Fragment>
            <Wrapper>
              <Parent numChildren={1} />
            </Wrapper>
            <Wrapper>
              <Parent numChildren={3} />
            </Wrapper>
            <Wrapper>
              <Parent numChildren={2} />
            </Wrapper>
          </React.Fragment>
        );
        const Parent = ({numChildren}) =>
          new Array(numChildren)
            .fill(true)
            .map((_, index) => <Child key={index} />);
        const Child = () => null;
  
        utils.act(() =>
          ReactDOM.render(<Grandparent />, document.createElement('div')),
        );
  
        let renderer;
        utils.act(() => (renderer = TestRenderer.create(<Contexts />)));
  
        const childID = ((store.getElementIDAtIndex(7): any): number);
        utils.act(() =>
          dispatch({type: 'SELECT_ELEMENT_BY_ID', payload: childID}),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
          →          <Child key="1">
                     <Child key="2">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
        `);
  
        // Basic navigation test
        utils.act(() =>
          dispatch({type: 'SELECT_OWNER_LIST_PREVIOUS_ELEMENT_IN_TREE'}),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
               ▾ <Wrapper>
          →      ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
                     <Child key="2">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
        `);
  
        utils.act(() =>
          dispatch({type: 'SELECT_OWNER_LIST_PREVIOUS_ELEMENT_IN_TREE'}),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
          →  ▾ <Grandparent>
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
                     <Child key="2">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
        `);
  
        // Noop (since we're at the root already)
        utils.act(() =>
          dispatch({type: 'SELECT_OWNER_LIST_PREVIOUS_ELEMENT_IN_TREE'}),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
          →  ▾ <Grandparent>
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
                     <Child key="2">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
        `);
  
        utils.act(() =>
          dispatch({type: 'SELECT_OWNER_LIST_NEXT_ELEMENT_IN_TREE'}),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
               ▾ <Wrapper>
          →      ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
                     <Child key="2">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
        `);
  
        utils.act(() =>
          dispatch({type: 'SELECT_OWNER_LIST_NEXT_ELEMENT_IN_TREE'}),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
          →          <Child key="1">
                     <Child key="2">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
        `);
  
        // Noop (since we're at the leaf node)
        utils.act(() =>
          dispatch({type: 'SELECT_OWNER_LIST_NEXT_ELEMENT_IN_TREE'}),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
          →          <Child key="1">
                     <Child key="2">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
        `);
  
        // Other navigational actions should clear out the temporary owner chain.
        utils.act(() => dispatch({type: 'SELECT_PREVIOUS_ELEMENT_IN_TREE'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
               ▾ <Wrapper>
                 ▾ <Parent>
          →          <Child key="0">
                     <Child key="1">
                     <Child key="2">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
        `);
  
        // Start a new tree on parent
        const parentID = ((store.getElementIDAtIndex(5): any): number);
        utils.act(() =>
          dispatch({type: 'SELECT_ELEMENT_BY_ID', payload: parentID}),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
               ▾ <Wrapper>
          →      ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
                     <Child key="2">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
        `);
  
        utils.act(() =>
          dispatch({type: 'SELECT_OWNER_LIST_PREVIOUS_ELEMENT_IN_TREE'}),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
          →  ▾ <Grandparent>
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
                     <Child key="2">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
        `);
  
        // Noop (since we're at the top)
        utils.act(() =>
          dispatch({type: 'SELECT_OWNER_LIST_PREVIOUS_ELEMENT_IN_TREE'}),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
          →  ▾ <Grandparent>
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
                     <Child key="2">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
        `);
  
        utils.act(() =>
          dispatch({type: 'SELECT_OWNER_LIST_NEXT_ELEMENT_IN_TREE'}),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
               ▾ <Wrapper>
          →      ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
                     <Child key="2">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
        `);
  
        // Noop (since we're at the leaf of this owner tree)
        // It should not be possible to navigate beyond the owner chain leaf.
        utils.act(() =>
          dispatch({type: 'SELECT_OWNER_LIST_NEXT_ELEMENT_IN_TREE'}),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
               ▾ <Wrapper>
          →      ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
                     <Child key="2">
               ▾ <Wrapper>
                 ▾ <Parent>
                     <Child key="0">
                     <Child key="1">
        `);
      });
    });
  
    describe('search state', () => {
      it('should find elements matching search text', () => {
        const Foo = () => null;
        const Bar = () => null;
        const Baz = () => null;
        const Qux = () => null;
  
        Qux.displayName = `withHOC(${Qux.name})`;
  
        utils.act(() =>
          ReactDOM.render(
            <React.Fragment>
              <Foo />
              <Bar />
              <Baz />
              <Qux />
            </React.Fragment>,
            document.createElement('div'),
          ),
        );
  
        let renderer;
        utils.act(() => (renderer = TestRenderer.create(<Contexts />)));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
               <Bar>
               <Baz>
               <Qux> [withHOC]
        `);
  
        // NOTE: multi-match
        utils.act(() => dispatch({type: 'SET_SEARCH_TEXT', payload: 'ba'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
          →    <Bar>
               <Baz>
               <Qux> [withHOC]
        `);
  
        // NOTE: single match
        utils.act(() => dispatch({type: 'SET_SEARCH_TEXT', payload: 'f'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
          →    <Foo>
               <Bar>
               <Baz>
               <Qux> [withHOC]
        `);
  
        // NOTE: no match
        utils.act(() => dispatch({type: 'SET_SEARCH_TEXT', payload: 'y'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
          →    <Foo>
               <Bar>
               <Baz>
               <Qux> [withHOC]
        `);
  
        // NOTE: HOC match
        utils.act(() => dispatch({type: 'SET_SEARCH_TEXT', payload: 'w'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
               <Bar>
               <Baz>
          →    <Qux> [withHOC]
        `);
      });
  
      it('should select the next and previous items within the search results', () => {
        const Foo = () => null;
        const Bar = () => null;
        const Baz = () => null;
  
        utils.act(() =>
          ReactDOM.render(
            <React.Fragment>
              <Foo />
              <Baz />
              <Bar />
              <Baz />
            </React.Fragment>,
            document.createElement('div'),
          ),
        );
  
        let renderer;
        utils.act(() => (renderer = TestRenderer.create(<Contexts />)));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
               <Baz>
               <Bar>
               <Baz>
        `);
  
        // search for "ba"
        utils.act(() => dispatch({type: 'SET_SEARCH_TEXT', payload: 'ba'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
          →    <Baz>
               <Bar>
               <Baz>
        `);
  
        // go to second result
        utils.act(() => dispatch({type: 'GO_TO_NEXT_SEARCH_RESULT'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
               <Baz>
          →    <Bar>
               <Baz>
        `);
  
        // go to third result
        utils.act(() => dispatch({type: 'GO_TO_NEXT_SEARCH_RESULT'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
               <Baz>
               <Bar>
          →    <Baz>
        `);
  
        // go to second result
        utils.act(() => dispatch({type: 'GO_TO_PREVIOUS_SEARCH_RESULT'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
               <Baz>
          →    <Bar>
               <Baz>
        `);
  
        // go to first result
        utils.act(() => dispatch({type: 'GO_TO_PREVIOUS_SEARCH_RESULT'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
          →    <Baz>
               <Bar>
               <Baz>
        `);
  
        // wrap to last result
        utils.act(() => dispatch({type: 'GO_TO_PREVIOUS_SEARCH_RESULT'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
               <Baz>
               <Bar>
          →    <Baz>
        `);
  
        // wrap to first result
        utils.act(() => dispatch({type: 'GO_TO_NEXT_SEARCH_RESULT'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
          →    <Baz>
               <Bar>
               <Baz>
        `);
      });
  
      it('should add newly mounted elements to the search results set if they match the current text', async () => {
        const Foo = () => null;
        const Bar = () => null;
        const Baz = () => null;
  
        const container = document.createElement('div');
  
        utils.act(() =>
          ReactDOM.render(
            <React.Fragment>
              <Foo />
              <Bar />
            </React.Fragment>,
            container,
          ),
        );
  
        let renderer;
        utils.act(() => (renderer = TestRenderer.create(<Contexts />)));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
               <Bar>
        `);
  
        utils.act(() => dispatch({type: 'SET_SEARCH_TEXT', payload: 'ba'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
          →    <Bar>
        `);
  
        await utils.actAsync(() =>
          ReactDOM.render(
            <React.Fragment>
              <Foo />
              <Bar />
              <Baz />
            </React.Fragment>,
            container,
          ),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
          →    <Bar>
               <Baz>
        `);
  
        utils.act(() => dispatch({type: 'GO_TO_NEXT_SEARCH_RESULT'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
               <Bar>
          →    <Baz>
        `);
      });
  
      it('should remove unmounted elements from the search results set', async () => {
        const Foo = () => null;
        const Bar = () => null;
        const Baz = () => null;
  
        const container = document.createElement('div');
  
        utils.act(() =>
          ReactDOM.render(
            <React.Fragment>
              <Foo />
              <Bar />
              <Baz />
            </React.Fragment>,
            container,
          ),
        );
  
        let renderer;
        utils.act(() => (renderer = TestRenderer.create(<Contexts />)));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
               <Bar>
               <Baz>
        `);
  
        utils.act(() => dispatch({type: 'SET_SEARCH_TEXT', payload: 'ba'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
          →    <Bar>
               <Baz>
        `);
  
        utils.act(() => dispatch({type: 'GO_TO_NEXT_SEARCH_RESULT'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
               <Bar>
          →    <Baz>
        `);
  
        await utils.actAsync(() =>
          ReactDOM.render(
            <React.Fragment>
              <Foo />
              <Bar />
            </React.Fragment>,
            container,
          ),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
               <Bar>
        `);
  
        utils.act(() => dispatch({type: 'GO_TO_NEXT_SEARCH_RESULT'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
          →    <Bar>
        `);
  
        // Noop since the list is now one item long
        utils.act(() => dispatch({type: 'GO_TO_NEXT_SEARCH_RESULT'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Foo>
          →    <Bar>
        `);
      });
    });
  
    describe('owners state', () => {
      it('should support entering and existing the owners tree view', () => {
        const Grandparent = () => <Parent />;
        const Parent = () => (
          <React.Fragment>
            <Child />
            <Child />
          </React.Fragment>
        );
        const Child = () => null;
  
        utils.act(() =>
          ReactDOM.render(<Grandparent />, document.createElement('div')),
        );
  
        let renderer;
        utils.act(() => (renderer = TestRenderer.create(<Contexts />)));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
                   <Child>
                   <Child>
        `);
  
        const parentID = ((store.getElementIDAtIndex(1): any): number);
        utils.act(() => dispatch({type: 'SELECT_OWNER', payload: parentID}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [owners]
          →  ▾ <Parent>
                 <Child>
                 <Child>
        `);
  
        utils.act(() => dispatch({type: 'RESET_OWNER_STACK'}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
          →    ▾ <Parent>
                   <Child>
                   <Child>
        `);
      });
  
      it('should remove an element from the owners list if it is unmounted', async () => {
        const Grandparent = ({count}) => <Parent count={count} />;
        const Parent = ({count}) =>
          new Array(count).fill(true).map((_, index) => <Child key={index} />);
        const Child = () => null;
  
        const container = document.createElement('div');
        utils.act(() => ReactDOM.render(<Grandparent count={2} />, container));
  
        let renderer;
        utils.act(() => (renderer = TestRenderer.create(<Contexts />)));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Grandparent>
               ▾ <Parent>
                   <Child key="0">
                   <Child key="1">
        `);
  
        const parentID = ((store.getElementIDAtIndex(1): any): number);
        utils.act(() => dispatch({type: 'SELECT_OWNER', payload: parentID}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [owners]
          →  ▾ <Parent>
                 <Child key="0">
                 <Child key="1">
        `);
  
        await utils.actAsync(() =>
          ReactDOM.render(<Grandparent count={1} />, container),
        );
        expect(state).toMatchInlineSnapshot(`
          [owners]
          →  ▾ <Parent>
                 <Child key="0">
        `);
  
        await utils.actAsync(() =>
          ReactDOM.render(<Grandparent count={0} />, container),
        );
        expect(state).toMatchInlineSnapshot(`
          [owners]
          →    <Parent>
        `);
      });
  
      it('should exit the owners list if the current owner is unmounted', async () => {
        const Parent = props => props.children || null;
        const Child = () => null;
  
        const container = document.createElement('div');
        utils.act(() =>
          ReactDOM.render(
            <Parent>
              <Child />
            </Parent>,
            container,
          ),
        );
  
        let renderer;
        utils.act(() => (renderer = TestRenderer.create(<Contexts />)));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Parent>
                 <Child>
        `);
  
        const childID = ((store.getElementIDAtIndex(1): any): number);
        utils.act(() => dispatch({type: 'SELECT_OWNER', payload: childID}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [owners]
          →    <Child>
        `);
  
        await utils.actAsync(() => ReactDOM.render(<Parent />, container));
        expect(state).toMatchInlineSnapshot(`
          [root]
          →    <Parent>
        `);
  
        const parentID = ((store.getElementIDAtIndex(0): any): number);
        utils.act(() => dispatch({type: 'SELECT_OWNER', payload: parentID}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [owners]
          →    <Parent>
        `);
  
        await utils.actAsync(() => ReactDOM.unmountComponentAtNode(container));
        expect(state).toMatchInlineSnapshot(``);
      });
  
      // This tests ensures support for toggling Suspense boundaries outside of the active owners list.
      it('should exit the owners list if an element outside the list is selected', () => {
        const Grandchild = () => null;
        const Child = () => (
          <React.Suspense fallback="Loading">
            <Grandchild />
          </React.Suspense>
        );
        const Parent = () => (
          <React.Suspense fallback="Loading">
            <Child />
          </React.Suspense>
        );
  
        const container = document.createElement('div');
        utils.act(() => ReactDOM.render(<Parent />, container));
  
        let renderer;
        utils.act(() => (renderer = TestRenderer.create(<Contexts />)));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Parent>
               ▾ <Suspense>
                 ▾ <Child>
                   ▾ <Suspense>
                       <Grandchild>
        `);
  
        const outerSuspenseID = ((store.getElementIDAtIndex(1): any): number);
        const childID = ((store.getElementIDAtIndex(2): any): number);
        const innerSuspenseID = ((store.getElementIDAtIndex(3): any): number);
  
        utils.act(() => dispatch({type: 'SELECT_OWNER', payload: childID}));
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [owners]
          →  ▾ <Child>
               ▾ <Suspense>
                   <Grandchild>
        `);
  
        // Toggling a Suspense boundary inside of the flat list should update selected index
        utils.act(() =>
          dispatch({type: 'SELECT_ELEMENT_BY_ID', payload: innerSuspenseID}),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [owners]
             ▾ <Child>
          →    ▾ <Suspense>
                   <Grandchild>
        `);
  
        // Toggling a Suspense boundary outside of the flat list should exit owners list and update index
        utils.act(() =>
          dispatch({type: 'SELECT_ELEMENT_BY_ID', payload: outerSuspenseID}),
        );
        utils.act(() => renderer.update(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
             ▾ <Parent>
          →    ▾ <Suspense>
                 ▾ <Child>
                   ▾ <Suspense>
                       <Grandchild>
        `);
      });
    });
  
    describe('inline errors/warnings state', () => {
      function clearAllErrors() {
        utils.act(() => store.clearErrorsAndWarnings());
        // flush events to the renderer
        jest.runAllTimers();
      }
  
      function clearErrorsForElement(id) {
        utils.act(() => store.clearErrorsForElement(id));
        // flush events to the renderer
        jest.runAllTimers();
      }
  
      function clearWarningsForElement(id) {
        utils.act(() => store.clearWarningsForElement(id));
        // flush events to the renderer
        jest.runAllTimers();
      }
  
      function selectNextErrorOrWarning() {
        utils.act(() =>
          dispatch({type: 'SELECT_NEXT_ELEMENT_WITH_ERROR_OR_WARNING_IN_TREE'}),
        );
      }
  
      function selectPreviousErrorOrWarning() {
        utils.act(() =>
          dispatch({
            type: 'SELECT_PREVIOUS_ELEMENT_WITH_ERROR_OR_WARNING_IN_TREE',
          }),
        );
      }
  
      function Child({logError = false, logWarning = false}) {
        if (logError === true) {
          console.error('test-only: error');
        }
        if (logWarning === true) {
          console.warn('test-only: warning');
        }
        return null;
      }
  
      it('should handle when there are no errors/warnings', () => {
        utils.act(() =>
          ReactDOM.render(
            <React.Fragment>
              <Child />
              <Child />
              <Child />
            </React.Fragment>,
            document.createElement('div'),
          ),
        );
  
        utils.act(() => TestRenderer.create(<Contexts />));
  
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Child>
               <Child>
               <Child>
        `);
  
        // Next/previous errors should be a no-op
        selectPreviousErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Child>
               <Child>
               <Child>
        `);
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Child>
               <Child>
               <Child>
        `);
  
        utils.act(() => dispatch({type: 'SELECT_ELEMENT_AT_INDEX', payload: 0}));
        expect(state).toMatchInlineSnapshot(`
          [root]
          →    <Child>
               <Child>
               <Child>
        `);
  
        // Next/previous errors should still be a no-op
        selectPreviousErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          [root]
          →    <Child>
               <Child>
               <Child>
        `);
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          [root]
          →    <Child>
               <Child>
               <Child>
        `);
      });
  
      it('should cycle through the next errors/warnings and wrap around', () => {
        withErrorsOrWarningsIgnored(['test-only:'], () =>
          utils.act(() =>
            ReactDOM.render(
              <React.Fragment>
                <Child />
                <Child logWarning={true} />
                <Child />
                <Child logError={true} />
                <Child />
              </React.Fragment>,
              document.createElement('div'),
            ),
          ),
        );
  
        utils.act(() => TestRenderer.create(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
               <Child> ⚠
               <Child>
               <Child> ✕
               <Child>
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
          →    <Child> ⚠
               <Child>
               <Child> ✕
               <Child>
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
               <Child> ⚠
               <Child>
          →    <Child> ✕
               <Child>
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
          →    <Child> ⚠
               <Child>
               <Child> ✕
               <Child>
        `);
      });
  
      it('should cycle through the previous errors/warnings and wrap around', () => {
        withErrorsOrWarningsIgnored(['test-only:'], () =>
          utils.act(() =>
            ReactDOM.render(
              <React.Fragment>
                <Child />
                <Child logWarning={true} />
                <Child />
                <Child logError={true} />
                <Child />
              </React.Fragment>,
              document.createElement('div'),
            ),
          ),
        );
  
        utils.act(() => TestRenderer.create(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
               <Child> ⚠
               <Child>
               <Child> ✕
               <Child>
        `);
  
        selectPreviousErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
               <Child> ⚠
               <Child>
          →    <Child> ✕
               <Child>
        `);
  
        selectPreviousErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
          →    <Child> ⚠
               <Child>
               <Child> ✕
               <Child>
        `);
  
        selectPreviousErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
               <Child> ⚠
               <Child>
          →    <Child> ✕
               <Child>
        `);
      });
  
      it('should cycle through the next errors/warnings and wrap around with multiple roots', () => {
        withErrorsOrWarningsIgnored(['test-only:'], () => {
          utils.act(() => {
            ReactDOM.render(
              <React.Fragment>
                <Child />
                <Child logWarning={true} />,
              </React.Fragment>,
              document.createElement('div'),
            );
            ReactDOM.render(
              <React.Fragment>
                <Child />
                <Child logError={true} />
                <Child />
              </React.Fragment>,
              document.createElement('div'),
            );
          });
        });
  
        utils.act(() => TestRenderer.create(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
               <Child> ⚠
          [root]
               <Child>
               <Child> ✕
               <Child>
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
          →    <Child> ⚠
          [root]
               <Child>
               <Child> ✕
               <Child>
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
               <Child> ⚠
          [root]
               <Child>
          →    <Child> ✕
               <Child>
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
          →    <Child> ⚠
          [root]
               <Child>
               <Child> ✕
               <Child>
        `);
      });
  
      it('should cycle through the previous errors/warnings and wrap around with multiple roots', () => {
        withErrorsOrWarningsIgnored(['test-only:'], () => {
          utils.act(() => {
            ReactDOM.render(
              <React.Fragment>
                <Child />
                <Child logWarning={true} />,
              </React.Fragment>,
              document.createElement('div'),
            );
            ReactDOM.render(
              <React.Fragment>
                <Child />
                <Child logError={true} />
                <Child />
              </React.Fragment>,
              document.createElement('div'),
            );
          });
        });
  
        utils.act(() => TestRenderer.create(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
               <Child> ⚠
          [root]
               <Child>
               <Child> ✕
               <Child>
        `);
  
        selectPreviousErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
               <Child> ⚠
          [root]
               <Child>
          →    <Child> ✕
               <Child>
        `);
  
        selectPreviousErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
          →    <Child> ⚠
          [root]
               <Child>
               <Child> ✕
               <Child>
        `);
  
        selectPreviousErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
               <Child> ⚠
          [root]
               <Child>
          →    <Child> ✕
               <Child>
        `);
      });
  
      it('should select the next or previous element relative to the current selection', () => {
        withErrorsOrWarningsIgnored(['test-only:'], () =>
          utils.act(() =>
            ReactDOM.render(
              <React.Fragment>
                <Child />
                <Child logWarning={true} />
                <Child />
                <Child logError={true} />
                <Child />
              </React.Fragment>,
              document.createElement('div'),
            ),
          ),
        );
  
        utils.act(() => TestRenderer.create(<Contexts />));
        utils.act(() => dispatch({type: 'SELECT_ELEMENT_AT_INDEX', payload: 2}));
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
               <Child> ⚠
          →    <Child>
               <Child> ✕
               <Child>
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
               <Child> ⚠
               <Child>
          →    <Child> ✕
               <Child>
        `);
  
        utils.act(() => dispatch({type: 'SELECT_ELEMENT_AT_INDEX', payload: 2}));
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
               <Child> ⚠
          →    <Child>
               <Child> ✕
               <Child>
        `);
  
        selectPreviousErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child>
          →    <Child> ⚠
               <Child>
               <Child> ✕
               <Child>
        `);
      });
  
      it('should update correctly when errors/warnings are cleared for a fiber in the list', () => {
        withErrorsOrWarningsIgnored(['test-only:'], () =>
          utils.act(() =>
            ReactDOM.render(
              <React.Fragment>
                <Child logWarning={true} />
                <Child logError={true} />
                <Child logError={true} />
                <Child logWarning={true} />
              </React.Fragment>,
              document.createElement('div'),
            ),
          ),
        );
  
        utils.act(() => TestRenderer.create(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          ✕ 2, ⚠ 2
          [root]
               <Child> ⚠
               <Child> ✕
               <Child> ✕
               <Child> ⚠
        `);
  
        // Select the first item in the list
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 2, ⚠ 2
          [root]
          →    <Child> ⚠
               <Child> ✕
               <Child> ✕
               <Child> ⚠
        `);
  
        // Clear warnings (but the next Fiber has only errors)
        clearWarningsForElement(store.getElementIDAtIndex(1));
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 2, ⚠ 2
          [root]
               <Child> ⚠
          →    <Child> ✕
               <Child> ✕
               <Child> ⚠
        `);
  
        clearErrorsForElement(store.getElementIDAtIndex(2));
  
        // Should step to the (now) next one in the list.
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 2
          [root]
               <Child> ⚠
               <Child> ✕
               <Child>
          →    <Child> ⚠
        `);
  
        // Should skip over the (now) cleared Fiber
        selectPreviousErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 2
          [root]
               <Child> ⚠
          →    <Child> ✕
               <Child>
               <Child> ⚠
        `);
      });
  
      it('should update correctly when errors/warnings are cleared for the currently selected fiber', () => {
        withErrorsOrWarningsIgnored(['test-only:'], () =>
          utils.act(() =>
            ReactDOM.render(
              <React.Fragment>
                <Child logWarning={true} />
                <Child logError={true} />
              </React.Fragment>,
              document.createElement('div'),
            ),
          ),
        );
  
        utils.act(() => TestRenderer.create(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child> ⚠
               <Child> ✕
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
          →    <Child> ⚠
               <Child> ✕
        `);
  
        clearWarningsForElement(store.getElementIDAtIndex(0));
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 0
          [root]
               <Child>
          →    <Child> ✕
        `);
      });
  
      it('should update correctly when new errors/warnings are added', () => {
        const container = document.createElement('div');
  
        withErrorsOrWarningsIgnored(['test-only:'], () =>
          utils.act(() =>
            ReactDOM.render(
              <React.Fragment>
                <Child logWarning={true} />
                <Child />
                <Child />
                <Child logError={true} />
              </React.Fragment>,
              container,
            ),
          ),
        );
  
        utils.act(() => TestRenderer.create(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child> ⚠
               <Child>
               <Child>
               <Child> ✕
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
          →    <Child> ⚠
               <Child>
               <Child>
               <Child> ✕
        `);
  
        withErrorsOrWarningsIgnored(['test-only:'], () =>
          utils.act(() =>
            ReactDOM.render(
              <React.Fragment>
                <Child />
                <Child logWarning={true} />
                <Child />
                <Child />
              </React.Fragment>,
              container,
            ),
          ),
        );
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 2
          [root]
               <Child> ⚠
          →    <Child> ⚠
               <Child>
               <Child> ✕
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 2
          [root]
               <Child> ⚠
               <Child> ⚠
               <Child>
          →    <Child> ✕
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 2
          [root]
          →    <Child> ⚠
               <Child> ⚠
               <Child>
               <Child> ✕
        `);
      });
  
      it('should update correctly when all errors/warnings are cleared', () => {
        withErrorsOrWarningsIgnored(['test-only:'], () =>
          utils.act(() =>
            ReactDOM.render(
              <React.Fragment>
                <Child logWarning={true} />
                <Child logError={true} />
              </React.Fragment>,
              document.createElement('div'),
            ),
          ),
        );
  
        utils.act(() => TestRenderer.create(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
               <Child> ⚠
               <Child> ✕
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 1, ⚠ 1
          [root]
          →    <Child> ⚠
               <Child> ✕
        `);
  
        clearAllErrors();
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          [root]
          →    <Child>
               <Child>
        `);
  
        selectPreviousErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          [root]
          →    <Child>
               <Child>
        `);
      });
  
      it('should update select and auto-expand parts components within hidden parts of the tree', () => {
        const Wrapper = ({children}) => children;
  
        store.collapseNodesByDefault = true;
  
        withErrorsOrWarningsIgnored(['test-only:'], () =>
          utils.act(() =>
            ReactDOM.render(
              <React.Fragment>
                <Wrapper>
                  <Child logWarning={true} />
                </Wrapper>
                <Wrapper>
                  <Wrapper>
                    <Child logWarning={true} />
                  </Wrapper>
                </Wrapper>
              </React.Fragment>,
              document.createElement('div'),
            ),
          ),
        );
  
        utils.act(() => TestRenderer.create(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          ✕ 0, ⚠ 2
          [root]
             ▸ <Wrapper>
             ▸ <Wrapper>
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 0, ⚠ 2
          [root]
             ▾ <Wrapper>
          →      <Child> ⚠
             ▸ <Wrapper>
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 0, ⚠ 2
          [root]
             ▾ <Wrapper>
                 <Child> ⚠
             ▾ <Wrapper>
               ▾ <Wrapper>
          →        <Child> ⚠
        `);
      });
  
      it('should properly handle when components filters are updated', () => {
        const Wrapper = ({children}) => children;
  
        withErrorsOrWarningsIgnored(['test-only:'], () =>
          utils.act(() =>
            ReactDOM.render(
              <React.Fragment>
                <Wrapper>
                  <Child logWarning={true} />
                </Wrapper>
                <Wrapper>
                  <Wrapper>
                    <Child logWarning={true} />
                  </Wrapper>
                </Wrapper>
              </React.Fragment>,
              document.createElement('div'),
            ),
          ),
        );
  
        utils.act(() => TestRenderer.create(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          ✕ 0, ⚠ 2
          [root]
             ▾ <Wrapper>
                 <Child> ⚠
             ▾ <Wrapper>
               ▾ <Wrapper>
                   <Child> ⚠
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 0, ⚠ 2
          [root]
             ▾ <Wrapper>
          →      <Child> ⚠
             ▾ <Wrapper>
               ▾ <Wrapper>
                   <Child> ⚠
        `);
  
        utils.act(() => {
          store.componentFilters = [utils.createDisplayNameFilter('Wrapper')];
        });
        expect(state).toMatchInlineSnapshot(`
          ✕ 0, ⚠ 2
          [root]
          →    <Child> ⚠
               <Child> ⚠
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 0, ⚠ 2
          [root]
               <Child> ⚠
          →    <Child> ⚠
        `);
  
        utils.act(() => {
          store.componentFilters = [];
        });
        expect(state).toMatchInlineSnapshot(`
          ✕ 0, ⚠ 2
          [root]
             ▾ <Wrapper>
                 <Child> ⚠
             ▾ <Wrapper>
               ▾ <Wrapper>
          →        <Child> ⚠
        `);
  
        selectPreviousErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 0, ⚠ 2
          [root]
             ▾ <Wrapper>
          →      <Child> ⚠
             ▾ <Wrapper>
               ▾ <Wrapper>
                   <Child> ⚠
        `);
      });
  
      it('should preserve errors for fibers even if they are filtered out of the tree initially', () => {
        const Wrapper = ({children}) => children;
  
        withErrorsOrWarningsIgnored(['test-only:'], () =>
          utils.act(() =>
            ReactDOM.render(
              <React.Fragment>
                <Wrapper>
                  <Child logWarning={true} />
                </Wrapper>
                <Wrapper>
                  <Wrapper>
                    <Child logWarning={true} />
                  </Wrapper>
                </Wrapper>
              </React.Fragment>,
              document.createElement('div'),
            ),
          ),
        );
  
        store.componentFilters = [utils.createDisplayNameFilter('Child')];
  
        utils.act(() => TestRenderer.create(<Contexts />));
        expect(state).toMatchInlineSnapshot(`
          [root]
               <Wrapper>
             ▾ <Wrapper>
                 <Wrapper>
        `);
  
        utils.act(() => {
          store.componentFilters = [];
        });
        expect(state).toMatchInlineSnapshot(`
          ✕ 0, ⚠ 2
          [root]
             ▾ <Wrapper>
                 <Child> ⚠
             ▾ <Wrapper>
               ▾ <Wrapper>
                   <Child> ⚠
        `);
  
        selectNextErrorOrWarning();
        expect(state).toMatchInlineSnapshot(`
          ✕ 0, ⚠ 2
          [root]
             ▾ <Wrapper>
          →      <Child> ⚠
             ▾ <Wrapper>
               ▾ <Wrapper>
                   <Child> ⚠
        `);
      });
  
      describe('suspense', () => {
        // This verifies that we don't flush before the tree has been committed.
        it('should properly handle errors/warnings from components inside of delayed Suspense', async () => {
          const NeverResolves = React.lazy(() => new Promise(() => {}));
  
          withErrorsOrWarningsIgnored(['test-only:'], () =>
            utils.act(() =>
              ReactDOM.render(
                <React.Suspense fallback={null}>
                  <Child logWarning={true} />
                  <NeverResolves />
                </React.Suspense>,
                document.createElement('div'),
              ),
            ),
          );
          utils.act(() => TestRenderer.create(<Contexts />));
  
          jest.runAllTimers();
  
          expect(state).toMatchInlineSnapshot(`
                    [root]
                         <Suspense>
                `);
  
          selectNextErrorOrWarning();
  
          expect(state).toMatchInlineSnapshot(`
                    [root]
                         <Suspense>
                `);
        });
  
        it('should properly handle errors/warnings from components that dont mount because of Suspense', async () => {
          async function fakeImport(result) {
            return {default: result};
          }
          const LazyComponent = React.lazy(() => fakeImport(Child));
  
          const container = document.createElement('div');
  
          withErrorsOrWarningsIgnored(['test-only:'], () =>
            utils.act(() =>
              ReactDOM.render(
                <React.Suspense fallback={null}>
                  <Child logWarning={true} />
                  <LazyComponent />
                </React.Suspense>,
                container,
              ),
            ),
          );
          utils.act(() => TestRenderer.create(<Contexts />));
  
          expect(state).toMatchInlineSnapshot(`
                    [root]
                         <Suspense>
                `);
  
          await Promise.resolve();
          withErrorsOrWarningsIgnored(['test-only:'], () =>
            utils.act(() =>
              ReactDOM.render(
                <React.Suspense fallback={null}>
                  <Child logWarning={true} />
                  <LazyComponent />
                </React.Suspense>,
                container,
              ),
            ),
          );
  
          expect(state).toMatchInlineSnapshot(`
            ✕ 0, ⚠ 1
            [root]
               ▾ <Suspense>
                   <Child> ⚠
                   <Child>
          `);
        });
  
        it('should properly show errors/warnings from components in the Suspense fallback tree', async () => {
          async function fakeImport(result) {
            return {default: result};
          }
          const LazyComponent = React.lazy(() => fakeImport(Child));
  
          const Fallback = () => <Child logError={true} />;
  
          const container = document.createElement('div');
  
          withErrorsOrWarningsIgnored(['test-only:'], () =>
            utils.act(() =>
              ReactDOM.render(
                <React.Suspense fallback={<Fallback />}>
                  <LazyComponent />
                </React.Suspense>,
                container,
              ),
            ),
          );
          utils.act(() => TestRenderer.create(<Contexts />));
  
          expect(state).toMatchInlineSnapshot(`
            ✕ 1, ⚠ 0
            [root]
               ▾ <Suspense>
                 ▾ <Fallback>
                     <Child> ✕
          `);
  
          await Promise.resolve();
          withErrorsOrWarningsIgnored(['test-only:'], () =>
            utils.act(() =>
              ReactDOM.render(
                <React.Suspense fallback={<Fallback />}>
                  <LazyComponent />
                </React.Suspense>,
                container,
              ),
            ),
          );
  
          expect(state).toMatchInlineSnapshot(`
                    [root]
                       ▾ <Suspense>
                           <Child>
                `);
        });
      });
  
      describe('error boundaries', () => {
        it('should properly handle errors/warnings from components that dont mount because of an error', () => {
          class ErrorBoundary extends React.Component {
            state = {error: null};
            static getDerivedStateFromError(error) {
              return {error};
            }
            render() {
              if (this.state.error) {
                return null;
              }
              return this.props.children;
            }
          }
  
          class BadRender extends React.Component {
            render() {
              console.error('test-only: I am about to throw!');
              throw new Error('test-only: Oops!');
            }
          }
  
          const container = document.createElement('div');
          withErrorsOrWarningsIgnored(
            ['test-only:', 'React will try to recreate this component tree'],
            () => {
              utils.act(() =>
                ReactDOM.render(
                  <ErrorBoundary>
                    <BadRender />
                  </ErrorBoundary>,
                  container,
                ),
              );
            },
          );
  
          utils.act(() => TestRenderer.create(<Contexts />));
  
          expect(store).toMatchInlineSnapshot(`
            ✕ 1, ⚠ 0
            [root]
                <ErrorBoundary> ✕
          `);
  
          selectNextErrorOrWarning();
          expect(state).toMatchInlineSnapshot(`
            ✕ 1, ⚠ 0
            [root]
            →    <ErrorBoundary> ✕
          `);
  
          utils.act(() => ReactDOM.unmountComponentAtNode(container));
          expect(state).toMatchInlineSnapshot(``);
  
          // Should be a noop
          selectNextErrorOrWarning();
          expect(state).toMatchInlineSnapshot(``);
        });
  
        it('should properly handle errors/warnings from components that dont mount because of an error', () => {
          class ErrorBoundary extends React.Component {
            state = {error: null};
            static getDerivedStateFromError(error) {
              return {error};
            }
            render() {
              if (this.state.error) {
                return null;
              }
              return this.props.children;
            }
          }
  
          class LogsWarning extends React.Component {
            render() {
              console.warn('test-only: I am about to throw!');
              return <ThrowsError />;
            }
          }
          class ThrowsError extends React.Component {
            render() {
              throw new Error('test-only: Oops!');
            }
          }
  
          const container = document.createElement('div');
          withErrorsOrWarningsIgnored(
            ['test-only:', 'React will try to recreate this component tree'],
            () => {
              utils.act(() =>
                ReactDOM.render(
                  <ErrorBoundary>
                    <LogsWarning />
                  </ErrorBoundary>,
                  container,
                ),
              );
            },
          );
  
          utils.act(() => TestRenderer.create(<Contexts />));
  
          expect(store).toMatchInlineSnapshot(`
            ✕ 1, ⚠ 0
            [root]
                <ErrorBoundary> ✕
          `);
  
          selectNextErrorOrWarning();
          expect(state).toMatchInlineSnapshot(`
            ✕ 1, ⚠ 0
            [root]
            →    <ErrorBoundary> ✕
          `);
  
          utils.act(() => ReactDOM.unmountComponentAtNode(container));
          expect(state).toMatchInlineSnapshot(``);
  
          // Should be a noop
          selectNextErrorOrWarning();
          expect(state).toMatchInlineSnapshot(``);
        });
  
        it('should properly handle errors/warnings from inside of an error boundary', () => {
          class ErrorBoundary extends React.Component {
            state = {error: null};
            static getDerivedStateFromError(error) {
              return {error};
            }
            render() {
              if (this.state.error) {
                return <Child logError={true} />;
              }
              return this.props.children;
            }
          }
  
          class BadRender extends React.Component {
            render() {
              console.error('test-only: I am about to throw!');
              throw new Error('test-only: Oops!');
            }
          }
  
          const container = document.createElement('div');
          withErrorsOrWarningsIgnored(
            ['test-only:', 'React will try to recreate this component tree'],
            () => {
              utils.act(() =>
                ReactDOM.render(
                  <ErrorBoundary>
                    <BadRender />
                  </ErrorBoundary>,
                  container,
                ),
              );
            },
          );
  
          utils.act(() => TestRenderer.create(<Contexts />));
  
          expect(store).toMatchInlineSnapshot(`
            ✕ 2, ⚠ 0
            [root]
              ▾ <ErrorBoundary> ✕
                  <Child> ✕
          `);
  
          selectNextErrorOrWarning();
          expect(state).toMatchInlineSnapshot(`
            ✕ 2, ⚠ 0
            [root]
            →  ▾ <ErrorBoundary> ✕
                   <Child> ✕
          `);
        });
      });
    });
  });
  