import { GlSkeletonLoader } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/flash';
import RunnerJobs from '~/runner/components/runner_jobs.vue';
import RunnerJobsTable from '~/runner/components/runner_jobs_table.vue';
import RunnerPagination from '~/runner/components/runner_pagination.vue';
import { captureException } from '~/runner/sentry_utils';
import { I18N_NO_JOBS_FOUND, RUNNER_DETAILS_JOBS_PAGE_SIZE } from '~/runner/constants';

import runnerJobsQuery from '~/runner/graphql/show/runner_jobs.query.graphql';

import { runnerData, runnerJobsData } from '../mock_data';

jest.mock('~/flash');
jest.mock('~/runner/sentry_utils');

const mockRunner = runnerData.data.runner;
const mockRunnerWithJobs = runnerJobsData.data.runner;
const mockJobs = mockRunnerWithJobs.jobs.nodes;

Vue.use(VueApollo);

describe('RunnerJobs', () => {
  let wrapper;
  let mockRunnerJobsQuery;

  const findGlSkeletonLoading = () => wrapper.findComponent(GlSkeletonLoader);
  const findRunnerJobsTable = () => wrapper.findComponent(RunnerJobsTable);
  const findRunnerPagination = () => wrapper.findComponent(RunnerPagination);

  const createComponent = ({ mountFn = shallowMountExtended } = {}) => {
    wrapper = mountFn(RunnerJobs, {
      apolloProvider: createMockApollo([[runnerJobsQuery, mockRunnerJobsQuery]]),
      propsData: {
        runner: mockRunner,
      },
    });
  };

  beforeEach(() => {
    mockRunnerJobsQuery = jest.fn();
  });

  afterEach(() => {
    mockRunnerJobsQuery.mockReset();
    wrapper.destroy();
  });

  it('Requests runner jobs', async () => {
    createComponent();

    await waitForPromises();

    expect(mockRunnerJobsQuery).toHaveBeenCalledTimes(1);
    expect(mockRunnerJobsQuery).toHaveBeenCalledWith({
      id: mockRunner.id,
      first: RUNNER_DETAILS_JOBS_PAGE_SIZE,
    });
  });

  describe('When there are jobs assigned', () => {
    beforeEach(async () => {
      mockRunnerJobsQuery.mockResolvedValueOnce(runnerJobsData);

      createComponent();
      await waitForPromises();
    });

    it('Shows jobs', () => {
      const jobs = findRunnerJobsTable().props('jobs');

      expect(jobs).toEqual(mockJobs);
    });

    describe('When "Next" page is clicked', () => {
      beforeEach(async () => {
        findRunnerPagination().vm.$emit('input', { page: 2, after: 'AFTER_CURSOR' });

        await waitForPromises();
      });

      it('A new page is requested', () => {
        expect(mockRunnerJobsQuery).toHaveBeenCalledTimes(2);
        expect(mockRunnerJobsQuery).toHaveBeenLastCalledWith({
          id: mockRunner.id,
          first: RUNNER_DETAILS_JOBS_PAGE_SIZE,
          after: 'AFTER_CURSOR',
        });
      });
    });
  });

  describe('When loading', () => {
    it('shows loading indicator and no other content', () => {
      createComponent();

      expect(findGlSkeletonLoading().exists()).toBe(true);
      expect(findRunnerJobsTable().exists()).toBe(false);
      expect(findRunnerPagination().attributes('disabled')).toBe('true');
    });
  });

  describe('When there are no jobs', () => {
    beforeEach(async () => {
      mockRunnerJobsQuery.mockResolvedValueOnce({
        data: {
          runner: {
            id: mockRunner.id,
            projectCount: 0,
            jobs: {
              nodes: [],
              pageInfo: {
                hasNextPage: false,
                hasPreviousPage: false,
                startCursor: '',
                endCursor: '',
              },
            },
          },
        },
      });

      createComponent();
      await waitForPromises();
    });

    it('Shows a "None" label', () => {
      expect(wrapper.text()).toBe(I18N_NO_JOBS_FOUND);
    });
  });

  describe('When an error occurs', () => {
    beforeEach(async () => {
      mockRunnerJobsQuery.mockRejectedValue(new Error('Error!'));

      createComponent();
      await waitForPromises();
    });

    it('shows an error', () => {
      expect(createAlert).toHaveBeenCalled();
    });

    it('reports an error', () => {
      expect(captureException).toHaveBeenCalledWith({
        component: 'RunnerJobs',
        error: expect.any(Error),
      });
    });
  });
});