// @flow
import React from 'react';
import { act } from 'react-dom/test-utils';
import {
  mountWrappedComponent,
  getFakeComponent,
  itIsAccessible,
} from '../../../../utils/tests';

import type { ModalProps } from './modal.proptypes';
import {
  Modal,
  ModalBackground,
  ModalContent,
  ModalHeader,
  ModalRemove,
} from './index';

const DEFAULT_PROPS: ModalProps = {
  show: true,
  persistent: false,
  onClose: () => {},
  size: 'md',
  children: getFakeComponent('modal child'),
};

describe('Modal', () => {
  it('renders children', () => {
    const modal = mountWrappedComponent(<Modal {...DEFAULT_PROPS} />);

    expect(modal.contains(DEFAULT_PROPS.children)).toBe(true);
  });

  it('does not render if show is false', () => {
    const modal = mountWrappedComponent(
      <Modal {...DEFAULT_PROPS} show={false} />,
    );

    expect(modal.contains(DEFAULT_PROPS.children)).toBe(false);
    expect(modal.find(ModalBackground).exists()).toBe(false);
  });

  describe('background', () => {
    it('closes when background is clicked and not persistent', () => {
      const handleOnClose = jest.fn();
      const handlePreventDefault = jest.fn();
      const modal = mountWrappedComponent(
        <Modal {...DEFAULT_PROPS} onClose={handleOnClose} persistent={false} />,
      );

      expect(handlePreventDefault).toHaveBeenCalledTimes(0);
      expect(handleOnClose).toHaveBeenCalledTimes(0);

      const background = modal.find(ModalBackground);
      background.simulate('click', {
        preventDefault: handlePreventDefault,
      });

      expect(handlePreventDefault).toHaveBeenCalledTimes(1);
      expect(handleOnClose).toHaveBeenCalledTimes(1);
    });

    it('does not close when persistent and background is clicked', () => {
      const handleOnClose = jest.fn();
      const handlePreventDefault = jest.fn();
      const modal = mountWrappedComponent(
        <Modal {...DEFAULT_PROPS} onClose={handleOnClose} persistent />,
      );

      expect(handlePreventDefault).toHaveBeenCalledTimes(0);
      expect(handleOnClose).toHaveBeenCalledTimes(0);

      const background = modal.find(ModalBackground);
      background.simulate('click', {
        preventDefault: handlePreventDefault,
      });

      expect(handlePreventDefault).toHaveBeenCalledTimes(1);
      expect(handleOnClose).toHaveBeenCalledTimes(0);
    });
  });

  describe('content', () => {
    it('renders modal content', () => {
      const modal = mountWrappedComponent(<Modal {...DEFAULT_PROPS} />);

      const content = modal.find(ModalContent);
      expect(content.children().exists()).toBe(true);
    });

    it('is given valid props', () => {
      const modal = mountWrappedComponent(<Modal {...DEFAULT_PROPS} />);

      const content = modal.find(ModalContent);
      expect(content.props()).toEqual({
        onClick: expect.any(Function),
        size: DEFAULT_PROPS.size,
        className: expect.any(String),
        children: expect.anything(),
        'aria-modal': 'true',
        role: 'dialog',
      });
    });

    it('does not progate click event', () => {
      const handleStopPropagation = jest.fn();
      const modal = mountWrappedComponent(<Modal {...DEFAULT_PROPS} />);

      expect(handleStopPropagation).toHaveBeenCalledTimes(0);

      const content = modal.find(ModalContent);
      content.simulate('click', { stopPropagation: handleStopPropagation });

      expect(handleStopPropagation).toHaveBeenCalledTimes(1);
    });
  });

  describe('header', () => {
    it('renders a remove button if not persistent', () => {
      const modal = mountWrappedComponent(
        <Modal {...DEFAULT_PROPS} persistent={false} />,
      );

      const header = modal.find(ModalHeader);
      expect(header.find(ModalRemove).exists()).toBe(true);
    });

    it('does not render a remove button if persistent', () => {
      const modal = mountWrappedComponent(
        <Modal {...DEFAULT_PROPS} persistent />,
      );

      const header = modal.find(ModalHeader);
      expect(header.find(ModalRemove).exists()).toBe(false);
    });

    it('closes when background is clicked and not persistent', () => {
      const handleOnClose = jest.fn();
      const handleStopPropagation = jest.fn();
      const modal = mountWrappedComponent(
        <Modal {...DEFAULT_PROPS} onClose={handleOnClose} persistent={false} />,
      );

      expect(handleOnClose).toHaveBeenCalledTimes(0);

      const removeButton = modal.find(ModalRemove);
      removeButton.simulate('click', {
        stopPropagation: handleStopPropagation,
      });

      expect(handleStopPropagation).toHaveBeenCalledWith();
      expect(handleOnClose).toHaveBeenCalledWith();
    });

    it('does not render header if hideHeader prop is true', () => {
      const modal = mountWrappedComponent(
        <Modal {...DEFAULT_PROPS} hideHeader />,
      );

      expect(modal.find(ModalHeader).exists()).toBe(false);
    });
  });

  describe('key press', () => {
    it('should call onClose on "Escape" press', () => {
      const handleOnClose = jest.fn();
      mountWrappedComponent(
        <Modal {...DEFAULT_PROPS} onClose={handleOnClose} />,
      );

      expect(handleOnClose).toHaveBeenCalledTimes(0);

      const event = new KeyboardEvent('keydown', { code: 'Escape' });
      window.dispatchEvent(event);

      expect(handleOnClose).toHaveBeenCalledTimes(1);
    });

    it('should do nothing if any other key pressed', () => {
      const handleOnClose = jest.fn();
      mountWrappedComponent(
        <Modal {...DEFAULT_PROPS} onClose={handleOnClose} />,
      );

      expect(handleOnClose).toHaveBeenCalledTimes(0);

      const event = new KeyboardEvent('keydown', { code: 'Space' });
      window.dispatchEvent(event);

      expect(handleOnClose).toHaveBeenCalledTimes(0);
    });

    it('should not listen to keypresses if modal not shown', () => {
      const handleOnClose = jest.fn();
      mountWrappedComponent(
        <Modal {...DEFAULT_PROPS} onClose={handleOnClose} show={false} />,
      );

      expect(handleOnClose).toHaveBeenCalledTimes(0);

      const event = new KeyboardEvent('keydown', { code: 'Escape' });
      window.dispatchEvent(event);

      expect(handleOnClose).toHaveBeenCalledTimes(0);
    });

    it('should not close if persistent', () => {
      const handleOnClose = jest.fn();
      mountWrappedComponent(
        <Modal {...DEFAULT_PROPS} onClose={handleOnClose} persistent />,
      );

      expect(handleOnClose).toHaveBeenCalledTimes(0);

      const event = new KeyboardEvent('keydown', { code: 'Escape' });
      window.dispatchEvent(event);

      expect(handleOnClose).toHaveBeenCalledTimes(0);
    });
  });

  describe('modal focusing', () => {
    beforeEach(() => {
      // $FlowFixMe
      document.body.innerHTML = '';
    });

    it('focuses on the modal content', () => {
      const modal = mountWrappedComponent(<Modal {...DEFAULT_PROPS} />);

      const focusedElement = document.activeElement;
      const modalContent = modal.find('ModalContent');
      expect(focusedElement).toEqual(modalContent.getDOMNode());
    });

    describe('when closing the modal', () => {
      beforeEach(() => {
        // $FlowFixMe
        document.body.innerHTML = '';
      });

      const TestComponent = () => {
        const [show, setShow] = React.useState(false);

        return (
          <>
            {/* eslint-disable jsx-a11y/no-autofocus */}
            <input autoFocus id="focus-me" />
            <button id="open-modal" type="button" onClick={() => setShow(true)}>
              open
            </button>
            <Modal
              {...DEFAULT_PROPS}
              show={show}
              onClose={() => setShow(false)}
            />
          </>
        );
      };

      it('traps focus so we cannot go out of focus on the modal', () => {
        let component = mountWrappedComponent(<TestComponent />);

        // we open the modal, putting focus on the modal
        act(() => {
          component.find('#open-modal').simulate('click');
        });

        component = component.update();

        act(() => {
          // simulate a focus on an outside element
          component.find('#open-modal').simulate('focus');
        });

        expect(document.activeElement).toEqual(
          component.find('ModalContent').getDOMNode(),
        );
      });
    });

    describe('when the modal child has auto focus', () => {
      beforeEach(() => {
        // $FlowFixMe
        document.body.innerHTML = '';
      });

      it('focuses on the child', () => {
        const modal = mountWrappedComponent(
          <Modal {...DEFAULT_PROPS}>
            {/* eslint-disable jsx-a11y/no-autofocus */}
            <input id="focus-me" autoFocus />
          </Modal>,
        );

        const focusedElement = document.activeElement;
        const focusedInput = modal.find('#focus-me');

        expect(focusedElement).toEqual(focusedInput.getDOMNode());
      });
    });
  });

  describe('accessibility', () => {
    itIsAccessible(<Modal {...DEFAULT_PROPS} show />);
  });
});
