Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/pipelines/graph/linked_pipeline_spec.js')
-rw-r--r--spec/frontend/pipelines/graph/linked_pipeline_spec.js336
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', () => {