diff options
Diffstat (limited to 'spec/frontend/commit/pipelines/legacy_pipelines_table_wrapper_spec.js')
-rw-r--r-- | spec/frontend/commit/pipelines/legacy_pipelines_table_wrapper_spec.js | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/spec/frontend/commit/pipelines/legacy_pipelines_table_wrapper_spec.js b/spec/frontend/commit/pipelines/legacy_pipelines_table_wrapper_spec.js new file mode 100644 index 00000000000..4af292e3588 --- /dev/null +++ b/spec/frontend/commit/pipelines/legacy_pipelines_table_wrapper_spec.js @@ -0,0 +1,362 @@ +import { GlLoadingIcon, GlModal, GlTableLite } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import { nextTick } from 'vue'; +import fixture from 'test_fixtures/pipelines/pipelines.json'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import { stubComponent } from 'helpers/stub_component'; +import waitForPromises from 'helpers/wait_for_promises'; +import Api from '~/api'; +import LegacyPipelinesTableWraper from '~/commit/pipelines/legacy_pipelines_table_wrapper.vue'; +import { + HTTP_STATUS_BAD_REQUEST, + HTTP_STATUS_INTERNAL_SERVER_ERROR, + HTTP_STATUS_OK, + HTTP_STATUS_UNAUTHORIZED, +} from '~/lib/utils/http_status'; +import { createAlert } from '~/alert'; +import { TOAST_MESSAGE } from '~/ci/pipeline_details/constants'; +import axios from '~/lib/utils/axios_utils'; + +const $toast = { + show: jest.fn(), +}; + +jest.mock('~/alert'); + +describe('Pipelines table in Commits and Merge requests', () => { + let wrapper; + let pipeline; + let mock; + const showMock = jest.fn(); + + const findRunPipelineBtn = () => wrapper.findByTestId('run_pipeline_button'); + const findRunPipelineBtnMobile = () => wrapper.findByTestId('run_pipeline_button_mobile'); + const findLoadingState = () => wrapper.findComponent(GlLoadingIcon); + const findErrorEmptyState = () => wrapper.findByTestId('pipeline-error-empty-state'); + const findEmptyState = () => wrapper.findByTestId('pipeline-empty-state'); + const findTable = () => wrapper.findComponent(GlTableLite); + const findTableRows = () => wrapper.findAllByTestId('pipeline-table-row'); + const findModal = () => wrapper.findComponent(GlModal); + const findMrPipelinesDocsLink = () => wrapper.findByTestId('mr-pipelines-docs-link'); + + const createComponent = ({ props = {} } = {}) => { + wrapper = extendedWrapper( + mount(LegacyPipelinesTableWraper, { + propsData: { + endpoint: 'endpoint.json', + emptyStateSvgPath: 'foo', + errorStateSvgPath: 'foo', + ...props, + }, + mocks: { + $toast, + }, + stubs: { + GlModal: stubComponent(GlModal, { + template: '<div />', + methods: { show: showMock }, + }), + }, + }), + ); + }; + + beforeEach(() => { + mock = new MockAdapter(axios); + + const { pipelines } = fixture; + + pipeline = pipelines.find((p) => p.user !== null && p.commit !== null); + }); + + describe('successful request', () => { + describe('without pipelines', () => { + beforeEach(async () => { + mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, []); + + createComponent(); + + await waitForPromises(); + }); + + it('should render the empty state', () => { + expect(findTableRows()).toHaveLength(0); + expect(findLoadingState().exists()).toBe(false); + expect(findErrorEmptyState().exists()).toBe(false); + expect(findEmptyState().exists()).toBe(true); + }); + + it('should render correct empty state content', () => { + expect(findRunPipelineBtn().exists()).toBe(true); + expect(findMrPipelinesDocsLink().attributes('href')).toBe( + '/help/ci/pipelines/merge_request_pipelines.md#prerequisites', + ); + expect(findEmptyState().text()).toContain( + 'To run a merge request pipeline, the jobs in the CI/CD configuration file must be configured to run in merge request pipelines.', + ); + }); + }); + + describe('with pagination', () => { + beforeEach(async () => { + mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, [pipeline], { + 'X-TOTAL': 10, + 'X-PER-PAGE': 2, + 'X-PAGE': 1, + 'X-TOTAL-PAGES': 5, + 'X-NEXT-PAGE': 2, + 'X-PREV-PAGE': 2, + }); + + createComponent(); + + await waitForPromises(); + }); + + it('should make an API request when using pagination', async () => { + expect(mock.history.get).toHaveLength(1); + expect(mock.history.get[0].params.page).toBe('1'); + + wrapper.find('.next-page-item').trigger('click'); + + await waitForPromises(); + + expect(mock.history.get).toHaveLength(2); + expect(mock.history.get[1].params.page).toBe('2'); + }); + }); + + describe('with pipelines', () => { + beforeEach(async () => { + mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, [pipeline], { 'x-total': 10 }); + + createComponent(); + + await waitForPromises(); + }); + + it('should render a table with the received pipelines', () => { + expect(findTable().exists()).toBe(true); + expect(findTableRows()).toHaveLength(1); + expect(findLoadingState().exists()).toBe(false); + expect(findErrorEmptyState().exists()).toBe(false); + }); + + describe('pipeline badge counts', () => { + it('should receive update-pipelines-count event', () => { + const element = document.createElement('div'); + document.body.appendChild(element); + + return new Promise((resolve) => { + element.addEventListener('update-pipelines-count', (event) => { + expect(event.detail.pipelineCount).toEqual(10); + resolve(); + }); + + createComponent(); + + element.appendChild(wrapper.vm.$el); + }); + }); + }); + }); + }); + + describe('run pipeline button', () => { + let pipelineCopy; + + beforeEach(() => { + pipelineCopy = { ...pipeline }; + }); + + describe('when latest pipeline has detached flag', () => { + it('renders the run pipeline button', async () => { + pipelineCopy.flags.detached_merge_request_pipeline = true; + pipelineCopy.flags.merge_request_pipeline = true; + + mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, [pipelineCopy]); + + createComponent(); + + await waitForPromises(); + + expect(findRunPipelineBtn().exists()).toBe(true); + expect(findRunPipelineBtnMobile().exists()).toBe(true); + }); + }); + + describe('when latest pipeline does not have detached flag', () => { + it('does not render the run pipeline button', async () => { + pipelineCopy.flags.detached_merge_request_pipeline = false; + pipelineCopy.flags.merge_request_pipeline = false; + + mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, [pipelineCopy]); + + createComponent(); + + await waitForPromises(); + + expect(findRunPipelineBtn().exists()).toBe(false); + expect(findRunPipelineBtnMobile().exists()).toBe(false); + }); + }); + + describe('on click', () => { + beforeEach(async () => { + pipelineCopy.flags.detached_merge_request_pipeline = true; + + mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, [pipelineCopy]); + + createComponent({ + props: { + canRunPipeline: true, + projectId: '5', + mergeRequestId: 3, + }, + }); + + await waitForPromises(); + }); + describe('success', () => { + beforeEach(() => { + jest.spyOn(Api, 'postMergeRequestPipeline').mockResolvedValue(); + }); + it('displays a toast message during pipeline creation', async () => { + await findRunPipelineBtn().trigger('click'); + + expect($toast.show).toHaveBeenCalledWith(TOAST_MESSAGE); + }); + + it('on desktop, shows a loading button', async () => { + await findRunPipelineBtn().trigger('click'); + + expect(findRunPipelineBtn().props('loading')).toBe(true); + + await waitForPromises(); + + expect(findRunPipelineBtn().props('loading')).toBe(false); + }); + + it('on mobile, shows a loading button', async () => { + await findRunPipelineBtnMobile().trigger('click'); + + expect(findRunPipelineBtn().props('loading')).toBe(true); + + await waitForPromises(); + + expect(findRunPipelineBtn().props('disabled')).toBe(false); + expect(findRunPipelineBtn().props('loading')).toBe(false); + }); + }); + + describe('failure', () => { + const permissionsMsg = 'You do not have permission to run a pipeline on this branch.'; + const defaultMsg = + 'An error occurred while trying to run a new pipeline for this merge request.'; + + it.each` + status | message + ${HTTP_STATUS_BAD_REQUEST} | ${defaultMsg} + ${HTTP_STATUS_UNAUTHORIZED} | ${permissionsMsg} + ${HTTP_STATUS_INTERNAL_SERVER_ERROR} | ${defaultMsg} + `('displays permissions error message', async ({ status, message }) => { + const response = { response: { status } }; + + jest.spyOn(Api, 'postMergeRequestPipeline').mockRejectedValue(response); + + await findRunPipelineBtn().trigger('click'); + + await waitForPromises(); + + expect(createAlert).toHaveBeenCalledWith({ + message, + primaryButton: { + text: 'Learn more', + link: '/help/ci/pipelines/merge_request_pipelines.md', + }, + }); + }); + }); + }); + + describe('on click for fork merge request', () => { + beforeEach(async () => { + pipelineCopy.flags.detached_merge_request_pipeline = true; + + mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, [pipelineCopy]); + + createComponent({ + props: { + projectId: '5', + mergeRequestId: 3, + canCreatePipelineInTargetProject: true, + sourceProjectFullPath: 'test/parent-project', + targetProjectFullPath: 'test/fork-project', + }, + }); + + jest.spyOn(Api, 'postMergeRequestPipeline').mockResolvedValue(); + + await waitForPromises(); + }); + + it('on desktop, shows a security warning modal', async () => { + await findRunPipelineBtn().trigger('click'); + + await nextTick(); + + expect(findModal()).not.toBeNull(); + }); + + it('on mobile, shows a security warning modal', async () => { + await findRunPipelineBtnMobile().trigger('click'); + + expect(findModal()).not.toBeNull(); + }); + }); + + describe('when no pipelines were created on a forked merge request', () => { + beforeEach(async () => { + mock.onGet('endpoint.json').reply(HTTP_STATUS_OK, []); + + createComponent({ + props: { + projectId: '5', + mergeRequestId: 3, + canCreatePipelineInTargetProject: true, + sourceProjectFullPath: 'test/parent-project', + targetProjectFullPath: 'test/fork-project', + }, + }); + + await waitForPromises(); + }); + + it('should show security modal from empty state run pipeline button', () => { + expect(findEmptyState().exists()).toBe(true); + expect(findModal().exists()).toBe(true); + + findRunPipelineBtn().trigger('click'); + + expect(showMock).toHaveBeenCalled(); + }); + }); + }); + + describe('unsuccessfull request', () => { + beforeEach(async () => { + mock.onGet('endpoint.json').reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, []); + + createComponent(); + + await waitForPromises(); + }); + + it('should render error state', () => { + expect(findErrorEmptyState().text()).toBe( + 'There was an error fetching the pipelines. Try again in a few moments or contact your support team.', + ); + }); + }); +}); |