jest.mock('./helpers');
jest.mock('./windowEvents');
jest.mock('./windowHelpers');

import { dialog, BrowserWindow, WebContents } from 'electron';
import { linkIsInternal, openExternal, nativeTabsSupported } from './helpers';
const { onNewWindowHelper, onWillNavigate, onWillPreventUnload } =
  jest.requireActual('./windowEvents');
import {
  blockExternalURL,
  createAboutBlankWindow,
  createNewTab,
} from './windowHelpers';

describe('onNewWindowHelper', () => {
  const originalURL = 'https://medium.com/';
  const internalURL = 'https://medium.com/topics/technology';
  const externalURL = 'https://www.wikipedia.org/wiki/Electron';
  const foregroundDisposition = 'foreground-tab';
  const backgroundDisposition = 'background-tab';

  const mockBlockExternalURL: jest.SpyInstance = blockExternalURL as jest.Mock;
  const mockCreateAboutBlank: jest.SpyInstance =
    createAboutBlankWindow as jest.Mock;
  const mockCreateNewTab: jest.SpyInstance = createNewTab as jest.Mock;
  const mockLinkIsInternal: jest.SpyInstance = (
    linkIsInternal as jest.Mock
  ).mockImplementation(() => true);
  const mockNativeTabsSupported: jest.SpyInstance =
    nativeTabsSupported as jest.Mock;
  const mockOpenExternal: jest.SpyInstance = openExternal as jest.Mock;
  const preventDefault = jest.fn();
  const setupWindow = jest.fn();

  beforeEach(() => {
    mockBlockExternalURL
      .mockReset()
      .mockReturnValue(Promise.resolve(undefined));
    mockCreateAboutBlank.mockReset();
    mockCreateNewTab.mockReset();
    mockLinkIsInternal.mockReset().mockReturnValue(true);
    mockNativeTabsSupported.mockReset().mockReturnValue(false);
    mockOpenExternal.mockReset();
    preventDefault.mockReset();
    setupWindow.mockReset();
  });

  afterAll(() => {
    mockBlockExternalURL.mockRestore();
    mockCreateAboutBlank.mockRestore();
    mockCreateNewTab.mockRestore();
    mockLinkIsInternal.mockRestore();
    mockNativeTabsSupported.mockRestore();
    mockOpenExternal.mockRestore();
  });

  test('internal urls should not be handled', () => {
    const options = {
      blockExternalUrls: false,
      targetUrl: originalURL,
    };

    onNewWindowHelper(
      options,
      setupWindow,
      internalURL,
      undefined,
      preventDefault,
    );

    expect(mockCreateAboutBlank).not.toHaveBeenCalled();
    expect(mockCreateNewTab).not.toHaveBeenCalled();
    expect(mockBlockExternalURL).not.toHaveBeenCalled();
    expect(mockOpenExternal).not.toHaveBeenCalled();
    expect(preventDefault).not.toHaveBeenCalled();
  });

  test('external urls should be opened externally', () => {
    mockLinkIsInternal.mockReturnValue(false);
    const options = {
      blockExternalUrls: false,
      targetUrl: originalURL,
    };
    onNewWindowHelper(
      options,
      setupWindow,
      externalURL,
      undefined,
      preventDefault,
    );

    expect(mockCreateAboutBlank).not.toHaveBeenCalled();
    expect(mockCreateNewTab).not.toHaveBeenCalled();
    expect(mockBlockExternalURL).not.toHaveBeenCalled();
    expect(mockOpenExternal).toHaveBeenCalledTimes(1);
    expect(preventDefault).toHaveBeenCalledTimes(1);
  });

  test('external urls should be ignored if blockExternalUrls is true', () => {
    mockLinkIsInternal.mockReturnValue(false);
    const options = {
      blockExternalUrls: true,
      targetUrl: originalURL,
    };
    onNewWindowHelper(
      options,
      setupWindow,
      externalURL,
      undefined,
      preventDefault,
    );

    expect(mockCreateAboutBlank).not.toHaveBeenCalled();
    expect(mockCreateNewTab).not.toHaveBeenCalled();
    expect(mockBlockExternalURL).toHaveBeenCalledTimes(1);
    expect(mockOpenExternal).not.toHaveBeenCalled();
    expect(preventDefault).toHaveBeenCalledTimes(1);
  });

  test('tab disposition should be ignored if tabs are not enabled', () => {
    const options = {
      blockExternalUrls: false,
      targetUrl: originalURL,
    };
    onNewWindowHelper(
      options,
      setupWindow,
      internalURL,
      foregroundDisposition,
      preventDefault,
    );

    expect(mockCreateAboutBlank).not.toHaveBeenCalled();
    expect(mockCreateNewTab).not.toHaveBeenCalled();
    expect(mockBlockExternalURL).not.toHaveBeenCalled();
    expect(mockOpenExternal).not.toHaveBeenCalled();
    expect(preventDefault).not.toHaveBeenCalled();
  });

  test('tab disposition should be ignored if url is external', () => {
    mockLinkIsInternal.mockReturnValue(false);
    const options = {
      blockExternalUrls: false,
      targetUrl: originalURL,
    };
    onNewWindowHelper(
      options,
      setupWindow,
      externalURL,
      foregroundDisposition,
      preventDefault,
    );

    expect(mockCreateAboutBlank).not.toHaveBeenCalled();
    expect(mockCreateNewTab).not.toHaveBeenCalled();
    expect(mockBlockExternalURL).not.toHaveBeenCalled();
    expect(mockOpenExternal).toHaveBeenCalledTimes(1);
    expect(preventDefault).toHaveBeenCalledTimes(1);
  });

  test('foreground tabs with internal urls should be opened in the foreground', () => {
    mockNativeTabsSupported.mockReturnValue(true);

    const options = {
      blockExternalUrls: false,
      targetUrl: originalURL,
    };
    onNewWindowHelper(
      options,
      setupWindow,
      internalURL,
      foregroundDisposition,
      preventDefault,
    );

    expect(mockCreateAboutBlank).not.toHaveBeenCalled();
    expect(mockCreateNewTab).toHaveBeenCalledTimes(1);
    expect(mockCreateNewTab).toHaveBeenCalledWith(
      options,
      setupWindow,
      internalURL,
      true,
      undefined,
    );
    expect(mockBlockExternalURL).not.toHaveBeenCalled();
    expect(mockOpenExternal).not.toHaveBeenCalled();
    expect(preventDefault).toHaveBeenCalledTimes(1);
  });

  test('background tabs with internal urls should be opened in background tabs', () => {
    mockNativeTabsSupported.mockReturnValue(true);

    const options = {
      blockExternalUrls: false,
      targetUrl: originalURL,
    };
    onNewWindowHelper(
      options,
      setupWindow,
      internalURL,
      backgroundDisposition,
      preventDefault,
    );

    expect(mockCreateAboutBlank).not.toHaveBeenCalled();
    expect(mockCreateNewTab).toHaveBeenCalledTimes(1);
    expect(mockCreateNewTab).toHaveBeenCalledWith(
      options,
      setupWindow,
      internalURL,
      false,
      undefined,
    );
    expect(mockBlockExternalURL).not.toHaveBeenCalled();
    expect(mockOpenExternal).not.toHaveBeenCalled();
    expect(preventDefault).toHaveBeenCalledTimes(1);
  });

  test('about:blank urls should be handled', () => {
    const options = {
      blockExternalUrls: false,
      targetUrl: originalURL,
    };
    onNewWindowHelper(
      options,
      setupWindow,
      'about:blank',
      undefined,
      preventDefault,
    );

    expect(mockCreateAboutBlank).toHaveBeenCalledTimes(1);
    expect(mockCreateNewTab).not.toHaveBeenCalled();
    expect(mockBlockExternalURL).not.toHaveBeenCalled();
    expect(mockOpenExternal).not.toHaveBeenCalled();
    expect(preventDefault).toHaveBeenCalledTimes(1);
  });
});

