/**
 * 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.
 *
 * @emails react-core
 */

'use strict';

describe('EventPluginRegistry', () => {
  let EventPluginRegistry;
  let createPlugin;

  beforeEach(() => {
    jest.resetModules();
    // These tests are intentionally testing the private injection interface.
    // The public API surface of this is covered by other tests so
    // if `EventPluginRegistry` is ever deleted, these tests should be
    // safe to remove too.
    EventPluginRegistry = require('react-native-renderer/src/legacy-events/EventPluginRegistry');

    createPlugin = function (properties) {
      return Object.assign({extractEvents: function () {}}, properties);
    };
  });

  it('should be able to inject ordering before plugins', () => {
    const OnePlugin = createPlugin();
    const TwoPlugin = createPlugin();
    const ThreePlugin = createPlugin();

    EventPluginRegistry.injectEventPluginOrder(['one', 'two', 'three']);
    EventPluginRegistry.injectEventPluginsByName({
      one: OnePlugin,
      two: TwoPlugin,
    });
    EventPluginRegistry.injectEventPluginsByName({
      three: ThreePlugin,
    });

    expect(EventPluginRegistry.plugins.length).toBe(3);
    expect(EventPluginRegistry.plugins[0]).toBe(OnePlugin);
    expect(EventPluginRegistry.plugins[1]).toBe(TwoPlugin);
    expect(EventPluginRegistry.plugins[2]).toBe(ThreePlugin);
  });

  it('should be able to inject plugins before and after ordering', () => {
    const OnePlugin = createPlugin();
    const TwoPlugin = createPlugin();
    const ThreePlugin = createPlugin();

    EventPluginRegistry.injectEventPluginsByName({
      one: OnePlugin,
      two: TwoPlugin,
    });
    EventPluginRegistry.injectEventPluginOrder(['one', 'two', 'three']);
    EventPluginRegistry.injectEventPluginsByName({
      three: ThreePlugin,
    });

    expect(EventPluginRegistry.plugins.length).toBe(3);
    expect(EventPluginRegistry.plugins[0]).toBe(OnePlugin);
    expect(EventPluginRegistry.plugins[1]).toBe(TwoPlugin);
    expect(EventPluginRegistry.plugins[2]).toBe(ThreePlugin);
  });

  it('should be able to inject repeated plugins and out-of-order', () => {
    const OnePlugin = createPlugin();
    const TwoPlugin = createPlugin();
    const ThreePlugin = createPlugin();

    EventPluginRegistry.injectEventPluginsByName({
      one: OnePlugin,
      three: ThreePlugin,
    });
    EventPluginRegistry.injectEventPluginOrder(['one', 'two', 'three']);
    EventPluginRegistry.injectEventPluginsByName({
      two: TwoPlugin,
      three: ThreePlugin,
    });

    expect(EventPluginRegistry.plugins.length).toBe(3);
    expect(EventPluginRegistry.plugins[0]).toBe(OnePlugin);
    expect(EventPluginRegistry.plugins[1]).toBe(TwoPlugin);
    expect(EventPluginRegistry.plugins[2]).toBe(ThreePlugin);
  });

  it('should throw if plugin does not implement `extractEvents`', () => {
    const BadPlugin = {};

    EventPluginRegistry.injectEventPluginOrder(['bad']);

    expect(function () {
      EventPluginRegistry.injectEventPluginsByName({
        bad: BadPlugin,
      });
    }).toThrowError(
      'EventPluginRegistry: Event plugins must implement an `extractEvents` ' +
        'method, but `bad` does not.',
    );
  });

  it('should throw if plugin does not exist in ordering', () => {
    const OnePlugin = createPlugin();
    const RandomPlugin = createPlugin();

    EventPluginRegistry.injectEventPluginOrder(['one']);

    expect(function () {
      EventPluginRegistry.injectEventPluginsByName({
        one: OnePlugin,
        random: RandomPlugin,
      });
    }).toThrowError(
      'EventPluginRegistry: Cannot inject event plugins that do not exist ' +
        'in the plugin ordering, `random`.',
    );
  });

  it('should throw if ordering is injected more than once', () => {
    const pluginOrdering = [];

    EventPluginRegistry.injectEventPluginOrder(pluginOrdering);

    expect(function () {
      EventPluginRegistry.injectEventPluginOrder(pluginOrdering);
    }).toThrowError(
      'EventPluginRegistry: Cannot inject event plugin ordering more than ' +
        'once. You are likely trying to load more than one copy of React.',
    );
  });

  it('should throw if different plugins injected using same name', () => {
    const OnePlugin = createPlugin();
    const TwoPlugin = createPlugin();

    EventPluginRegistry.injectEventPluginsByName({same: OnePlugin});

    expect(function () {
      EventPluginRegistry.injectEventPluginsByName({same: TwoPlugin});
    }).toThrowError(
      'EventPluginRegistry: Cannot inject two different event plugins using ' +
        'the same name, `same`.',
    );
  });

  it('should publish registration names of injected plugins', () => {
    const OnePlugin = createPlugin({
      eventTypes: {
        click: {registrationName: 'onClick'},
        focus: {registrationName: 'onFocus'},
      },
    });
    const TwoPlugin = createPlugin({
      eventTypes: {
        magic: {
          phasedRegistrationNames: {
            bubbled: 'onMagicBubble',
            captured: 'onMagicCapture',
          },
        },
      },
    });

    EventPluginRegistry.injectEventPluginsByName({one: OnePlugin});
    EventPluginRegistry.injectEventPluginOrder(['one', 'two']);

    expect(
      Object.keys(EventPluginRegistry.registrationNameModules).length,
    ).toBe(2);
    expect(EventPluginRegistry.registrationNameModules.onClick).toBe(OnePlugin);
    expect(EventPluginRegistry.registrationNameModules.onFocus).toBe(OnePlugin);

    EventPluginRegistry.injectEventPluginsByName({two: TwoPlugin});

    expect(
      Object.keys(EventPluginRegistry.registrationNameModules).length,
    ).toBe(4);
    expect(EventPluginRegistry.registrationNameModules.onMagicBubble).toBe(
      TwoPlugin,
    );
    expect(EventPluginRegistry.registrationNameModules.onMagicCapture).toBe(
      TwoPlugin,
    );
  });

  it('should throw if multiple registration names collide', () => {
    const OnePlugin = createPlugin({
      eventTypes: {
        photoCapture: {registrationName: 'onPhotoCapture'},
      },
    });
    const TwoPlugin = createPlugin({
      eventTypes: {
        photo: {
          phasedRegistrationNames: {
            bubbled: 'onPhotoBubble',
            captured: 'onPhotoCapture',
          },
        },
      },
    });

    EventPluginRegistry.injectEventPluginsByName({
      one: OnePlugin,
      two: TwoPlugin,
    });

    expect(function () {
      EventPluginRegistry.injectEventPluginOrder(['one', 'two']);
    }).toThrowError(
      'EventPluginRegistry: More than one plugin attempted to publish the same ' +
        'registration name, `onPhotoCapture`.',
    );
  });

  it('should throw if an invalid event is published', () => {
    const OnePlugin = createPlugin({
      eventTypes: {
        badEvent: {
          /* missing configuration */
        },
      },
    });

    EventPluginRegistry.injectEventPluginsByName({one: OnePlugin});

    expect(function () {
      EventPluginRegistry.injectEventPluginOrder(['one']);
    }).toThrowError(
      'EventPluginRegistry: Failed to publish event `badEvent` for plugin ' +
        '`one`.',
    );
  });
});