diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-22 00:08:43 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-22 00:08:43 +0300 |
commit | 6216eabd1c94ac7e560b4f43b164bff73e17b0da (patch) | |
tree | e4c4747a2474f4575732f11d023fc411dec2dba2 /spec/frontend/pipelines/graph | |
parent | 2c87a975e36acc152da28665d39ca720f479fe53 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/pipelines/graph')
5 files changed, 298 insertions, 44 deletions
diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js index cb7073fb5f5..49d64c6eac0 100644 --- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js @@ -16,7 +16,7 @@ import { } from '~/performance/constants'; import * as perfUtils from '~/performance/utils'; import { - IID_FAILURE, + ACTION_FAILURE, LAYER_VIEW, STAGE_VIEW, VIEW_TYPE_KEY, @@ -188,7 +188,9 @@ describe('Pipeline graph wrapper', () => { it('displays the no iid alert', () => { expect(getAlert().exists()).toBe(true); - expect(getAlert().text()).toBe(wrapper.vm.$options.errorTexts[IID_FAILURE]); + expect(getAlert().text()).toBe( + 'The data in this pipeline is too old to be rendered as a graph. Please check the Jobs tab to access historical data.', + ); }); it('does not display the graph', () => { @@ -196,6 +198,27 @@ describe('Pipeline graph wrapper', () => { }); }); + describe('when there is an error with an action in the graph', () => { + beforeEach(async () => { + createComponentWithApollo(); + await waitForPromises(); + await getGraph().vm.$emit('error', { type: ACTION_FAILURE }); + }); + + it('does not display the loading icon', () => { + expect(getLoadingIcon().exists()).toBe(false); + }); + + it('displays the action error alert', () => { + expect(getAlert().exists()).toBe(true); + expect(getAlert().text()).toBe('An error occurred while performing this action.'); + }); + + it('displays the graph', () => { + expect(getGraph().exists()).toBe(true); + }); + }); + describe('when refresh action is emitted', () => { beforeEach(async () => { createComponentWithApollo(); diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js index d800a8c341e..9d420816b47 100644 --- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js +++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js @@ -1,11 +1,21 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; import { GlButton, GlLoadingIcon } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; -import { UPSTREAM, DOWNSTREAM } from '~/pipelines/components/graph/constants'; +import { ACTION_FAILURE, UPSTREAM, DOWNSTREAM } from '~/pipelines/components/graph/constants'; import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue'; +import { PIPELINE_GRAPHQL_TYPE } from '~/pipelines/constants'; +import CancelPipelineMutation from '~/pipelines/graphql/mutations/cancel_pipeline.mutation.graphql'; +import RetryPipelineMutation from '~/pipelines/graphql/mutations/retry_pipeline.mutation.graphql'; import CiStatus from '~/vue_shared/components/ci_icon.vue'; import mockPipeline from './linked_pipelines_mock_data'; +Vue.use(VueApollo); + describe('Linked pipeline', () => { let wrapper; @@ -27,22 +37,29 @@ describe('Linked pipeline', () => { }; const findButton = () => wrapper.find(GlButton); - const findDownstreamPipelineTitle = () => wrapper.find('[data-testid="downstream-title"]'); - const findPipelineLabel = () => wrapper.find('[data-testid="downstream-pipeline-label"]'); + const findRetryButton = () => wrapper.findByLabelText('Retry downstream pipeline'); + const findCancelButton = () => wrapper.findByLabelText('Cancel downstream pipeline'); + const findDownstreamPipelineTitle = () => wrapper.findByTestId('downstream-title'); + const findPipelineLabel = () => wrapper.findByTestId('downstream-pipeline-label'); const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' }); const findLoadingIcon = () => wrapper.find(GlLoadingIcon); - const findPipelineLink = () => wrapper.find('[data-testid="pipelineLink"]'); - const findExpandButton = () => wrapper.find('[data-testid="expand-pipeline-button"]'); - - const createWrapper = (propsData, data = []) => { - wrapper = mount(LinkedPipelineComponent, { - propsData, - data() { - return { - ...data, - }; - }, - }); + const findPipelineLink = () => wrapper.findByTestId('pipelineLink'); + const findExpandButton = () => wrapper.findByTestId('expand-pipeline-button'); + + const createWrapper = ({ propsData, downstreamRetryAction = false }) => { + const mockApollo = createMockApollo(); + + wrapper = extendedWrapper( + mount(LinkedPipelineComponent, { + propsData, + provide: { + glFeatures: { + downstreamRetryAction, + }, + }, + apolloProvider: mockApollo, + }), + ); }; afterEach(() => { @@ -59,7 +76,7 @@ describe('Linked pipeline', () => { }; beforeEach(() => { - createWrapper(props); + createWrapper({ propsData: props }); }); it('should render the project name', () => { @@ -105,7 +122,7 @@ describe('Linked pipeline', () => { describe('upstream pipelines', () => { beforeEach(() => { - createWrapper(upstreamProps); + createWrapper({ propsData: upstreamProps }); }); it('should display parent label when pipeline project id is the same as triggered_by pipeline project id', () => { @@ -123,29 +140,214 @@ describe('Linked pipeline', () => { }); describe('downstream pipelines', () => { - beforeEach(() => { - createWrapper(downstreamProps); - }); - - it('parent/child label container should exist', () => { - expect(findPipelineLabel().exists()).toBe(true); - }); - - it('should display child label when pipeline project id is the same as triggered pipeline project id', () => { - expect(findPipelineLabel().exists()).toBe(true); - }); - - it('should have the name of the trigger job on the card when it is a child pipeline', () => { - expect(findDownstreamPipelineTitle().text()).toBe(mockPipeline.sourceJob.name); - }); - - it('downstream pipeline should contain the correct link', () => { - expect(findPipelineLink().attributes('href')).toBe(downstreamProps.pipeline.path); + describe('styling', () => { + beforeEach(() => { + createWrapper({ propsData: downstreamProps }); + }); + + it('parent/child label container should exist', () => { + expect(findPipelineLabel().exists()).toBe(true); + }); + + it('should display child label when pipeline project id is the same as triggered pipeline project id', () => { + expect(findPipelineLabel().exists()).toBe(true); + }); + + it('should have the name of the trigger job on the card when it is a child pipeline', () => { + expect(findDownstreamPipelineTitle().text()).toBe(mockPipeline.sourceJob.name); + }); + + it('downstream pipeline should contain the correct link', () => { + expect(findPipelineLink().attributes('href')).toBe(downstreamProps.pipeline.path); + }); + + it('applies the flex-row css class to the card', () => { + expect(findLinkedPipeline().classes()).toContain('gl-flex-direction-row'); + expect(findLinkedPipeline().classes()).not.toContain('gl-flex-direction-row-reverse'); + }); }); - it('applies the flex-row css class to the card', () => { - expect(findLinkedPipeline().classes()).toContain('gl-flex-direction-row'); - expect(findLinkedPipeline().classes()).not.toContain('gl-flex-direction-row-reverse'); + describe('action button', () => { + describe('with the `downstream_retry_action` flag on', () => { + describe('with permissions', () => { + describe('on an upstream', () => { + describe('when retryable', () => { + beforeEach(() => { + const retryablePipeline = { + ...upstreamProps, + pipeline: { ...mockPipeline, retryable: true }, + }; + + createWrapper({ propsData: retryablePipeline, downstreamRetryAction: true }); + }); + + it('does not show the retry or cancel button', () => { + expect(findCancelButton().exists()).toBe(false); + expect(findRetryButton().exists()).toBe(false); + }); + }); + }); + + describe('on a downstream', () => { + describe('when retryable', () => { + beforeEach(() => { + const retryablePipeline = { + ...downstreamProps, + pipeline: { ...mockPipeline, retryable: true }, + }; + + createWrapper({ propsData: retryablePipeline, downstreamRetryAction: true }); + }); + + it('shows only the retry button', () => { + expect(findCancelButton().exists()).toBe(false); + expect(findRetryButton().exists()).toBe(true); + }); + + describe('and the retry button is clicked', () => { + describe('on success', () => { + beforeEach(async () => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(); + jest.spyOn(wrapper.vm, '$emit'); + await findRetryButton().trigger('click'); + }); + + it('calls the retry mutation ', () => { + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1); + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation: RetryPipelineMutation, + variables: { + id: convertToGraphQLId(PIPELINE_GRAPHQL_TYPE, mockPipeline.id), + }, + }); + }); + + it('emits the refreshPipelineGraph event', () => { + expect(wrapper.vm.$emit).toHaveBeenCalledWith('refreshPipelineGraph'); + }); + }); + + describe('on failure', () => { + beforeEach(async () => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue({ errors: [] }); + jest.spyOn(wrapper.vm, '$emit'); + await findRetryButton().trigger('click'); + }); + + it('emits an error event', () => { + expect(wrapper.vm.$emit).toHaveBeenCalledWith('error', { + type: ACTION_FAILURE, + }); + }); + }); + }); + }); + + describe('when cancelable', () => { + beforeEach(() => { + const cancelablePipeline = { + ...downstreamProps, + pipeline: { ...mockPipeline, cancelable: true }, + }; + + createWrapper({ propsData: cancelablePipeline, downstreamRetryAction: true }); + }); + + it('shows only the cancel button ', () => { + expect(findCancelButton().exists()).toBe(true); + expect(findRetryButton().exists()).toBe(false); + }); + + describe('and the cancel button is clicked', () => { + describe('on success', () => { + beforeEach(async () => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(); + jest.spyOn(wrapper.vm, '$emit'); + await findCancelButton().trigger('click'); + }); + + it('calls the cancel mutation', () => { + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1); + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation: CancelPipelineMutation, + variables: { + id: convertToGraphQLId(PIPELINE_GRAPHQL_TYPE, mockPipeline.id), + }, + }); + }); + it('emits the refreshPipelineGraph event', () => { + expect(wrapper.vm.$emit).toHaveBeenCalledWith('refreshPipelineGraph'); + }); + }); + describe('on failure', () => { + beforeEach(async () => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue({ errors: [] }); + jest.spyOn(wrapper.vm, '$emit'); + await findCancelButton().trigger('click'); + }); + it('emits an error event', () => { + expect(wrapper.vm.$emit).toHaveBeenCalledWith('error', { + type: ACTION_FAILURE, + }); + }); + }); + }); + }); + + describe('when both cancellable and retryable', () => { + beforeEach(() => { + const pipelineWithTwoActions = { + ...downstreamProps, + pipeline: { ...mockPipeline, cancelable: true, retryable: true }, + }; + + createWrapper({ propsData: pipelineWithTwoActions, downstreamRetryAction: true }); + }); + + it('only shows the cancel button', () => { + expect(findRetryButton().exists()).toBe(false); + expect(findCancelButton().exists()).toBe(true); + }); + }); + }); + }); + + describe('without permissions', () => { + beforeEach(() => { + const pipelineWithTwoActions = { + ...downstreamProps, + pipeline: { + ...mockPipeline, + cancelable: true, + retryable: true, + userPermissions: { updatePipeline: false }, + }, + }; + + createWrapper({ propsData: pipelineWithTwoActions }); + }); + + it('does not show any action button', () => { + expect(findRetryButton().exists()).toBe(false); + expect(findCancelButton().exists()).toBe(false); + }); + }); + }); + + describe('with the `downstream_retry_action` flag off', () => { + beforeEach(() => { + const pipelineWithTwoActions = { + ...downstreamProps, + pipeline: { ...mockPipeline, cancelable: true, retryable: true }, + }; + + createWrapper({ propsData: pipelineWithTwoActions }); + }); + it('does not show any action button', () => { + expect(findRetryButton().exists()).toBe(false); + expect(findCancelButton().exists()).toBe(false); + }); + }); }); }); @@ -159,7 +361,7 @@ describe('Linked pipeline', () => { `( '$pipelineType.columnTitle pipeline button icon should be $anglePosition with $borderClass if expanded state is $expanded', ({ pipelineType, anglePosition, borderClass, expanded }) => { - createWrapper({ ...pipelineType, expanded }); + createWrapper({ propsData: { ...pipelineType, expanded } }); expect(findExpandButton().props('icon')).toBe(anglePosition); expect(findExpandButton().classes()).toContain(borderClass); }, @@ -176,7 +378,7 @@ describe('Linked pipeline', () => { }; beforeEach(() => { - createWrapper(props); + createWrapper({ propsData: props }); }); it('loading icon is visible', () => { @@ -194,7 +396,7 @@ describe('Linked pipeline', () => { }; beforeEach(() => { - createWrapper(props); + createWrapper({ propsData: props }); }); it('emits `pipelineClicked` event', () => { diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js index 1673065e09c..46000711110 100644 --- a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js +++ b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js @@ -67,7 +67,6 @@ describe('Linked Pipelines Column', () => { afterEach(() => { wrapper.destroy(); - wrapper = null; }); describe('it renders correctly', () => { diff --git a/spec/frontend/pipelines/graph/linked_pipelines_mock_data.js b/spec/frontend/pipelines/graph/linked_pipelines_mock_data.js index 955b70cbd3b..f7f5738e46d 100644 --- a/spec/frontend/pipelines/graph/linked_pipelines_mock_data.js +++ b/spec/frontend/pipelines/graph/linked_pipelines_mock_data.js @@ -2,6 +2,11 @@ export default { __typename: 'Pipeline', id: 195, iid: '5', + retryable: false, + cancelable: false, + userPermissions: { + updatePipeline: true, + }, path: '/root/elemenohpee/-/pipelines/195', status: { __typename: 'DetailedStatus', diff --git a/spec/frontend/pipelines/graph/mock_data.js b/spec/frontend/pipelines/graph/mock_data.js index 0cf7dc507f4..94915d4ce7b 100644 --- a/spec/frontend/pipelines/graph/mock_data.js +++ b/spec/frontend/pipelines/graph/mock_data.js @@ -699,6 +699,11 @@ export const downstream = { id: 175, iid: '31', path: '/root/elemenohpee/-/pipelines/175', + retryable: true, + cancelable: false, + userPermissions: { + updatePipeline: true, + }, status: { id: '70', group: 'success', @@ -724,6 +729,11 @@ export const downstream = { id: 181, iid: '27', path: '/root/abcd-dag/-/pipelines/181', + retryable: true, + cancelable: false, + userPermissions: { + updatePipeline: true, + }, status: { id: '72', group: 'success', @@ -752,6 +762,11 @@ export const upstream = { id: 161, iid: '24', path: '/root/abcd-dag/-/pipelines/161', + retryable: true, + cancelable: false, + userPermissions: { + updatePipeline: true, + }, status: { id: '74', group: 'success', @@ -786,6 +801,11 @@ export const wrappedPipelineReturn = { updatePipeline: true, }, downstream: { + retryable: true, + cancelable: false, + userPermissions: { + updatePipeline: true, + }, __typename: 'PipelineConnection', nodes: [], }, @@ -793,6 +813,11 @@ export const wrappedPipelineReturn = { id: 'gid://gitlab/Ci::Pipeline/174', iid: '37', path: '/root/elemenohpee/-/pipelines/174', + retryable: true, + cancelable: false, + userPermissions: { + updatePipeline: true, + }, __typename: 'Pipeline', status: { __typename: 'DetailedStatus', |