diff --git a/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue b/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue index 7beabcfe4035dfc63351f9795c37fd15b1436f77..3662100d5266c5ce8c3c932105ae02ace15788ca 100644 --- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue +++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_editor_mini_graph.vue @@ -10,8 +10,6 @@ export default { }, components: { PipelineMiniGraph, - LinkedPipelinesMiniList: () => - import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'), }, inject: ['projectFullPath'], props: { @@ -47,9 +45,6 @@ export default { downstreamPipelines() { return this.linkedPipelines?.downstream?.nodes || []; }, - hasDownstreamPipelines() { - return this.downstreamPipelines.length > 0; - }, hasPipelineStages() { return this.pipelineStages.length > 0; }, @@ -87,23 +82,11 @@ export default { </script> <template> - <div + <pipeline-mini-graph v-if="hasPipelineStages" - class="gl-align-items-center gl-display-inline-flex gl-flex-wrap stage-cell gl-mr-5" - > - <linked-pipelines-mini-list - v-if="upstreamPipeline" - :triggered-by="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [ - upstreamPipeline, - ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" - data-testid="pipeline-editor-mini-graph-upstream" - /> - <pipeline-mini-graph :stages="pipelineStages" /> - <linked-pipelines-mini-list - v-if="hasDownstreamPipelines" - :triggered="downstreamPipelines" - :pipeline-path="pipelinePath" - data-testid="pipeline-editor-mini-graph-downstream" - /> - </div> + :downstream-pipelines="downstreamPipelines" + :pipeline-path="pipelinePath" + :stages="pipelineStages" + :upstream-pipeline="upstreamPipeline" + /> </template> diff --git a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue index 4b9c98135ec60030d09a112b15a3be39c2887ef9..137dfca68d6d0873b87c6ee7314f3be46d20f79a 100644 --- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue +++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue @@ -174,7 +174,7 @@ export default { <div class="gl-display-flex gl-flex-wrap"> <pipeline-editor-mini-graph :pipeline="pipeline" v-on="$listeners" /> <gl-button - class="gl-mt-2 gl-md-mt-0" + class="gl-ml-3" category="secondary" variant="confirm" :href="status.detailsPath" diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue index 05cb2ebb769e6a0f8f0d62653f9ced09d4207229..f1edc3d4be8d73e382c6a51163ce079688906f2e 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue @@ -1,32 +1,60 @@ <script> -import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue'; +import { GlIcon } from '@gitlab/ui'; +import PipelineStages from '~/pipelines/components/pipelines_list/pipeline_stages.vue'; /** * Renders the pipeline mini graph. */ export default { components: { - PipelineStage, + GlIcon, + PipelineStages, + LinkedPipelinesMiniList: () => + import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'), }, + arrowStyles: [ + 'arrow-icon gl-display-inline-block gl-mx-1 gl-text-gray-500 gl-vertical-align-middle!', + ], props: { - stages: { + downstreamPipelines: { type: Array, - required: true, + required: false, + default: () => [], }, - updateDropdown: { + isMergeTrain: { type: Boolean, required: false, default: false, }, + pipelinePath: { + type: String, + required: false, + default: '', + }, + stages: { + type: Array, + required: true, + default: () => [], + }, stagesClass: { type: [Array, Object, String], required: false, default: '', }, - isMergeTrain: { + updateDropdown: { type: Boolean, required: false, default: false, }, + upstreamPipeline: { + type: Object, + required: false, + default: () => {}, + }, + }, + computed: { + hasDownstreamPipelines() { + return Boolean(this.downstreamPipelines.length); + }, }, methods: { onPipelineActionRequestComplete() { @@ -36,19 +64,39 @@ export default { }; </script> <template> - <div data-testid="pipeline-mini-graph" class="gl-display-inline gl-vertical-align-middle"> - <div - v-for="stage in stages" - :key="stage.name" - :class="stagesClass" - class="dropdown gl-display-inline-block gl-mr-2 gl-my-2 gl-vertical-align-middle stage-container" - > - <pipeline-stage - :stage="stage" - :update-dropdown="updateDropdown" - :is-merge-train="isMergeTrain" - @pipelineActionRequestComplete="onPipelineActionRequestComplete" - /> - </div> + <div class="stage-cell" data-testid="pipeline-mini-graph"> + <linked-pipelines-mini-list + v-if="upstreamPipeline" + :triggered-by="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [ + upstreamPipeline, + ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" + data-testid="pipeline-mini-graph-upstream" + /> + <gl-icon + v-if="upstreamPipeline" + :class="$options.arrowStyles" + name="long-arrow" + data-testid="upstream-arrow-icon" + /> + <pipeline-stages + :is-merge-train="isMergeTrain" + :stages="stages" + :update-dropdown="updateDropdown" + :stages-class="stagesClass" + data-testid="pipeline-stages" + @pipelineActionRequestComplete="onPipelineActionRequestComplete" + /> + <gl-icon + v-if="hasDownstreamPipelines" + :class="$options.arrowStyles" + name="long-arrow" + data-testid="downstream-arrow-icon" + /> + <linked-pipelines-mini-list + v-if="hasDownstreamPipelines" + :triggered="downstreamPipelines" + :pipeline-path="pipelinePath" + data-testid="pipeline-mini-graph-downstream" + /> </div> </template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stages.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stages.vue new file mode 100644 index 0000000000000000000000000000000000000000..f1923e94a477a3d282d159a8665ac7594988dde4 --- /dev/null +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stages.vue @@ -0,0 +1,54 @@ +<script> +import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue'; +/** + * Renders the pipeline stages portion of the pipeline mini graph. + */ +export default { + components: { + PipelineStage, + }, + props: { + stages: { + type: Array, + required: true, + }, + updateDropdown: { + type: Boolean, + required: false, + default: false, + }, + stagesClass: { + type: [Array, Object, String], + required: false, + default: '', + }, + isMergeTrain: { + type: Boolean, + required: false, + default: false, + }, + }, + methods: { + onPipelineActionRequestComplete() { + this.$emit('pipelineActionRequestComplete'); + }, + }, +}; +</script> +<template> + <div data-testid="pipeline-stages" class="gl-display-inline gl-vertical-align-middle"> + <div + v-for="stage in stages" + :key="stage.name" + :class="stagesClass" + class="dropdown gl-display-inline-block gl-mr-2 gl-my-2 gl-vertical-align-middle stage-container" + > + <pipeline-stage + :stage="stage" + :update-dropdown="updateDropdown" + :is-merge-train="isMergeTrain" + @pipelineActionRequestComplete="onPipelineActionRequestComplete" + /> + </div> + </div> +</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue index 53da98434b0d8c2a0bb7efbf3bf2188a059889c0..4046ee69428dec3f553080abfc9d67d1202ee6d7 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue @@ -17,8 +17,6 @@ const DEFAULT_TH_CLASSES = export default { components: { GlTableLite, - LinkedPipelinesMiniList: () => - import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'), PipelineMiniGraph, PipelineOperations, PipelinesStatusBadge, @@ -169,29 +167,14 @@ export default { </template> <template #cell(stages)="{ item }"> - <div class="stage-cell"> - <!-- This empty div should be removed, see https://gitlab.com/gitlab-org/gitlab/-/issues/323488 --> - <div></div> - <linked-pipelines-mini-list - v-if="item.triggered_by" - :triggered-by="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [ - item.triggered_by, - ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" - data-testid="mini-graph-upstream" - /> - <pipeline-mini-graph - v-if="item.details && item.details.stages && item.details.stages.length > 0" - :stages="item.details.stages" - :update-dropdown="updateGraphDropdown" - @pipelineActionRequestComplete="onPipelineActionRequestComplete" - /> - <linked-pipelines-mini-list - v-if="item.triggered.length" - :triggered="item.triggered" - :pipeline-path="item.path" - data-testid="mini-graph-downstream" - /> - </div> + <pipeline-mini-graph + :downstream-pipelines="item.triggered" + :pipeline-path="item.path" + :stages="item.details.stages" + :update-dropdown="updateGraphDropdown" + :upstream-pipeline="item.triggered_by" + @pipelineActionRequestComplete="onPipelineActionRequestComplete" + /> </template> <template #cell(actions)="{ item }"> diff --git a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue index 1cdf26b76b752e9f88ee7efdc4cea1f6b3e69b67..a4044106a531b2d85cee27c0dd982f69b8b18ed5 100644 --- a/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue +++ b/app/assets/javascripts/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue @@ -2,11 +2,11 @@ import { GlLoadingIcon } from '@gitlab/ui'; import createFlash from '~/flash'; import { __ } from '~/locale'; -import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; import { getQueryHeaders, toggleQueryPollingByVisibility, } from '~/pipelines/components/graph/utils'; +import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; import { formatStages } from '../utils'; import getLinkedPipelinesQuery from '../graphql/queries/get_linked_pipelines.query.graphql'; import getPipelineStagesQuery from '../graphql/queries/get_pipeline_stages.query.graphql'; @@ -21,8 +21,6 @@ export default { components: { GlLoadingIcon, PipelineMiniGraph, - LinkedPipelinesMiniList: () => - import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'), }, inject: { fullPath: { @@ -92,12 +90,12 @@ export default { }; }, computed: { - hasDownstream() { - return this.pipeline?.downstream?.nodes.length > 0; - }, downstreamPipelines() { return this.pipeline?.downstream?.nodes; }, + pipelinePath() { + return this.pipeline?.path ?? ''; + }, upstreamPipeline() { return this.pipeline?.upstream; }, @@ -128,23 +126,13 @@ export default { <template> <div class="gl-pt-2"> <gl-loading-icon v-if="$apollo.queries.pipeline.loading" /> - <div v-else class="gl-align-items-center gl-display-flex"> - <linked-pipelines-mini-list - v-if="upstreamPipeline" - :triggered-by="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [ - upstreamPipeline, - ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" - data-testid="commit-box-mini-graph-upstream" - /> - - <pipeline-mini-graph :stages="formattedStages" data-testid="commit-box-mini-graph" /> - - <linked-pipelines-mini-list - v-if="hasDownstream" - :triggered="downstreamPipelines" - :pipeline-path="pipeline.path" - data-testid="commit-box-mini-graph-downstream" - /> - </div> + <pipeline-mini-graph + v-else + data-testid="commit-box-pipeline-mini-graph" + :downstream-pipelines="downstreamPipelines" + :pipeline-path="pipelinePath" + :stages="formattedStages" + :upstream-pipeline="upstreamPipeline" + /> </div> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue index 1e1a20494145715440559ba2e9c5815b47ac52b5..5ecf49b51be8112b85ccd5637d5927a0f279819b 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue @@ -11,8 +11,8 @@ import { } from '@gitlab/ui'; import mrWidgetPipelineMixin from 'ee_else_ce/vue_merge_request_widget/mixins/mr_widget_pipeline'; import { s__, n__ } from '~/locale'; -import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; import PipelineArtifacts from '~/pipelines/components/pipelines_list/pipelines_artifacts.vue'; +import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; @@ -31,8 +31,6 @@ export default { PipelineMiniGraph, TimeAgoTooltip, TooltipOnTruncate, - LinkedPipelinesMiniList: () => - import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'), }, directives: { GlTooltip: GlTooltipDirective, @@ -276,17 +274,15 @@ export default { </div> </div> <div> - <span class="gl-align-items-center gl-display-inline-flex mr-widget-pipeline-graph"> - <span class="gl-align-items-center gl-display-inline-flex gl-flex-wrap stage-cell"> - <linked-pipelines-mini-list v-if="triggeredBy.length" :triggered-by="triggeredBy" /> - <pipeline-mini-graph - v-if="hasStages" - stages-class="mr-widget-pipeline-stages" - :stages="pipeline.details.stages" - :is-merge-train="isMergeTrain" - /> - </span> - <linked-pipelines-mini-list v-if="triggered.length" :triggered="triggered" /> + <span class="gl-align-items-center gl-display-inline-flex"> + <pipeline-mini-graph + v-if="pipeline.details.stages" + :downstream-pipelines="triggered" + :is-merge-train="isMergeTrain" + :stages="pipeline.details.stages" + :upstream-pipeline="triggeredBy[0]" + stages-class="mr-widget-pipeline-stages" + /> <pipeline-artifacts :pipeline-id="pipeline.id" :artifacts="artifacts" class="gl-ml-3" /> </span> </div> diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss index 14873c54cd77d8f6cddbf0ed82b76811ef1c737c..269afd01615e32e5a7f6f178b9cbeaecb8b776e9 100644 --- a/app/assets/stylesheets/page_bundles/merge_requests.scss +++ b/app/assets/stylesheets/page_bundles/merge_requests.scss @@ -400,12 +400,6 @@ $tabs-holder-z-index: 250; display: block; } - .mr-widget-pipeline-graph { - .dropdown-menu { - z-index: $zindex-dropdown-menu; - } - } - .normal { flex: 1; flex-basis: auto; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index c96d8ecc782ee805b286ed6c1c325846f8f47688..19318d8773127c63d7052430852adda1b6beab41 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -33,12 +33,6 @@ height: 22px; } } - - .mr-widget-pipeline-graph { - .dropdown-menu { - margin-top: 11px; - } - } } .branch-info .commit-icon { diff --git a/ee/app/assets/javascripts/vue_shared/components/linked_pipelines_mini_list.vue b/ee/app/assets/javascripts/vue_shared/components/linked_pipelines_mini_list.vue index 4ebe9539411cb469eae60a8908ef311b9f16729e..4d180dfcd824905ca818d51914257c944e64134a 100644 --- a/ee/app/assets/javascripts/vue_shared/components/linked_pipelines_mini_list.vue +++ b/ee/app/assets/javascripts/vue_shared/components/linked_pipelines_mini_list.vue @@ -1,20 +1,18 @@ <script> -import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; +import { GlTooltipDirective } from '@gitlab/ui'; import { sprintf, s__ } from '~/locale'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; import { accessValue } from '../accessors/linked_pipelines_accessors'; - +/** + * Renders the upstream/downstream portions of the pipeline mini graph. + */ export default { directives: { GlTooltip: GlTooltipDirective, }, components: { CiIcon, - GlIcon, }, - arrowStyles: [ - 'arrow-icon gl-display-inline-block gl-mx-1 gl-text-gray-500 gl-vertical-align-middle!', - ], inject: { dataMethod: { default: 'rest', @@ -101,8 +99,6 @@ export default { }" class="linked-pipeline-mini-list gl-display-inline gl-vertical-align-middle" > - <gl-icon v-if="isDownstream" :class="$options.arrowStyles" name="long-arrow" /> - <a v-for="pipeline in linkedPipelinesTrimmed" :key="pipeline.id" @@ -132,7 +128,5 @@ export default { > {{ counterLabel }} </a> - - <gl-icon v-if="isUpstream" :class="$options.arrowStyles" name="long-arrow" /> </span> </template> diff --git a/ee/spec/frontend/commit_box/components/commit_box_pipeline_mini_graph_spec.js b/ee/spec/frontend/commit_box/components/commit_box_pipeline_mini_graph_spec.js index ceb43768657a0bbe134c0e71c932d7093d524d2d..4dd6e9e54867aa553ddfd668dc71f05424f4a384 100644 --- a/ee/spec/frontend/commit_box/components/commit_box_pipeline_mini_graph_spec.js +++ b/ee/spec/frontend/commit_box/components/commit_box_pipeline_mini_graph_spec.js @@ -7,6 +7,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import createFlash from '~/flash'; import CommitBoxPipelineMiniGraph from '~/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue'; +import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; import { COMMIT_BOX_POLL_INTERVAL } from '~/projects/commit_box/info/constants'; import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql'; import getPipelineStagesQuery from '~/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql'; @@ -37,9 +38,7 @@ describe('Commit box pipeline mini graph', () => { const stagesHandler = jest.fn().mockResolvedValue(mockPipelineStagesQueryResponse); const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); - const findMiniGraph = () => wrapper.findByTestId('commit-box-mini-graph'); - const findUpstream = () => wrapper.findByTestId('commit-box-mini-graph-upstream'); - const findDownstream = () => wrapper.findByTestId('commit-box-mini-graph-downstream'); + const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph); const advanceToNextFetch = () => { jest.advanceTimersByTime(COMMIT_BOX_POLL_INTERVAL); @@ -80,18 +79,25 @@ describe('Commit box pipeline mini graph', () => { createComponent(); expect(findLoadingIcon().exists()).toBe(true); - expect(findMiniGraph().exists()).toBe(false); + expect(findPipelineMiniGraph().exists()).toBe(false); }); }); describe('loaded state', () => { + const samplePipeline = { + id: expect.any(String), + path: expect.any(String), + project: expect.any(Object), + detailedStatus: expect.any(Object), + }; + it('should not display loading state after the query is resolved', async () => { createComponent(); await waitForPromises(); expect(findLoadingIcon().exists()).toBe(false); - expect(findMiniGraph().exists()).toBe(true); + expect(findPipelineMiniGraph().exists()).toBe(true); }); it('should pass the pipeline path prop for the counter badge', async () => { @@ -100,24 +106,45 @@ describe('Commit box pipeline mini graph', () => { await waitForPromises(); const expectedPath = mockDownstreamQueryResponse.data.project.pipeline.path; + const pipelinePath = findPipelineMiniGraph().props('pipelinePath'); - expect(findDownstream().props('pipelinePath')).toBe(expectedPath); + expect(pipelinePath).toBe(expectedPath); }); - describe.each` - handler | downstreamRenders | upstreamRenders - ${downstreamHandler} | ${true} | ${false} - ${upstreamHandler} | ${false} | ${true} - ${upstreamDownstreamHandler} | ${true} | ${true} - `('given a linked pipeline', ({ handler, downstreamRenders, upstreamRenders }) => { - it('should render the correct linked pipelines', async () => { - createComponent(handler); + it('should render a downstream pipeline only', async () => { + createComponent(downstreamHandler); + + await waitForPromises(); - await waitForPromises(); + const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines'); + const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline'); - expect(findDownstream().exists()).toBe(downstreamRenders); - expect(findUpstream().exists()).toBe(upstreamRenders); - }); + expect(downstreamPipelines).toEqual(expect.any(Array)); + expect(upstreamPipeline).toEqual(null); + }); + + it('should render an upstream pipeline only', async () => { + createComponent(upstreamHandler); + + await waitForPromises(); + + const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines'); + const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline'); + + expect(upstreamPipeline).toEqual(samplePipeline); + expect(downstreamPipelines).toHaveLength(0); + }); + + it('should render downstream and upstream pipelines', async () => { + createComponent(upstreamDownstreamHandler); + + await waitForPromises(); + + const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines'); + const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline'); + + expect(upstreamPipeline).toEqual(samplePipeline); + expect(downstreamPipelines).toEqual(expect.arrayContaining([samplePipeline])); }); it('formatted stages should be passed to the pipeline mini graph', async () => { @@ -139,7 +166,7 @@ describe('Commit box pipeline mini graph', () => { await waitForPromises(); - expect(findMiniGraph().props('stages')).toEqual(expectedStages); + expect(findPipelineMiniGraph().props('stages')).toEqual(expectedStages); }); }); diff --git a/ee/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js b/ee/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js index cd4910e77411ebea0bb74b1f9931e0475bf12b13..c6700a74e34fa164c76527de9ad4ac3d493aab5f 100644 --- a/ee/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js +++ b/ee/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js @@ -1,9 +1,9 @@ import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; -import waitForPromises from 'helpers/wait_for_promises'; import createMockApollo from 'helpers/mock_apollo_helper'; import PipelineEditorMiniGraph from '~/pipeline_editor/components/header/pipeline_editor_mini_graph.vue'; +import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql'; import { mockLinkedPipelines, mockProjectFullPath, mockProjectPipeline } from '../../mock_data'; @@ -39,9 +39,7 @@ describe('Pipeline Status', () => { }); }; - const findUpstream = () => wrapper.find('[data-testid="pipeline-editor-mini-graph-upstream"]'); - const findDownstream = () => - wrapper.find('[data-testid="pipeline-editor-mini-graph-downstream"]'); + const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph); beforeEach(() => { mockLinkedPipelinesQuery = jest.fn(); @@ -52,33 +50,31 @@ describe('Pipeline Status', () => { wrapper.destroy(); }); - describe('when querying upstream and downstream pipelines', () => { - describe('when query succeeds', () => { + describe('when querying pipeline stages', () => { + describe('when query returns data', () => { beforeEach(() => { mockLinkedPipelinesQuery.mockResolvedValue(mockLinkedPipelines()); createComponentWithApollo(); }); - describe('linked pipeline rendering based on given data', () => { - it.each` - hasDownstream | hasUpstream | downstreamRenderAction | upstreamRenderAction - ${true} | ${true} | ${'renders'} | ${'renders'} - ${true} | ${false} | ${'renders'} | ${'hides'} - ${false} | ${true} | ${'hides'} | ${'renders'} - ${false} | ${false} | ${'hides'} | ${'hides'} - `( - '$downstreamRenderAction downstream and $upstreamRenderAction upstream', - async ({ hasDownstream, hasUpstream }) => { - mockLinkedPipelinesQuery.mockResolvedValue( - mockLinkedPipelines({ hasDownstream, hasUpstream }), - ); - createComponentWithApollo(); - await waitForPromises(); + describe('pipeline mini graph rendering based on given data', () => { + it('renders pipeline mini graph', () => { + expect(findPipelineMiniGraph().exists()).toBe(true); + }); + }); + }); + + describe('when query returns no data', () => { + beforeEach(() => { + mockLinkedPipelinesQuery.mockResolvedValue(mockLinkedPipelines()); + const hasStages = false; + createComponentWithApollo(hasStages); + }); - expect(findUpstream().exists()).toBe(hasUpstream); - expect(findDownstream().exists()).toBe(hasDownstream); - }, - ); + describe('pipeline mini graph rendering based on given data', () => { + it('does not render pipeline mini graph', () => { + expect(findPipelineMiniGraph().exists()).toBe(false); + }); }); }); }); diff --git a/ee/spec/frontend/pipelines/pipelines_table_spec.js b/ee/spec/frontend/pipelines/pipelines_table_spec.js index 5ce7f28dee1939be8e255ba0e6045f421c4d5562..b17ab954c568be52bbda044c91f0ce8f7ab6cc5a 100644 --- a/ee/spec/frontend/pipelines/pipelines_table_spec.js +++ b/ee/spec/frontend/pipelines/pipelines_table_spec.js @@ -2,6 +2,7 @@ import { mount } from '@vue/test-utils'; import fixture from 'test_fixtures/pipelines/pipelines.json'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue'; +import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; import { PipelineKeyOptions } from '~/pipelines/constants'; import { triggeredBy, triggered } from './mock_data'; @@ -35,8 +36,7 @@ describe('Pipelines Table', () => { ); }; - const findUpstream = () => wrapper.findByTestId('mini-graph-upstream'); - const findDownstream = () => wrapper.findByTestId('mini-graph-downstream'); + const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph); beforeEach(() => { pipeline = createMockPipeline(); @@ -47,6 +47,19 @@ describe('Pipelines Table', () => { }); describe('Pipelines Table', () => { + describe('pipeline mini graph', () => { + beforeEach(() => { + pipeline = createMockPipeline(); + pipeline.triggered_by = triggeredBy; + + createComponent({ pipelines: [pipeline] }); + }); + + it('should render a pipeline mini graph', () => { + expect(findPipelineMiniGraph().exists()).toBe(true); + }); + }); + describe('upstream linked pipelines', () => { beforeEach(() => { pipeline = createMockPipeline(); @@ -56,16 +69,17 @@ describe('Pipelines Table', () => { }); it('should render only a upstream pipeline', () => { - expect(findUpstream().exists()).toBe(true); - expect(findDownstream().exists()).toBe(false); + const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline'); + const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines'); + + expect(upstreamPipeline).toEqual(expect.any(Object)); + expect(downstreamPipelines).toHaveLength(0); }); - it('should pass an array of the correct data to the linked pipeline component', () => { - const triggeredByProps = findUpstream().props('triggeredBy'); + it('should pass an object of the correct data to the linked pipeline component', () => { + const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline'); - expect(triggeredByProps).toEqual(expect.any(Array)); - expect(triggeredByProps).toHaveLength(1); - expect(triggeredByProps[0]).toBe(triggeredBy); + expect(upstreamPipeline).toBe(triggeredBy); }); }); @@ -78,12 +92,22 @@ describe('Pipelines Table', () => { }); it('should pass the pipeline path prop for the counter badge', () => { - expect(findDownstream().props('pipelinePath')).toBe(pipeline.path); + const pipelinePath = findPipelineMiniGraph().props('pipelinePath'); + expect(pipelinePath).toBe(pipeline.path); }); it('should render only a downstream pipeline', () => { - expect(findDownstream().exists()).toBe(true); - expect(findUpstream().exists()).toBe(false); + const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline'); + const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines'); + + expect(downstreamPipelines).toEqual(expect.any(Array)); + expect(upstreamPipeline).toEqual(null); + }); + + it('should pass an array of the correct data to the linked pipeline component', () => { + const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines'); + + expect(downstreamPipelines).toEqual(triggered); }); }); @@ -97,8 +121,11 @@ describe('Pipelines Table', () => { }); it('should render both downstream and upstream pipelines', () => { - expect(findDownstream().exists()).toBe(true); - expect(findUpstream().exists()).toBe(true); + const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline'); + const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines'); + + expect(downstreamPipelines).toEqual(triggered); + expect(upstreamPipeline).toEqual(triggeredBy); }); }); }); diff --git a/ee/spec/frontend/vue_mr_widget/mr_widget_pipeline_spec.js b/ee/spec/frontend/vue_mr_widget/mr_widget_pipeline_spec.js index 8c35938fcb9c875363572cfa4ec64b53dbc6c925..7b87df4d5cf3a55b7564e51f90697ecf44d9e5aa 100644 --- a/ee/spec/frontend/vue_mr_widget/mr_widget_pipeline_spec.js +++ b/ee/spec/frontend/vue_mr_widget/mr_widget_pipeline_spec.js @@ -1,14 +1,14 @@ import { shallowMount } from '@vue/test-utils'; -import LinkedPipelinesMiniList from 'ee/vue_shared/components/linked_pipelines_mini_list.vue'; import mockData from 'ee_jest/vue_mr_widget/mock_data'; import MrWidgetPipeline from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue'; +import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; import mockLinkedPipelines from '../vue_shared/components/linked_pipelines_mock_data'; describe('MRWidgetPipeline', () => { let wrapper; const findPipelineInfoContainer = () => wrapper.find('[data-testid="pipeline-info-container"'); - const findPipelinesMiniList = () => wrapper.findComponent(LinkedPipelinesMiniList); + const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph); const createWrapper = (props) => { wrapper = shallowMount(MrWidgetPipeline, { @@ -75,20 +75,14 @@ describe('MRWidgetPipeline', () => { createWrapper({ pipeline }); }); - it('should render the linked pipelines mini list', () => { - expect(findPipelinesMiniList().exists()).toBe(true); + it('should render the pipeline mini graph', () => { + expect(findPipelineMiniGraph().exists()).toBe(true); }); - it('should render the linked pipelines mini list as an upstream list', () => { - expect(findPipelinesMiniList().classes('is-upstream')).toBe(true); - }); - - it('should add a single triggeredBy into an array', () => { - const triggeredBy = findPipelinesMiniList().props('triggeredBy'); + it('should send upstream pipeline', () => { + const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline'); - expect(triggeredBy).toEqual(expect.any(Array)); - expect(triggeredBy).toHaveLength(1); - expect(triggeredBy[0]).toBe(mockLinkedPipelines.triggered_by); + expect(upstreamPipeline).toBe(mockLinkedPipelines.triggered_by); }); }); @@ -99,18 +93,14 @@ describe('MRWidgetPipeline', () => { createWrapper({ pipeline }); }); - it('should render the linked pipelines mini list', () => { - expect(findPipelinesMiniList().exists()).toBe(true); + it('should render the pipeline mini graph', () => { + expect(findPipelineMiniGraph().exists()).toBe(true); }); it('should render the linked pipelines mini list as a downstream list', () => { - expect(findPipelinesMiniList().classes('is-downstream')).toBe(true); - }); - - it('should pass the triggered pipelines', () => { - const triggered = findPipelinesMiniList().props('triggered'); + const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines'); - expect(triggered).toBe(mockLinkedPipelines.triggered); + expect(downstreamPipelines).toBe(mockLinkedPipelines.triggered); }); }); }); diff --git a/ee/spec/frontend/vue_shared/components/linked_pipelines_mini_list_spec.js b/ee/spec/frontend/vue_shared/components/linked_pipelines_mini_list_spec.js index 3cebe451ceb5224a281742cd2a5c88bfe897b920..8896faf5c34b28c5340da867cd378949d47262e4 100644 --- a/ee/spec/frontend/vue_shared/components/linked_pipelines_mini_list_spec.js +++ b/ee/spec/frontend/vue_shared/components/linked_pipelines_mini_list_spec.js @@ -7,7 +7,6 @@ import mockData from './linked_pipelines_mock_data'; describe('Linked pipeline mini list', () => { let wrapper; - const findArrowIcon = () => wrapper.find('[data-testid="long-arrow-icon"]'); const findCiIcon = () => wrapper.findComponent(CiIcon); const findCiIcons = () => wrapper.findAllComponents(CiIcon); const findLinkedPipelineCounter = () => wrapper.find('[data-testid="linked-pipeline-counter"]'); @@ -73,13 +72,6 @@ describe('Linked pipeline mini list', () => { expect(findCiIcon().classes('ci-status-icon-running')).toBe(true); }); - it('should render an arrow icon', () => { - expect(findArrowIcon().exists()).toBe(true); - - expect(findArrowIcon().props('name')).toBe('long-arrow'); - expect(findArrowIcon().classes('arrow-icon')).toBe(true); - }); - it('should have an activated tooltip', () => { expect(findLinkedPipelineMiniItem().exists()).toBe(true); const tooltip = getBinding(findLinkedPipelineMiniItem().element, 'gl-tooltip'); @@ -129,13 +121,6 @@ describe('Linked pipeline mini list', () => { expect(findCiIcon().classes('ci-status-icon-running')).toBe(true); }); - it('should render an arrow icon', () => { - expect(findArrowIcon().exists()).toBe(true); - - expect(findArrowIcon().props('name')).toBe('long-arrow'); - expect(findArrowIcon().classes('arrow-icon')).toBe(true); - }); - it('should have an activated tooltip', () => { expect(findLinkedPipelineMiniItem().exists()).toBe(true); const tooltip = getBinding(findLinkedPipelineMiniItem().element, 'gl-tooltip'); diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb index e472cff38cefb34918d88b1793d1ca2995b9626f..4740f6e19fe57248dab609c1367ba85fc58b055e 100644 --- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb +++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb @@ -27,7 +27,7 @@ end it 'displays a mini pipeline graph' do - expect(page).to have_selector('[data-testid="commit-box-mini-graph"]') + expect(page).to have_selector('[data-testid="commit-box-pipeline-mini-graph"]') first('[data-testid="mini-pipeline-graph-dropdown"]').click diff --git a/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js b/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js index b1c8ba48475f7358a18a6227f5e40e194d6993cd..ab5055de5e3fd1c02d7f16f58501ee623bbbb0ff 100644 --- a/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js +++ b/spec/frontend/commit/commit_box_pipeline_mini_graph_spec.js @@ -6,6 +6,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import createFlash from '~/flash'; import CommitBoxPipelineMiniGraph from '~/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue'; +import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql'; import getPipelineStagesQuery from '~/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql'; import { mockPipelineStagesQueryResponse, mockStages } from './mock_data'; @@ -17,9 +18,7 @@ Vue.use(VueApollo); describe('Commit box pipeline mini graph', () => { let wrapper; - const findMiniGraph = () => wrapper.findByTestId('commit-box-mini-graph'); - const findUpstream = () => wrapper.findByTestId('commit-box-mini-graph-upstream'); - const findDownstream = () => wrapper.findByTestId('commit-box-mini-graph-downstream'); + const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph); const stagesHandler = jest.fn().mockResolvedValue(mockPipelineStagesQueryResponse); @@ -51,13 +50,16 @@ describe('Commit box pipeline mini graph', () => { await createComponent(); }); - it('should display the mini pipeine graph', () => { - expect(findMiniGraph().exists()).toBe(true); + it('should display the pipeline mini graph', () => { + expect(findPipelineMiniGraph().exists()).toBe(true); }); it('should not display linked pipelines', () => { - expect(findUpstream().exists()).toBe(false); - expect(findDownstream().exists()).toBe(false); + const downstreamPipelines = findPipelineMiniGraph().props('downstreamPipelines'); + const upstreamPipeline = findPipelineMiniGraph().props('upstreamPipeline'); + + expect(downstreamPipelines).toHaveLength(0); + expect(upstreamPipeline).toEqual(undefined); }); }); diff --git a/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js b/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..93eb18c90cf16b31852cc411200c14b01347c829 --- /dev/null +++ b/spec/frontend/pipeline_editor/components/header/pipeline_editor_mini_graph_spec.js @@ -0,0 +1,109 @@ +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import PipelineEditorMiniGraph from '~/pipeline_editor/components/header/pipeline_editor_mini_graph.vue'; +import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; +import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql'; +import { PIPELINE_FAILURE } from '~/pipeline_editor/constants'; +import { mockLinkedPipelines, mockProjectFullPath, mockProjectPipeline } from '../../mock_data'; + +Vue.use(VueApollo); + +describe('Pipeline Status', () => { + let wrapper; + let mockApollo; + let mockLinkedPipelinesQuery; + + const createComponent = ({ hasStages = true, options } = {}) => { + wrapper = shallowMount(PipelineEditorMiniGraph, { + provide: { + dataMethod: 'graphql', + projectFullPath: mockProjectFullPath, + }, + propsData: { + pipeline: mockProjectPipeline({ hasStages }).pipeline, + }, + ...options, + }); + }; + + const createComponentWithApollo = (hasStages = true) => { + const handlers = [[getLinkedPipelinesQuery, mockLinkedPipelinesQuery]]; + mockApollo = createMockApollo(handlers); + + createComponent({ + hasStages, + options: { + apolloProvider: mockApollo, + }, + }); + }; + + const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph); + + beforeEach(() => { + mockLinkedPipelinesQuery = jest.fn(); + }); + + afterEach(() => { + mockLinkedPipelinesQuery.mockReset(); + wrapper.destroy(); + }); + + describe('when there are stages', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders pipeline mini graph', () => { + expect(findPipelineMiniGraph().exists()).toBe(true); + }); + }); + + describe('when there are no stages', () => { + beforeEach(() => { + createComponent({ hasStages: false }); + }); + + it('does not render pipeline mini graph', () => { + expect(findPipelineMiniGraph().exists()).toBe(false); + }); + }); + + describe('when querying upstream and downstream pipelines', () => { + describe('when query succeeds', () => { + beforeEach(() => { + mockLinkedPipelinesQuery.mockResolvedValue(mockLinkedPipelines()); + createComponentWithApollo(); + }); + + it('should call the query with the correct variables', () => { + expect(mockLinkedPipelinesQuery).toHaveBeenCalledTimes(1); + expect(mockLinkedPipelinesQuery).toHaveBeenCalledWith({ + fullPath: mockProjectFullPath, + iid: mockProjectPipeline().pipeline.iid, + }); + }); + }); + + describe('when query fails', () => { + beforeEach(async () => { + mockLinkedPipelinesQuery.mockRejectedValue(new Error()); + createComponentWithApollo(); + await waitForPromises(); + }); + + it('should emit an error event when query fails', async () => { + expect(wrapper.emitted('showError')).toHaveLength(1); + expect(wrapper.emitted('showError')[0]).toEqual([ + { + type: PIPELINE_FAILURE, + reasons: [wrapper.vm.$options.i18n.linkedPipelinesFetchError], + }, + ]); + }); + }); + }); +}); diff --git a/spec/frontend/pipelines/components/pipelines_list/pipeline_mini_graph_spec.js b/spec/frontend/pipelines/components/pipelines_list/pipeline_stages_spec.js similarity index 92% rename from spec/frontend/pipelines/components/pipelines_list/pipeline_mini_graph_spec.js rename to spec/frontend/pipelines/components/pipelines_list/pipeline_stages_spec.js index 1cb43c199aa5f70e461dc53ecef07dc4f462fbb0..1e31d8a62ffd997941a3d22efc96a66550b8b68e 100644 --- a/spec/frontend/pipelines/components/pipelines_list/pipeline_mini_graph_spec.js +++ b/spec/frontend/pipelines/components/pipelines_list/pipeline_stages_spec.js @@ -1,18 +1,18 @@ import { shallowMount } from '@vue/test-utils'; import { pipelines } from 'test_fixtures/pipelines/pipelines.json'; -import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue'; +import PipelineStages from '~/pipelines/components/pipelines_list/pipeline_stages.vue'; const mockStages = pipelines[0].details.stages; -describe('Pipeline Mini Graph', () => { +describe('Pipeline Stages', () => { let wrapper; const findPipelineStages = () => wrapper.findAll(PipelineStage); const findPipelineStagesAt = (i) => findPipelineStages().at(i); const createComponent = (props = {}) => { - wrapper = shallowMount(PipelineMiniGraph, { + wrapper = shallowMount(PipelineStages, { propsData: { stages: mockStages, ...props, diff --git a/spec/frontend/pipelines/linked_pipelines_mock_data.js b/spec/frontend/pipelines/linked_pipelines_mock_data.js new file mode 100644 index 0000000000000000000000000000000000000000..117c7f2ae529fc75849d75e27361e40ac9f3a712 --- /dev/null +++ b/spec/frontend/pipelines/linked_pipelines_mock_data.js @@ -0,0 +1,407 @@ +export default { + triggered_by: { + id: 129, + active: true, + path: '/gitlab-org/gitlab-foss/-/pipelines/129', + project: { + name: 'GitLabCE', + }, + details: { + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-foss/-/pipelines/129', + favicon: + '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico', + }, + }, + flags: { + latest: false, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: true, + }, + ref: { + name: '7-5-stable', + path: '/gitlab-org/gitlab-foss/commits/7-5-stable', + tag: false, + branch: true, + }, + commit: { + id: '23433d4d8b20d7e45c103d0b6048faad38a130ab', + short_id: '23433d4d', + title: 'Version 7.5.0.rc1', + created_at: '2014-11-17T15:44:14.000+01:00', + parent_ids: ['30ac909f30f58d319b42ed1537664483894b18cd'], + message: 'Version 7.5.0.rc1\n', + author_name: 'Jacob Vosmaer', + author_email: 'contact@jacobvosmaer.nl', + authored_date: '2014-11-17T15:44:14.000+01:00', + committer_name: 'Jacob Vosmaer', + committer_email: 'contact@jacobvosmaer.nl', + committed_date: '2014-11-17T15:44:14.000+01:00', + author_gravatar_url: + 'http://www.gravatar.com/avatar/e66d11c0eedf8c07b3b18fca46599807?s=80&d=identicon', + commit_url: + 'http://localhost:3000/gitlab-org/gitlab-foss/commit/23433d4d8b20d7e45c103d0b6048faad38a130ab', + commit_path: '/gitlab-org/gitlab-foss/commit/23433d4d8b20d7e45c103d0b6048faad38a130ab', + }, + retry_path: '/gitlab-org/gitlab-foss/-/pipelines/129/retry', + cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/129/cancel', + created_at: '2017-05-24T14:46:20.090Z', + updated_at: '2017-05-24T14:46:29.906Z', + }, + triggered: [ + { + id: 132, + active: true, + path: '/gitlab-org/gitlab-foss/-/pipelines/132', + project: { + name: 'GitLabCE', + }, + details: { + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-foss/-/pipelines/132', + favicon: + '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico', + }, + }, + flags: { + latest: false, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: true, + }, + ref: { + name: 'crowd', + path: '/gitlab-org/gitlab-foss/commits/crowd', + tag: false, + branch: true, + }, + commit: { + id: 'b9d58c4cecd06be74c3cc32ccfb522b31544ab2e', + short_id: 'b9d58c4c', + title: 'getting user keys publically through http without any authentication, the github…', + created_at: '2013-10-03T12:50:33.000+05:30', + parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'], + message: + 'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n\nchangelog updated to include ssh key retrieval feature update\n', + author_name: 'devaroop', + author_email: 'devaroop123@yahoo.co.in', + authored_date: '2013-10-02T20:39:29.000+05:30', + committer_name: 'devaroop', + committer_email: 'devaroop123@yahoo.co.in', + committed_date: '2013-10-03T12:50:33.000+05:30', + author_gravatar_url: + 'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon', + commit_url: + 'http://localhost:3000/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e', + commit_path: '/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e', + }, + retry_path: '/gitlab-org/gitlab-foss/-/pipelines/132/retry', + cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/132/cancel', + created_at: '2017-05-24T14:46:24.644Z', + updated_at: '2017-05-24T14:48:55.226Z', + }, + { + id: 133, + active: true, + path: '/gitlab-org/gitlab-foss/-/pipelines/133', + project: { + name: 'GitLabCE', + }, + details: { + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-foss/-/pipelines/133', + favicon: + '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico', + }, + }, + flags: { + latest: false, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: true, + }, + ref: { + name: 'crowd', + path: '/gitlab-org/gitlab-foss/commits/crowd', + tag: false, + branch: true, + }, + commit: { + id: 'b6bd4856a33df3d144be66c4ed1f1396009bb08b', + short_id: 'b6bd4856', + title: 'getting user keys publically through http without any authentication, the github…', + created_at: '2013-10-02T20:39:29.000+05:30', + parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'], + message: + 'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n', + author_name: 'devaroop', + author_email: 'devaroop123@yahoo.co.in', + authored_date: '2013-10-02T20:39:29.000+05:30', + committer_name: 'devaroop', + committer_email: 'devaroop123@yahoo.co.in', + committed_date: '2013-10-02T20:39:29.000+05:30', + author_gravatar_url: + 'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon', + commit_url: + 'http://localhost:3000/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b', + commit_path: '/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b', + }, + retry_path: '/gitlab-org/gitlab-foss/-/pipelines/133/retry', + cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/133/cancel', + created_at: '2017-05-24T14:46:24.648Z', + updated_at: '2017-05-24T14:48:59.673Z', + }, + { + id: 130, + active: true, + path: '/gitlab-org/gitlab-foss/-/pipelines/130', + project: { + name: 'GitLabCE', + }, + details: { + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-foss/-/pipelines/130', + favicon: + '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico', + }, + }, + flags: { + latest: false, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: true, + }, + ref: { + name: 'crowd', + path: '/gitlab-org/gitlab-foss/commits/crowd', + tag: false, + branch: true, + }, + commit: { + id: '6d7ced4a2311eeff037c5575cca1868a6d3f586f', + short_id: '6d7ced4a', + title: 'Whitespace fixes to patch', + created_at: '2013-10-08T13:53:22.000-05:00', + parent_ids: ['1875141a963a4238bda29011d8f7105839485253'], + message: 'Whitespace fixes to patch\n', + author_name: 'Dale Hamel', + author_email: 'dale.hamel@srvthe.net', + authored_date: '2013-10-08T13:53:22.000-05:00', + committer_name: 'Dale Hamel', + committer_email: 'dale.hamel@invenia.ca', + committed_date: '2013-10-08T13:53:22.000-05:00', + author_gravatar_url: + 'http://www.gravatar.com/avatar/cd08930e69fa5ad1a669206e7bafe476?s=80&d=identicon', + commit_url: + 'http://localhost:3000/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f', + commit_path: '/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f', + }, + retry_path: '/gitlab-org/gitlab-foss/-/pipelines/130/retry', + cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/130/cancel', + created_at: '2017-05-24T14:46:24.630Z', + updated_at: '2017-05-24T14:49:45.091Z', + }, + { + id: 131, + active: true, + path: '/gitlab-org/gitlab-foss/-/pipelines/132', + project: { + name: 'GitLabCE', + }, + details: { + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-foss/-/pipelines/132', + favicon: + '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico', + }, + }, + flags: { + latest: false, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: true, + }, + ref: { + name: 'crowd', + path: '/gitlab-org/gitlab-foss/commits/crowd', + tag: false, + branch: true, + }, + commit: { + id: 'b9d58c4cecd06be74c3cc32ccfb522b31544ab2e', + short_id: 'b9d58c4c', + title: 'getting user keys publically through http without any authentication, the github…', + created_at: '2013-10-03T12:50:33.000+05:30', + parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'], + message: + 'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n\nchangelog updated to include ssh key retrieval feature update\n', + author_name: 'devaroop', + author_email: 'devaroop123@yahoo.co.in', + authored_date: '2013-10-02T20:39:29.000+05:30', + committer_name: 'devaroop', + committer_email: 'devaroop123@yahoo.co.in', + committed_date: '2013-10-03T12:50:33.000+05:30', + author_gravatar_url: + 'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon', + commit_url: + 'http://localhost:3000/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e', + commit_path: '/gitlab-org/gitlab-foss/commit/b9d58c4cecd06be74c3cc32ccfb522b31544ab2e', + }, + retry_path: '/gitlab-org/gitlab-foss/-/pipelines/132/retry', + cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/132/cancel', + created_at: '2017-05-24T14:46:24.644Z', + updated_at: '2017-05-24T14:48:55.226Z', + }, + { + id: 134, + active: true, + path: '/gitlab-org/gitlab-foss/-/pipelines/133', + project: { + name: 'GitLabCE', + }, + details: { + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-foss/-/pipelines/133', + favicon: + '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico', + }, + }, + flags: { + latest: false, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: true, + }, + ref: { + name: 'crowd', + path: '/gitlab-org/gitlab-foss/commits/crowd', + tag: false, + branch: true, + }, + commit: { + id: 'b6bd4856a33df3d144be66c4ed1f1396009bb08b', + short_id: 'b6bd4856', + title: 'getting user keys publically through http without any authentication, the github…', + created_at: '2013-10-02T20:39:29.000+05:30', + parent_ids: ['e219cf7246c6a0495e4507deaffeba11e79f13b8'], + message: + 'getting user keys publically through http without any authentication, the github way. E.g: http://github.com/devaroop.keys\n', + author_name: 'devaroop', + author_email: 'devaroop123@yahoo.co.in', + authored_date: '2013-10-02T20:39:29.000+05:30', + committer_name: 'devaroop', + committer_email: 'devaroop123@yahoo.co.in', + committed_date: '2013-10-02T20:39:29.000+05:30', + author_gravatar_url: + 'http://www.gravatar.com/avatar/35df4b155ec66a3127d53459941cf8a2?s=80&d=identicon', + commit_url: + 'http://localhost:3000/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b', + commit_path: '/gitlab-org/gitlab-foss/commit/b6bd4856a33df3d144be66c4ed1f1396009bb08b', + }, + retry_path: '/gitlab-org/gitlab-foss/-/pipelines/133/retry', + cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/133/cancel', + created_at: '2017-05-24T14:46:24.648Z', + updated_at: '2017-05-24T14:48:59.673Z', + }, + { + id: 135, + active: true, + path: '/gitlab-org/gitlab-foss/-/pipelines/130', + project: { + name: 'GitLabCE', + }, + details: { + status: { + icon: 'status_running', + text: 'running', + label: 'running', + group: 'running', + has_details: true, + details_path: '/gitlab-org/gitlab-foss/-/pipelines/130', + favicon: + '/assets/ci_favicons/dev/favicon_status_running-c3ad2fc53ea6079c174e5b6c1351ff349e99ec3af5a5622fb77b0fe53ea279c1.ico', + }, + }, + flags: { + latest: false, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: true, + }, + ref: { + name: 'crowd', + path: '/gitlab-org/gitlab-foss/commits/crowd', + tag: false, + branch: true, + }, + commit: { + id: '6d7ced4a2311eeff037c5575cca1868a6d3f586f', + short_id: '6d7ced4a', + title: 'Whitespace fixes to patch', + created_at: '2013-10-08T13:53:22.000-05:00', + parent_ids: ['1875141a963a4238bda29011d8f7105839485253'], + message: 'Whitespace fixes to patch\n', + author_name: 'Dale Hamel', + author_email: 'dale.hamel@srvthe.net', + authored_date: '2013-10-08T13:53:22.000-05:00', + committer_name: 'Dale Hamel', + committer_email: 'dale.hamel@invenia.ca', + committed_date: '2013-10-08T13:53:22.000-05:00', + author_gravatar_url: + 'http://www.gravatar.com/avatar/cd08930e69fa5ad1a669206e7bafe476?s=80&d=identicon', + commit_url: + 'http://localhost:3000/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f', + commit_path: '/gitlab-org/gitlab-foss/commit/6d7ced4a2311eeff037c5575cca1868a6d3f586f', + }, + retry_path: '/gitlab-org/gitlab-foss/-/pipelines/130/retry', + cancel_path: '/gitlab-org/gitlab-foss/-/pipelines/130/cancel', + created_at: '2017-05-24T14:46:24.630Z', + updated_at: '2017-05-24T14:49:45.091Z', + }, + ], +}; diff --git a/spec/frontend/pipelines/pipeline_mini_graph_spec.js b/spec/frontend/pipelines/pipeline_mini_graph_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..81aa97ce13fe3db1fee771e1fe52a141c7db2e23 --- /dev/null +++ b/spec/frontend/pipelines/pipeline_mini_graph_spec.js @@ -0,0 +1,149 @@ +import { mount } from '@vue/test-utils'; +import { pipelines } from 'test_fixtures/pipelines/pipelines.json'; +import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; +import PipelineStages from '~/pipelines/components/pipelines_list/pipeline_stages.vue'; +import mockLinkedPipelines from './linked_pipelines_mock_data'; + +const mockStages = pipelines[0].details.stages; + +describe('Pipeline Mini Graph', () => { + let wrapper; + + const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph); + const findPipelineStages = () => wrapper.findComponent(PipelineStages); + + const findLinkedPipelineUpstream = () => + wrapper.findComponent('[data-testid="pipeline-mini-graph-upstream"]'); + const findLinkedPipelineDownstream = () => + wrapper.findComponent('[data-testid="pipeline-mini-graph-downstream"]'); + const findDownstreamArrowIcon = () => wrapper.find('[data-testid="downstream-arrow-icon"]'); + const findUpstreamArrowIcon = () => wrapper.find('[data-testid="upstream-arrow-icon"]'); + + const createComponent = (props = {}) => { + wrapper = mount(PipelineMiniGraph, { + propsData: { + stages: mockStages, + ...props, + }, + }); + }; + + describe('rendered state without upstream or downstream pipelines', () => { + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('should render the pipeline stages', () => { + expect(findPipelineStages().exists()).toBe(true); + }); + + it('should have the correct props', () => { + expect(findPipelineMiniGraph().props()).toMatchObject({ + downstreamPipelines: [], + isMergeTrain: false, + pipelinePath: '', + stages: expect.any(Array), + stagesClass: '', + updateDropdown: false, + upstreamPipeline: undefined, + }); + }); + + it('should have no linked pipelines', () => { + expect(findLinkedPipelineDownstream().exists()).toBe(false); + expect(findLinkedPipelineUpstream().exists()).toBe(false); + }); + + it('should not render arrow icons', () => { + expect(findUpstreamArrowIcon().exists()).toBe(false); + expect(findDownstreamArrowIcon().exists()).toBe(false); + }); + + it('triggers events in "action request complete"', () => { + createComponent(); + + findPipelineMiniGraph(0).vm.$emit('pipelineActionRequestComplete'); + findPipelineMiniGraph(1).vm.$emit('pipelineActionRequestComplete'); + + expect(wrapper.emitted('pipelineActionRequestComplete')).toHaveLength(2); + }); + }); + + describe('rendered state with upstream pipeline', () => { + beforeEach(() => { + createComponent({ + upstreamPipeline: mockLinkedPipelines.triggered_by, + }); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('should have the correct props', () => { + expect(findPipelineMiniGraph().props()).toMatchObject({ + downstreamPipelines: [], + isMergeTrain: false, + pipelinePath: '', + stages: expect.any(Array), + stagesClass: '', + updateDropdown: false, + upstreamPipeline: expect.any(Object), + }); + }); + + it('should render the upstream linked pipelines mini list only', () => { + expect(findLinkedPipelineUpstream().exists()).toBe(true); + expect(findLinkedPipelineDownstream().exists()).toBe(false); + }); + + it('should render an upstream arrow icon only', () => { + expect(findDownstreamArrowIcon().exists()).toBe(false); + expect(findUpstreamArrowIcon().exists()).toBe(true); + expect(findUpstreamArrowIcon().props('name')).toBe('long-arrow'); + }); + }); + + describe('rendered state with downstream pipelines', () => { + beforeEach(() => { + createComponent({ + downstreamPipelines: mockLinkedPipelines.triggered, + pipelinePath: 'my/pipeline/path', + }); + }); + + it('should have the correct props', () => { + expect(findPipelineMiniGraph().props()).toMatchObject({ + downstreamPipelines: expect.any(Array), + isMergeTrain: false, + pipelinePath: 'my/pipeline/path', + stages: expect.any(Array), + stagesClass: '', + updateDropdown: false, + upstreamPipeline: undefined, + }); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('should render the downstream linked pipelines mini list only', () => { + expect(findLinkedPipelineDownstream().exists()).toBe(true); + expect(findLinkedPipelineUpstream().exists()).toBe(false); + }); + + it('should render a downstream arrow icon only', () => { + expect(findUpstreamArrowIcon().exists()).toBe(false); + expect(findDownstreamArrowIcon().exists()).toBe(true); + expect(findDownstreamArrowIcon().props('name')).toBe('long-arrow'); + }); + }); +}); diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js index 7b49baa5a204586f10ed97b826ca7f9e4159782a..07818b9dadbe9129ca667de3ecfbb9c7afecac53 100644 --- a/spec/frontend/pipelines/pipelines_table_spec.js +++ b/spec/frontend/pipelines/pipelines_table_spec.js @@ -113,40 +113,28 @@ describe('Pipelines Table', () => { }); describe('stages cell', () => { - it('should render a pipeline mini graph', () => { + it('should render pipeline mini graph', () => { expect(findPipelineMiniGraph().exists()).toBe(true); }); it('should render the right number of stages', () => { const stagesLength = pipeline.details.stages.length; - expect( - findPipelineMiniGraph().findAll('[data-testid="mini-pipeline-graph-dropdown"]'), - ).toHaveLength(stagesLength); + expect(findPipelineMiniGraph().props('stages').length).toBe(stagesLength); }); describe('when pipeline does not have stages', () => { beforeEach(() => { pipeline = createMockPipeline(); - pipeline.details.stages = null; + pipeline.details.stages = []; createComponent({ pipelines: [pipeline] }); }); it('stages are not rendered', () => { - expect(findPipelineMiniGraph().exists()).toBe(false); + expect(findPipelineMiniGraph().props('stages')).toHaveLength(0); }); }); - it('should not update dropdown', () => { - expect(findPipelineMiniGraph().props('updateDropdown')).toBe(false); - }); - - it('when update graph dropdown is set, should update graph dropdown', () => { - createComponent({ pipelines: [pipeline], updateGraphDropdown: true }); - - expect(findPipelineMiniGraph().props('updateDropdown')).toBe(true); - }); - it('when action request is complete, should refresh table', () => { findPipelineMiniGraph().vm.$emit('pipelineActionRequestComplete'); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js index 6347e3c3be34cc706c3001069a2b1a0a87c2580d..a32f61c4567bf6cd2bb7a18627afde00615fbd83 100644 --- a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js +++ b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_spec.js @@ -4,9 +4,8 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { trimText } from 'helpers/text_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import MRWidgetPipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue'; import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; -import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue'; -import PipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue'; import { SUCCESS } from '~/vue_merge_request_widget/constants'; import mockData from '../mock_data'; @@ -30,14 +29,13 @@ describe('MRWidgetPipeline', () => { const findPipelineInfoContainer = () => wrapper.findByTestId('pipeline-info-container'); const findCommitLink = () => wrapper.findByTestId('commit-link'); const findPipelineFinishedAt = () => wrapper.findByTestId('finished-at'); - const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph); - const findAllPipelineStages = () => wrapper.findAllComponents(PipelineStage); const findPipelineCoverage = () => wrapper.findByTestId('pipeline-coverage'); const findPipelineCoverageDelta = () => wrapper.findByTestId('pipeline-coverage-delta'); const findPipelineCoverageTooltipText = () => wrapper.findByTestId('pipeline-coverage-tooltip').text(); const findPipelineCoverageDeltaTooltipText = () => wrapper.findByTestId('pipeline-coverage-delta-tooltip').text(); + const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph); const findMonitoringPipelineMessage = () => wrapper.findByTestId('monitoring-pipeline-message'); const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); @@ -45,7 +43,7 @@ describe('MRWidgetPipeline', () => { const createWrapper = (props = {}, mountFn = shallowMount) => { wrapper = extendedWrapper( - mountFn(PipelineComponent, { + mountFn(MRWidgetPipelineComponent, { propsData: { ...defaultProps, ...props, @@ -106,8 +104,10 @@ describe('MRWidgetPipeline', () => { }); it('should render pipeline graph', () => { + const stagesCount = mockData.pipeline.details.stages.length; + expect(findPipelineMiniGraph().exists()).toBe(true); - expect(findAllPipelineStages()).toHaveLength(mockData.pipeline.details.stages.length); + expect(findPipelineMiniGraph().props('stages')).toHaveLength(stagesCount); }); describe('should render pipeline coverage information', () => { @@ -176,15 +176,11 @@ describe('MRWidgetPipeline', () => { expect(findPipelineInfoContainer().text()).toMatch(mockData.pipeline.details.status.label); }); - it('should render pipeline graph with correct styles', () => { + it('should render pipeline graph', () => { const stagesCount = mockData.pipeline.details.stages.length; expect(findPipelineMiniGraph().exists()).toBe(true); - expect(findPipelineMiniGraph().findAll('.mr-widget-pipeline-stages')).toHaveLength( - stagesCount, - ); - - expect(findAllPipelineStages()).toHaveLength(stagesCount); + expect(findPipelineMiniGraph().props('stages')).toHaveLength(stagesCount); }); it('should render coverage information', () => {