diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-19 10:33:21 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-19 10:33:21 +0300 |
commit | 36a59d088eca61b834191dacea009677a96c052f (patch) | |
tree | e4f33972dab5d8ef79e3944a9f403035fceea43f /spec/frontend/pipelines/graph/linked_pipeline_spec.js | |
parent | a1761f15ec2cae7c7f7bbda39a75494add0dfd6f (diff) |
Add latest changes from gitlab-org/gitlab@15-0-stable-eev15.0.0-rc42
Diffstat (limited to 'spec/frontend/pipelines/graph/linked_pipeline_spec.js')
-rw-r--r-- | spec/frontend/pipelines/graph/linked_pipeline_spec.js | 336 |
1 files changed, 275 insertions, 61 deletions
diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js index d800a8c341e..06fd970778c 100644 --- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js +++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js @@ -1,11 +1,21 @@ -import { GlButton, GlLoadingIcon } from '@gitlab/ui'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { GlButton, GlLoadingIcon, GlTooltip } 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,30 @@ 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 findCancelButton = () => wrapper.findByLabelText('Cancel downstream pipeline'); + const findCardTooltip = () => wrapper.findComponent(GlTooltip); + const findDownstreamPipelineTitle = () => wrapper.findByTestId('downstream-title'); + const findExpandButton = () => wrapper.findByTestId('expand-pipeline-button'); 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 findPipelineLabel = () => wrapper.findByTestId('downstream-pipeline-label'); + const findPipelineLink = () => wrapper.findByTestId('pipelineLink'); + const findRetryButton = () => wrapper.findByLabelText('Retry downstream pipeline'); + + const createWrapper = ({ propsData, downstreamRetryAction = false }) => { + const mockApollo = createMockApollo(); + + wrapper = extendedWrapper( + mount(LinkedPipelineComponent, { + propsData, + provide: { + glFeatures: { + downstreamRetryAction, + }, + }, + apolloProvider: mockApollo, + }), + ); }; afterEach(() => { @@ -59,7 +77,7 @@ describe('Linked pipeline', () => { }; beforeEach(() => { - createWrapper(props); + createWrapper({ propsData: props }); }); it('should render the project name', () => { @@ -84,18 +102,13 @@ describe('Linked pipeline', () => { expect(wrapper.text()).toContain(`#${props.pipeline.id}`); }); - it('should correctly compute the tooltip text', () => { - expect(wrapper.vm.tooltipText).toContain(mockPipeline.project.name); - expect(wrapper.vm.tooltipText).toContain(mockPipeline.status.label); - expect(wrapper.vm.tooltipText).toContain(mockPipeline.sourceJob.name); - expect(wrapper.vm.tooltipText).toContain(mockPipeline.id); - }); + it('adds the card tooltip text to the DOM', () => { + expect(findCardTooltip().exists()).toBe(true); - it('should render the tooltip text as the title attribute', () => { - const titleAttr = findLinkedPipeline().attributes('title'); - - expect(titleAttr).toContain(mockPipeline.project.name); - expect(titleAttr).toContain(mockPipeline.status.label); + expect(findCardTooltip().text()).toContain(mockPipeline.project.name); + expect(findCardTooltip().text()).toContain(mockPipeline.status.label); + expect(findCardTooltip().text()).toContain(mockPipeline.sourceJob.name); + expect(findCardTooltip().text()).toContain(mockPipeline.id); }); it('should display multi-project label when pipeline project id is not the same as triggered pipeline project id', () => { @@ -105,7 +118,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,45 +136,246 @@ 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); + }); + + it('hides the card tooltip when the action button tooltip is hovered', async () => { + expect(findCardTooltip().exists()).toBe(true); + + await findRetryButton().trigger('mouseover'); + + expect(findCardTooltip().exists()).toBe(false); + }); + + 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); + }); + + it('hides the card tooltip when the action button tooltip is hovered', async () => { + expect(findCardTooltip().exists()).toBe(true); + + await findCancelButton().trigger('mouseover'); + + expect(findCardTooltip().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); + }); + }); }); }); describe('expand button', () => { it.each` - pipelineType | anglePosition | borderClass | expanded - ${downstreamProps} | ${'angle-right'} | ${'gl-border-l-1!'} | ${false} - ${downstreamProps} | ${'angle-left'} | ${'gl-border-l-1!'} | ${true} - ${upstreamProps} | ${'angle-left'} | ${'gl-border-r-1!'} | ${false} - ${upstreamProps} | ${'angle-right'} | ${'gl-border-r-1!'} | ${true} + pipelineType | anglePosition | buttonBorderClasses | expanded + ${downstreamProps} | ${'angle-right'} | ${'gl-border-l-0!'} | ${false} + ${downstreamProps} | ${'angle-left'} | ${'gl-border-l-0!'} | ${true} + ${upstreamProps} | ${'angle-left'} | ${'gl-border-r-0!'} | ${false} + ${upstreamProps} | ${'angle-right'} | ${'gl-border-r-0!'} | ${true} `( - '$pipelineType.columnTitle pipeline button icon should be $anglePosition with $borderClass if expanded state is $expanded', - ({ pipelineType, anglePosition, borderClass, expanded }) => { - createWrapper({ ...pipelineType, expanded }); + '$pipelineType.columnTitle pipeline button icon should be $anglePosition with $buttonBorderClasses if expanded state is $expanded', + ({ pipelineType, anglePosition, buttonBorderClasses, expanded }) => { + createWrapper({ propsData: { ...pipelineType, expanded } }); expect(findExpandButton().props('icon')).toBe(anglePosition); - expect(findExpandButton().classes()).toContain(borderClass); + expect(findExpandButton().classes()).toContain(buttonBorderClasses); }, ); }); @@ -176,7 +390,7 @@ describe('Linked pipeline', () => { }; beforeEach(() => { - createWrapper(props); + createWrapper({ propsData: props }); }); it('loading icon is visible', () => { @@ -194,7 +408,7 @@ describe('Linked pipeline', () => { }; beforeEach(() => { - createWrapper(props); + createWrapper({ propsData: props }); }); it('emits `pipelineClicked` event', () => { |