// eslint-disable-next-line no-restricted-imports
import Mousetrap from 'mousetrap';

const originalMethodReturnValue = {};
// Create a mock stopCallback method before ~/lib/utils/mousetrap overwrites
// it. This allows us to spy on calls to it.
const mockOriginalStopCallbackMethod = jest.fn().mockReturnValue(originalMethodReturnValue);
Mousetrap.prototype.stopCallback = mockOriginalStopCallbackMethod;

describe('mousetrap utils', () => {
  describe('addStopCallback', () => {
    let addStopCallback;
    let clearStopCallbacksForTests;
    const mockMousetrapInstance = { isMockMousetrap: true };
    const mockKeyboardEvent = { type: 'keydown', key: 'Enter' };
    const mockCombo = 'enter';

    const mockKeydown = ({
      instance = mockMousetrapInstance,
      event = mockKeyboardEvent,
      element = document,
      combo = mockCombo,
    } = {}) => Mousetrap.prototype.stopCallback.call(instance, event, element, combo);

    beforeEach(async () => {
      // Import async since it mutates the Mousetrap instance, by design.
      ({ addStopCallback, clearStopCallbacksForTests } = await import('~/lib/mousetrap'));
      clearStopCallbacksForTests();
    });

    it('delegates to the original stopCallback method when no additional callbacks added', () => {
      const returnValue = mockKeydown();

      expect(mockOriginalStopCallbackMethod).toHaveBeenCalledTimes(1);

      const [thisArg] = mockOriginalStopCallbackMethod.mock.contexts;
      const [eventArg, element, combo] = mockOriginalStopCallbackMethod.mock.calls[0];

      expect(thisArg).toBe(mockMousetrapInstance);
      expect(eventArg).toBe(mockKeyboardEvent);
      expect(element).toBe(document);
      expect(combo).toBe(mockCombo);

      expect(returnValue).toBe(originalMethodReturnValue);
    });

    it('passes the expected arguments to the given stop callback', () => {
      const callback = jest.fn();

      addStopCallback(callback);

      mockKeydown();

      expect(callback).toHaveBeenCalledTimes(1);

      const [thisArg] = callback.mock.contexts;
      const [eventArg, element, combo] = callback.mock.calls[0];

      expect(thisArg).toBe(mockMousetrapInstance);
      expect(eventArg).toBe(mockKeyboardEvent);
      expect(element).toBe(document);
      expect(combo).toBe(mockCombo);
    });

    describe.each([true, false])('when a stop handler returns %p', (stopCallbackReturnValue) => {
      let methodReturnValue;
      const stopCallback = jest.fn().mockReturnValue(stopCallbackReturnValue);

      beforeEach(() => {
        addStopCallback(stopCallback);

        methodReturnValue = mockKeydown();
      });

      it(`returns ${stopCallbackReturnValue}`, () => {
        expect(methodReturnValue).toBe(stopCallbackReturnValue);
      });

      it('calls stop callback', () => {
        expect(stopCallback).toHaveBeenCalledTimes(1);
      });

      it('does not call mockOriginalStopCallbackMethod', () => {
        expect(mockOriginalStopCallbackMethod).not.toHaveBeenCalled();
      });
    });

    describe('when a stop handler returns undefined', () => {
      let methodReturnValue;
      const stopCallback = jest.fn().mockReturnValue(undefined);

      beforeEach(() => {
        addStopCallback(stopCallback);

        methodReturnValue = mockKeydown();
      });

      it('returns originalMethodReturnValue', () => {
        expect(methodReturnValue).toBe(originalMethodReturnValue);
      });

      it('calls stop callback', () => {
        expect(stopCallback).toHaveBeenCalledTimes(1);
      });

      // Because this is the only registered stop callback, the next callback
      // is the original method.
      it('does call original stopCallback method', () => {
        expect(mockOriginalStopCallbackMethod).toHaveBeenCalledTimes(1);
      });
    });
  });
});