/**
 * Copyright (c) Meta Platforms, Inc. and 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 {getVersionedRenderImplementation} from './utils';

jest.mock('react-devtools-feature-flags', () => ({
  ...jest.requireActual('react-devtools-feature-flags'),
  enableLogger: true,
}));

describe('error reporting', () => {
  let React;
  let act;

  beforeEach(() => {
    React = require('react');
    act = require('./utils').act;
  });

  const {getContainer, render, unmount} = getVersionedRenderImplementation();

  it('reports a Store error before the frontend mounts', () => {
    const {registerEventLogger} = require('react-devtools-shared/src/Logger');
    const {
      subscribeToStoreErrors,
    } = require('react-devtools-shared/src/devtools/storeErrorLogger');
    const eventLogger = jest.fn();
    const unregisterEventLogger = registerEventLogger(eventLogger);
    const unsubscribeFromStoreErrors = subscribeToStoreErrors(
      global.store,
      global.bridge,
    );
    const error = new Error('Initial render error');

    try {
      expect(() => global.store._throwAndEmitError(error)).toThrow(error);
      expect(eventLogger).toHaveBeenCalledTimes(1);
      expect(eventLogger).toHaveBeenCalledWith({
        event_name: 'error',
        error_message: error.message,
        error_stack: error.stack,
        error_component_stack: null,
      });
    } finally {
      unsubscribeFromStoreErrors();
      unregisterEventLogger();
    }
  });

  it('reports a Store error once when multiple error boundaries observe it', () => {
    const {registerEventLogger} = require('react-devtools-shared/src/Logger');
    const ErrorBoundary =
      require('react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorBoundary').default;
    const {
      subscribeToStoreErrors,
    } = require('react-devtools-shared/src/devtools/storeErrorLogger');
    const store = global.store;
    const eventLogger = jest.fn();
    const unregisterEventLogger = registerEventLogger(eventLogger);
    const unsubscribeFromStoreErrors = subscribeToStoreErrors(
      store,
      global.bridge,
    );

    act(() => {
      render(
        <>
          <ErrorBoundary store={store}>First boundary</ErrorBoundary>
          <ErrorBoundary store={store}>Second boundary</ErrorBoundary>
          <ErrorBoundary store={store}>Third boundary</ErrorBoundary>
        </>,
      );
    });

    const error = new Error('Store error');
    try {
      act(() => {
        expect(() => store._throwAndEmitError(error)).toThrow(error);
      });

      expect(eventLogger).toHaveBeenCalledTimes(1);
      expect(eventLogger).toHaveBeenCalledWith({
        event_name: 'error',
        error_message: error.message,
        error_stack: error.stack,
        error_component_stack: null,
      });
      expect(
        getContainer().textContent.match(/Uncaught Error: Store error/g),
      ).toHaveLength(3);
    } finally {
      unsubscribeFromStoreErrors();
      unregisterEventLogger();
      act(() => unmount());
    }
  });

  it('registers the event logger once while its iframe is loading', () => {
    const Logger = require('react-devtools-shared/src/Logger');
    const registerEventLogger = jest.spyOn(Logger, 'registerEventLogger');
    const loggingURL = 'https://example.com/react-devtools-logging';
    const previousLoggingURL = process.env.LOGGING_URL;
    process.env.LOGGING_URL = loggingURL;

    try {
      const {
        registerDevToolsEventLogger,
      } = require('react-devtools-shared/src/registerDevToolsEventLogger');

      registerDevToolsEventLogger('test');
      registerDevToolsEventLogger('test');

      expect(registerEventLogger).toHaveBeenCalledTimes(1);
      expect(
        document.querySelectorAll(`iframe[src="${loggingURL}"]`),
      ).toHaveLength(1);
    } finally {
      registerEventLogger.mockRestore();
      if (previousLoggingURL === undefined) {
        delete process.env.LOGGING_URL;
      } else {
        process.env.LOGGING_URL = previousLoggingURL;
      }
      document
        .querySelectorAll(`iframe[src="${loggingURL}"]`)
        .forEach(iframe => iframe.remove());
    }
  });

  it('normalizes values that are not Error objects', () => {
    const {
      logErrorEvent,
      registerEventLogger,
    } = require('react-devtools-shared/src/Logger');
    const eventLogger = jest.fn();
    const unregisterEventLogger = registerEventLogger(eventLogger);

    try {
      logErrorEvent(null, null);
      logErrorEvent({message: 42, stack: {}}, null);

      expect(eventLogger).toHaveBeenNthCalledWith(1, {
        event_name: 'error',
        error_message: null,
        error_stack: null,
        error_component_stack: null,
      });
      expect(eventLogger).toHaveBeenNthCalledWith(2, {
        event_name: 'error',
        error_message: null,
        error_stack: null,
        error_component_stack: null,
      });
    } finally {
      unregisterEventLogger();
    }
  });
});