describe('ErrorBoundaryReconciliation', () => {
  let BrokenRender;
  let DidCatchErrorBoundary;
  let GetDerivedErrorBoundary;
  let React;
  let ReactTestRenderer;
  let span;
  let act;
  let assertConsoleErrorDev;

  beforeEach(() => {
    jest.resetModules();

    ReactTestRenderer = require('react-test-renderer');
    React = require('react');
    act = require('internal-test-utils').act;
    assertConsoleErrorDev =
      require('internal-test-utils').assertConsoleErrorDev;
    DidCatchErrorBoundary = class extends React.Component {
      state = {error: null};
      componentDidCatch(error) {
        this.setState({error});
      }
      render() {
        return this.state.error
          ? React.createElement(this.props.fallbackTagName, {
              prop: 'ErrorBoundary',
            })
          : this.props.children;
      }
    };

    GetDerivedErrorBoundary = class extends React.Component {
      state = {error: null};
      static getDerivedStateFromError(error) {
        return {error};
      }
      render() {
        return this.state.error
          ? React.createElement(this.props.fallbackTagName, {
              prop: 'ErrorBoundary',
            })
          : this.props.children;
      }
    };

    const InvalidType = undefined;
    BrokenRender = ({fail}) =>
      fail ? <InvalidType /> : <span prop="BrokenRender" />;
  });

  async function sharedTest(ErrorBoundary, fallbackTagName) {
    let renderer;

    await act(() => {
      renderer = ReactTestRenderer.create(
        <ErrorBoundary fallbackTagName={fallbackTagName}>
          <BrokenRender fail={false} />
        </ErrorBoundary>,
        {unstable_isConcurrent: true},
      );
    });
    expect(renderer).toMatchRenderedOutput(<span prop="BrokenRender" />);
    await act(() => {
      renderer.update(
        <ErrorBoundary fallbackTagName={fallbackTagName}>
          <BrokenRender fail={true} />
        </ErrorBoundary>,
      );
    });
    if (gate(flags => !flags.enableOwnerStacks)) {
      assertConsoleErrorDev(['invalid', 'invalid']);
    }

    const Fallback = fallbackTagName;
    expect(renderer).toMatchRenderedOutput(<Fallback prop="ErrorBoundary" />);
  }

  it('componentDidCatch can recover by rendering an element of the same type', () =>
    sharedTest(DidCatchErrorBoundary, 'span'));

  it('componentDidCatch can recover by rendering an element of a different type', () =>
    sharedTest(DidCatchErrorBoundary, 'div'));

  it('getDerivedStateFromError can recover by rendering an element of the same type', () =>
    sharedTest(GetDerivedErrorBoundary, 'span'));

  it('getDerivedStateFromError can recover by rendering an element of a different type', () =>
    sharedTest(GetDerivedErrorBoundary, 'div'));
});
