import Visibility from 'visibilityjs';
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Poll from '~/lib/utils/poll';
import { deprecatedCreateFlash as flash } from '~/flash';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import { getJSONFixture } from '../helpers/fixtures';

jest.mock('~/lib/utils/poll');
jest.mock('visibilityjs');
jest.mock('~/flash');

const mockFetchData = jest.fn();
jest.mock('~/projects/tree/services/commit_pipeline_service', () =>
  jest.fn().mockImplementation(() => ({
    fetchData: mockFetchData.mockReturnValue(Promise.resolve()),
  })),
);

describe('Commit pipeline status component', () => {
  let wrapper;
  const { pipelines } = getJSONFixture('pipelines/pipelines.json');
  const { status: mockCiStatus } = pipelines[0].details;

  const defaultProps = {
    endpoint: 'endpoint',
  };

  const createComponent = (props = {}) => {
    wrapper = shallowMount(CommitPipelineStatus, {
      propsData: {
        ...defaultProps,
        ...props,
      },
    });
  };

  const findLoader = () => wrapper.find(GlLoadingIcon);
  const findLink = () => wrapper.find('a');
  const findCiIcon = () => findLink().find(CiIcon);

  afterEach(() => {
    wrapper.destroy();
    wrapper = null;
  });

  describe('Visibility management', () => {
    describe('when component is hidden', () => {
      beforeEach(() => {
        Visibility.hidden.mockReturnValue(true);
        createComponent();
      });

      it('does not start polling', () => {
        const [pollInstance] = Poll.mock.instances;
        expect(pollInstance.makeRequest).not.toHaveBeenCalled();
      });

      it('requests pipeline data', () => {
        expect(mockFetchData).toHaveBeenCalled();
      });
    });

    describe('when component is visible', () => {
      beforeEach(() => {
        Visibility.hidden.mockReturnValue(false);
        createComponent();
      });

      it('starts polling', () => {
        const [pollInstance] = [...Poll.mock.instances].reverse();
        expect(pollInstance.makeRequest).toHaveBeenCalled();
      });
    });

    describe('when component changes its visibility', () => {
      it.each`
        visibility | action
        ${false}   | ${'restart'}
        ${true}    | ${'stop'}
      `(
        '$action polling when component visibility becomes $visibility',
        ({ visibility, action }) => {
          Visibility.hidden.mockReturnValue(!visibility);
          createComponent();
          const [pollInstance] = Poll.mock.instances;
          expect(pollInstance[action]).not.toHaveBeenCalled();
          Visibility.hidden.mockReturnValue(visibility);
          const [visibilityHandler] = Visibility.change.mock.calls[0];
          visibilityHandler();
          expect(pollInstance[action]).toHaveBeenCalled();
        },
      );
    });
  });

  it('stops polling when component is destroyed', () => {
    createComponent();
    wrapper.destroy();
    const [pollInstance] = Poll.mock.instances;
    expect(pollInstance.stop).toHaveBeenCalled();
  });

  describe('when polling', () => {
    let pollConfig;
    beforeEach(() => {
      Poll.mockImplementation(config => {
        pollConfig = config;
        return { makeRequest: jest.fn(), restart: jest.fn(), stop: jest.fn() };
      });
      createComponent();
    });

    it('shows the loading icon at start', () => {
      createComponent();
      expect(findLoader().exists()).toBe(true);

      pollConfig.successCallback({
        data: { pipelines: [] },
      });

      return wrapper.vm.$nextTick().then(() => {
        expect(findLoader().exists()).toBe(false);
      });
    });

    describe('is successful', () => {
      beforeEach(() => {
        pollConfig.successCallback({
          data: { pipelines: [{ details: { status: mockCiStatus } }] },
        });
        return wrapper.vm.$nextTick();
      });

      it('does not render loader', () => {
        expect(findLoader().exists()).toBe(false);
      });

      it('renders link with href', () => {
        expect(findLink().attributes('href')).toEqual(mockCiStatus.details_path);
      });

      it('renders CI icon', () => {
        expect(findCiIcon().attributes('title')).toEqual('Pipeline: pending');
        expect(findCiIcon().props('status')).toEqual(mockCiStatus);
      });
    });

    describe('is not successful', () => {
      beforeEach(() => {
        pollConfig.errorCallback();
      });

      it('does not render loader', () => {
        expect(findLoader().exists()).toBe(false);
      });

      it('renders link with href', () => {
        expect(findLink().attributes('href')).toBeUndefined();
      });

      it('renders not found CI icon', () => {
        expect(findCiIcon().attributes('title')).toEqual('Pipeline: not found');
        expect(findCiIcon().props('status')).toEqual({
          text: 'not found',
          icon: 'status_notfound',
          group: 'notfound',
        });
      });

      it('displays flash error message', () => {
        expect(flash).toHaveBeenCalled();
      });
    });
  });
});