describe('onWillNavigate', () => {
  const originalURL = 'https://medium.com/';
  const internalURL = 'https://medium.com/topics/technology';
  const externalURL = 'https://www.wikipedia.org/wiki/Electron';

  const mockBlockExternalURL: jest.SpyInstance = blockExternalURL as jest.Mock;
  const mockLinkIsInternal: jest.SpyInstance = linkIsInternal as jest.Mock;
  const mockOpenExternal: jest.SpyInstance = openExternal as jest.Mock;
  const preventDefault = jest.fn();

  beforeEach(() => {
    mockBlockExternalURL
      .mockReset()
      .mockReturnValue(Promise.resolve(undefined));
    mockLinkIsInternal.mockReset().mockReturnValue(false);
    mockOpenExternal.mockReset();
    preventDefault.mockReset();
  });

  afterAll(() => {
    mockBlockExternalURL.mockRestore();
    mockLinkIsInternal.mockRestore();
    mockOpenExternal.mockRestore();
  });

  test('internal urls should not be handled', () => {
    mockLinkIsInternal.mockReturnValue(true);
    const options = {
      blockExternalUrls: false,
      targetUrl: originalURL,
    };
    const event = { preventDefault };
    onWillNavigate(options, event, internalURL);

    expect(mockBlockExternalURL).not.toHaveBeenCalled();
    expect(mockOpenExternal).not.toHaveBeenCalled();
    expect(preventDefault).not.toHaveBeenCalled();
  });

  test('external urls should be opened externally', () => {
    const options = {
      blockExternalUrls: false,
      targetUrl: originalURL,
    };
    const event = { preventDefault };
    onWillNavigate(options, event, externalURL);

    expect(mockBlockExternalURL).not.toHaveBeenCalled();
    expect(mockOpenExternal).toHaveBeenCalledTimes(1);
    expect(preventDefault).toHaveBeenCalledTimes(1);
  });

  test('external urls should be ignored if blockExternalUrls is true', () => {
    const options = {
      blockExternalUrls: true,
      targetUrl: originalURL,
    };
    const event = { preventDefault };
    onWillNavigate(options, event, externalURL);

    expect(mockBlockExternalURL).toHaveBeenCalledTimes(1);
    expect(mockOpenExternal).not.toHaveBeenCalled();
    expect(preventDefault).toHaveBeenCalledTimes(1);
  });
});

