import { Button } from '..';
import { addNoScrollClass, removeNoScrollClass } from '../common';
import { render, waitFor, screen, userEvent, waitForElementToBeRemoved } from '../test-utils';

import { Dimmer } from './Dimmer';

jest.mock('../common/DOMOperations');

describe('Dimmer', () => {
  const props = {
    open: true,
    children: <div />,
    onClose: jest.fn(),
  };

  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('should render', () => {
    const myContent = 'My Content';
    render(<Dimmer {...props}>{myContent}</Dimmer>);
    expect(screen.getByText(myContent)).toBeInTheDocument();
  });

  describe('closing behaviour', () => {
    it('calls onClose if the dimmer is clicked', async () => {
      render(<Dimmer {...props} />);
      await userEvent.click(screen.getByRole('presentation'));
      expect(props.onClose).toHaveBeenCalledTimes(1);
    });

    it('does not call onClose if the dimmer content is clicked', async () => {
      render(
        <Dimmer {...props}>
          <Button v2 onClick={() => {}}>
            McClicky
          </Button>
        </Dimmer>,
      );
      await userEvent.click(screen.getByRole('button'));
      expect(props.onClose).toHaveBeenCalledTimes(0);
    });

    it('does not call onClose when clicked if disableClickToClose is true', async () => {
      render(<Dimmer {...props} disableClickToClose />);
      await userEvent.click(screen.getByRole('presentation'));
      expect(props.onClose).toHaveBeenCalledTimes(0);
    });

    it('calls onClose if the escape key is pressed', async () => {
      render(<Dimmer {...props} />);
      await userEvent.keyboard('{Escape}');
      expect(props.onClose).toHaveBeenCalledTimes(1);
    });

    it('does not call onClose if the escape key is pressed when disableClickToClose is true', async () => {
      render(<Dimmer {...props} disableClickToClose />);
      await userEvent.keyboard('{Escape}');
      expect(props.onClose).toHaveBeenCalledTimes(1);
    });
  });

  describe('transparent', () => {
    it('shows backdrop by default', () => {
      render(<Dimmer {...props} />);
      expect(screen.getByRole('presentation')).not.toHaveClass('dimmer--transparent');
    });

    it('adds a class to hide the backdrop when transparent is true', () => {
      render(<Dimmer {...props} transparent />);
      expect(screen.getByRole('presentation')).toHaveClass('dimmer--transparent');
    });

    it('will not lock body scroll when transparent is true', () => {
      render(<Dimmer {...props} transparent />);

      expect(addNoScrollClass).not.toHaveBeenCalled();
    });
  });

  describe('scrolling', () => {
    it('should not have `dimmer--scrollable` class by default', () => {
      render(<Dimmer {...props} />);
      expect(screen.getByRole('presentation')).not.toHaveClass('dimmer--scrollable');
    });

    it("should add 'dimmer--scrollable' class on `scrollable` prop set", () => {
      render(<Dimmer {...props} scrollable />);
      expect(screen.getByRole('presentation')).toHaveClass('dimmer--scrollable');
    });

    it('calls addNoScrollClass on render', () => {
      expect(addNoScrollClass).not.toHaveBeenCalled();

      render(<Dimmer {...props} />);

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

    it('calls removeNoScrollClass on unmount', async () => {
      expect(removeNoScrollClass).not.toHaveBeenCalled();

      const { unmount } = render(<Dimmer {...props} />);

      unmount();

      await waitFor(() => {
        expect(removeNoScrollClass).toHaveBeenCalledTimes(1);
      });
    });

    it('calls removeNoScrollClass only once when the children is unmounted', async () => {
      const { rerender, unmount } = render(<Dimmer {...props} />);

      expect(removeNoScrollClass).toHaveBeenCalledTimes(0);
      rerender(<Dimmer {...props} open={false} />);

      await waitFor(() => {
        expect(removeNoScrollClass).toHaveBeenCalledTimes(1);
      });

      unmount();

      await waitFor(() => {
        expect(removeNoScrollClass).toHaveBeenCalledTimes(1);
      });
    });
  });

  describe('animates out on exit', () => {
    it("isn't removed until the animation has finished", async () => {
      const { rerender } = render(
        <Dimmer {...props} open={false} fadeContentOnEnter fadeContentOnExit>
          <p>Content</p>
        </Dimmer>,
      );

      expect(screen.queryByText('Content')).not.toBeInTheDocument();

      rerender(
        <Dimmer {...props} open fadeContentOnEnter fadeContentOnExit>
          <p>Content</p>
        </Dimmer>,
      );

      expect(await screen.findByText('Content')).toBeInTheDocument();

      rerender(
        <Dimmer {...props} open={false} fadeContentOnEnter fadeContentOnExit>
          <p>Content</p>
        </Dimmer>,
      );

      await waitForElementToBeRemoved(screen.queryByText('Content'));
    });
  });
});