describe('onWillPreventUnload', () => {
  const mockFromWebContents: jest.SpyInstance = jest
    .spyOn(BrowserWindow, 'fromWebContents')
    .mockImplementation(() => new BrowserWindow());
  const mockShowDialog: jest.SpyInstance = jest.spyOn(
    dialog,
    'showMessageBoxSync',
  );
  const preventDefault: jest.SpyInstance = jest.fn();

  beforeEach(() => {
    mockFromWebContents.mockReset();
    mockShowDialog.mockReset().mockReturnValue(undefined);
    preventDefault.mockReset();
  });

  afterAll(() => {
    mockFromWebContents.mockRestore();
    mockShowDialog.mockRestore();
  });

  test('with no sender', () => {
    const event = {};
    onWillPreventUnload(event);

    expect(mockFromWebContents).not.toHaveBeenCalled();
    expect(mockShowDialog).not.toHaveBeenCalled();
    expect(preventDefault).not.toHaveBeenCalled();
  });

  test('shows dialog and calls preventDefault on ok', () => {
    mockShowDialog.mockReturnValue(0);

    const event = { preventDefault, sender: new WebContents() };
    onWillPreventUnload(event);

    expect(mockFromWebContents).toHaveBeenCalledWith(event.sender);
    expect(mockShowDialog).toHaveBeenCalled();
    expect(preventDefault).toHaveBeenCalledWith();
  });

  test('shows dialog and does not call preventDefault on cancel', () => {
    mockShowDialog.mockReturnValue(1);

    const event = { preventDefault, sender: new WebContents() };
    onWillPreventUnload(event);

    expect(mockFromWebContents).toHaveBeenCalledWith(event.sender);
    expect(mockShowDialog).toHaveBeenCalled();
    expect(preventDefault).not.toHaveBeenCalled();
  });
});
