diff options
Diffstat (limited to 'spec/frontend/pipelines/components/pipeline_mini_graph')
5 files changed, 1075 insertions, 0 deletions
diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js new file mode 100644 index 00000000000..5ea57c51e70 --- /dev/null +++ b/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js @@ -0,0 +1,176 @@ +import { mount } from '@vue/test-utils'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import LinkedPipelinesMiniList from '~/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list.vue'; +import mockData from './linked_pipelines_mock_data'; + +describe('Linked pipeline mini list', () => { + let wrapper; + + const findCiIcon = () => wrapper.findComponent(CiIcon); + const findCiIcons = () => wrapper.findAllComponents(CiIcon); + const findLinkedPipelineCounter = () => wrapper.find('[data-testid="linked-pipeline-counter"]'); + const findLinkedPipelineMiniItem = () => + wrapper.find('[data-testid="linked-pipeline-mini-item"]'); + const findLinkedPipelineMiniItems = () => + wrapper.findAll('[data-testid="linked-pipeline-mini-item"]'); + const findLinkedPipelineMiniList = () => wrapper.findComponent(LinkedPipelinesMiniList); + + const createComponent = (props = {}) => { + wrapper = mount(LinkedPipelinesMiniList, { + directives: { + GlTooltip: createMockDirective(), + }, + propsData: { + ...props, + }, + }); + }; + + describe('when passed an upstream pipeline as prop', () => { + beforeEach(() => { + createComponent({ + triggeredBy: [mockData.triggered_by], + }); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('should render one linked pipeline item', () => { + expect(findLinkedPipelineMiniItem().exists()).toBe(true); + }); + + it('should render a linked pipeline with the correct href', () => { + expect(findLinkedPipelineMiniItem().exists()).toBe(true); + + expect(findLinkedPipelineMiniItem().attributes('href')).toBe( + '/gitlab-org/gitlab-foss/-/pipelines/129', + ); + }); + + it('should render one ci status icon', () => { + expect(findCiIcon().exists()).toBe(true); + }); + + it('should render a borderless ci-icon', () => { + expect(findCiIcon().exists()).toBe(true); + + expect(findCiIcon().props('isBorderless')).toBe(true); + expect(findCiIcon().classes('borderless')).toBe(true); + }); + + it('should render a ci-icon with a custom border class', () => { + expect(findCiIcon().exists()).toBe(true); + + expect(findCiIcon().classes('gl-border')).toBe(true); + }); + + it('should render the correct ci status icon', () => { + expect(findCiIcon().classes('ci-status-icon-running')).toBe(true); + }); + + it('should have an activated tooltip', () => { + expect(findLinkedPipelineMiniItem().exists()).toBe(true); + const tooltip = getBinding(findLinkedPipelineMiniItem().element, 'gl-tooltip'); + + expect(tooltip.value.title).toBe('GitLabCE - running'); + }); + + it('should correctly set is-upstream', () => { + expect(findLinkedPipelineMiniList().exists()).toBe(true); + + expect(findLinkedPipelineMiniList().classes('is-upstream')).toBe(true); + }); + + it('should correctly compute shouldRenderCounter', () => { + expect(findLinkedPipelineMiniList().vm.shouldRenderCounter).toBe(false); + }); + + it('should not render the pipeline counter', () => { + expect(findLinkedPipelineCounter().exists()).toBe(false); + }); + }); + + describe('when passed downstream pipelines as props', () => { + beforeEach(() => { + createComponent({ + triggered: mockData.triggered, + pipelinePath: 'my/pipeline/path', + }); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('should render three linked pipeline items', () => { + expect(findLinkedPipelineMiniItems().exists()).toBe(true); + expect(findLinkedPipelineMiniItems().length).toBe(3); + }); + + it('should render three ci status icons', () => { + expect(findCiIcons().exists()).toBe(true); + expect(findCiIcons().length).toBe(3); + }); + + it('should render the correct ci status icon', () => { + expect(findCiIcon().classes('ci-status-icon-running')).toBe(true); + }); + + it('should have an activated tooltip', () => { + expect(findLinkedPipelineMiniItem().exists()).toBe(true); + const tooltip = getBinding(findLinkedPipelineMiniItem().element, 'gl-tooltip'); + + expect(tooltip.value.title).toBe('GitLabCE - running'); + }); + + it('should correctly set is-downstream', () => { + expect(findLinkedPipelineMiniList().exists()).toBe(true); + + expect(findLinkedPipelineMiniList().classes('is-downstream')).toBe(true); + }); + + it('should render a borderless ci-icon', () => { + expect(findCiIcon().exists()).toBe(true); + + expect(findCiIcon().props('isBorderless')).toBe(true); + expect(findCiIcon().classes('borderless')).toBe(true); + }); + + it('should render a ci-icon with a custom border class', () => { + expect(findCiIcon().exists()).toBe(true); + + expect(findCiIcon().classes('gl-border')).toBe(true); + }); + + it('should render the pipeline counter', () => { + expect(findLinkedPipelineCounter().exists()).toBe(true); + }); + + it('should correctly compute shouldRenderCounter', () => { + expect(findLinkedPipelineMiniList().vm.shouldRenderCounter).toBe(true); + }); + + it('should correctly trim linkedPipelines', () => { + expect(findLinkedPipelineMiniList().props('triggered').length).toBe(6); + expect(findLinkedPipelineMiniList().vm.linkedPipelinesTrimmed.length).toBe(3); + }); + + it('should set the correct pipeline path', () => { + expect(findLinkedPipelineCounter().exists()).toBe(true); + + expect(findLinkedPipelineCounter().attributes('href')).toBe('my/pipeline/path'); + }); + + it('should render the correct counterTooltipText', () => { + expect(findLinkedPipelineCounter().exists()).toBe(true); + const tooltip = getBinding(findLinkedPipelineCounter().element, 'gl-tooltip'); + + expect(tooltip.value.title).toBe(findLinkedPipelineMiniList().vm.counterTooltipText); + }); + }); +}); diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mock_data.js b/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mock_data.js new file mode 100644 index 00000000000..117c7f2ae52 --- /dev/null +++ b/spec/frontend/pipelines/components/pipeline_mini_graph/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/components/pipeline_mini_graph/pipeline_mini_graph_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_mini_graph_spec.js new file mode 100644 index 00000000000..7fa8a18ea1f --- /dev/null +++ b/spec/frontend/pipelines/components/pipeline_mini_graph/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/pipeline_mini_graph/pipeline_mini_graph.vue'; +import PipelineStages from '~/pipelines/components/pipeline_mini_graph/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/components/pipeline_mini_graph/pipeline_stage_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js new file mode 100644 index 00000000000..52b440f18bb --- /dev/null +++ b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js @@ -0,0 +1,260 @@ +import { GlDropdown } from '@gitlab/ui'; +import { nextTick } from 'vue'; +import { mount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import axios from '~/lib/utils/axios_utils'; +import PipelineStage from '~/pipelines/components/pipeline_mini_graph/pipeline_stage.vue'; +import eventHub from '~/pipelines/event_hub'; +import waitForPromises from 'helpers/wait_for_promises'; +import { stageReply } from '../../mock_data'; + +const dropdownPath = 'path.json'; + +describe('Pipelines stage component', () => { + let wrapper; + let mock; + let glTooltipDirectiveMock; + + const createComponent = (props = {}) => { + glTooltipDirectiveMock = jest.fn(); + wrapper = mount(PipelineStage, { + attachTo: document.body, + directives: { + GlTooltip: glTooltipDirectiveMock, + }, + propsData: { + stage: { + status: { + group: 'success', + icon: 'status_success', + title: 'success', + }, + dropdown_path: dropdownPath, + }, + updateDropdown: false, + ...props, + }, + }); + }; + + beforeEach(() => { + mock = new MockAdapter(axios); + jest.spyOn(eventHub, '$emit'); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + + eventHub.$emit.mockRestore(); + mock.restore(); + }); + + const findCiActionBtn = () => wrapper.find('.js-ci-action'); + const findCiIcon = () => wrapper.findComponent(CiIcon); + const findDropdown = () => wrapper.findComponent(GlDropdown); + const findDropdownToggle = () => wrapper.find('button.dropdown-toggle'); + const findDropdownMenu = () => + wrapper.find('[data-testid="mini-pipeline-graph-dropdown-menu-list"]'); + const findDropdownMenuTitle = () => + wrapper.find('[data-testid="pipeline-stage-dropdown-menu-title"]'); + const findMergeTrainWarning = () => wrapper.find('[data-testid="warning-message-merge-trains"]'); + const findLoadingState = () => wrapper.find('[data-testid="pipeline-stage-loading-state"]'); + + const openStageDropdown = async () => { + await findDropdownToggle().trigger('click'); + await waitForPromises(); + await nextTick(); + }; + + describe('loading state', () => { + beforeEach(async () => { + createComponent({ updateDropdown: true }); + + mock.onGet(dropdownPath).reply(200, stageReply); + + await openStageDropdown(); + }); + + it('displays loading state while jobs are being fetched', async () => { + jest.runOnlyPendingTimers(); + await nextTick(); + + expect(findLoadingState().exists()).toBe(true); + expect(findLoadingState().text()).toBe(PipelineStage.i18n.loadingText); + }); + + it('does not display loading state after jobs have been fetched', async () => { + await waitForPromises(); + + expect(findLoadingState().exists()).toBe(false); + }); + }); + + describe('default appearance', () => { + beforeEach(() => { + createComponent(); + }); + + it('sets up the tooltip to not have a show delay animation', () => { + expect(glTooltipDirectiveMock.mock.calls[0][1].modifiers.ds0).toBe(true); + }); + + it('renders a dropdown with the status icon', () => { + expect(findDropdown().exists()).toBe(true); + expect(findDropdownToggle().exists()).toBe(true); + expect(findCiIcon().exists()).toBe(true); + }); + + it('renders a borderless ci-icon', () => { + expect(findCiIcon().exists()).toBe(true); + expect(findCiIcon().props('isBorderless')).toBe(true); + expect(findCiIcon().classes('borderless')).toBe(true); + }); + + it('renders a ci-icon with a custom border class', () => { + expect(findCiIcon().exists()).toBe(true); + expect(findCiIcon().classes('gl-border')).toBe(true); + }); + }); + + describe('when user opens dropdown and stage request is successful', () => { + beforeEach(async () => { + mock.onGet(dropdownPath).reply(200, stageReply); + createComponent(); + + await openStageDropdown(); + await jest.runAllTimers(); + await axios.waitForAll(); + }); + + it('renders the received data and emits the correct events', async () => { + expect(findDropdownMenu().text()).toContain(stageReply.latest_statuses[0].name); + expect(findDropdownMenuTitle().text()).toContain(stageReply.name); + expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown'); + expect(wrapper.emitted('miniGraphStageClick')).toEqual([[]]); + }); + + it('refreshes when updateDropdown is set to true', async () => { + expect(mock.history.get).toHaveLength(1); + + wrapper.setProps({ updateDropdown: true }); + await axios.waitForAll(); + + expect(mock.history.get).toHaveLength(2); + }); + }); + + describe('when user opens dropdown and stage request fails', () => { + it('should close the dropdown', async () => { + mock.onGet(dropdownPath).reply(500); + createComponent(); + + await openStageDropdown(); + await axios.waitForAll(); + await waitForPromises(); + + expect(findDropdown().classes('show')).toBe(false); + }); + }); + + describe('update endpoint correctly', () => { + beforeEach(async () => { + const copyStage = { ...stageReply }; + copyStage.latest_statuses[0].name = 'this is the updated content'; + mock.onGet('bar.json').reply(200, copyStage); + createComponent({ + stage: { + status: { + group: 'running', + icon: 'status_running', + title: 'running', + }, + dropdown_path: 'bar.json', + }, + }); + await axios.waitForAll(); + }); + + it('should update the stage to request the new endpoint provided', async () => { + await openStageDropdown(); + jest.runOnlyPendingTimers(); + await waitForPromises(); + + expect(findDropdownMenu().text()).toContain('this is the updated content'); + }); + }); + + describe('pipelineActionRequestComplete', () => { + beforeEach(async () => { + mock.onGet(dropdownPath).reply(200, stageReply); + mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200); + + createComponent(); + await waitForPromises(); + await nextTick(); + }); + + const clickCiAction = async () => { + await openStageDropdown(); + jest.runOnlyPendingTimers(); + await waitForPromises(); + + await findCiActionBtn().trigger('click'); + }; + + it('closes dropdown when job item action is clicked', async () => { + const hidden = jest.fn(); + + wrapper.vm.$root.$on('bv::dropdown::hide', hidden); + + expect(hidden).toHaveBeenCalledTimes(0); + + await clickCiAction(); + await waitForPromises(); + + expect(hidden).toHaveBeenCalledTimes(1); + }); + + it('emits `pipelineActionRequestComplete` when job item action is clicked', async () => { + await clickCiAction(); + await waitForPromises(); + + expect(wrapper.emitted('pipelineActionRequestComplete')).toHaveLength(1); + }); + }); + + describe('With merge trains enabled', () => { + it('shows a warning on the dropdown', async () => { + mock.onGet(dropdownPath).reply(200, stageReply); + createComponent({ + isMergeTrain: true, + }); + + await openStageDropdown(); + jest.runOnlyPendingTimers(); + await waitForPromises(); + + const warning = findMergeTrainWarning(); + + expect(warning.text()).toBe('Merge train pipeline jobs can not be retried'); + }); + }); + + describe('With merge trains disabled', () => { + beforeEach(async () => { + mock.onGet(dropdownPath).reply(200, stageReply); + createComponent(); + + await openStageDropdown(); + await axios.waitForAll(); + }); + + it('does not show a warning on the dropdown', () => { + const warning = findMergeTrainWarning(); + + expect(warning.exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js new file mode 100644 index 00000000000..bfb780d5d39 --- /dev/null +++ b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js @@ -0,0 +1,83 @@ +import { shallowMount } from '@vue/test-utils'; +import { pipelines } from 'test_fixtures/pipelines/pipelines.json'; +import PipelineStage from '~/pipelines/components/pipeline_mini_graph/pipeline_stage.vue'; +import PipelineStages from '~/pipelines/components/pipeline_mini_graph/pipeline_stages.vue'; + +const mockStages = pipelines[0].details.stages; + +describe('Pipeline Stages', () => { + let wrapper; + + const findPipelineStages = () => wrapper.findAllComponents(PipelineStage); + const findPipelineStagesAt = (i) => findPipelineStages().at(i); + + const createComponent = (props = {}) => { + wrapper = shallowMount(PipelineStages, { + propsData: { + stages: mockStages, + ...props, + }, + }); + }; + + it('renders stages', () => { + createComponent(); + + expect(findPipelineStages()).toHaveLength(mockStages.length); + }); + + it('renders stages with a custom class', () => { + createComponent({ stagesClass: 'my-class' }); + + expect(wrapper.findAll('.my-class')).toHaveLength(mockStages.length); + }); + + it('does not fail when stages are empty', () => { + createComponent({ stages: [] }); + + expect(wrapper.exists()).toBe(true); + expect(findPipelineStages()).toHaveLength(0); + }); + + it('triggers events in "action request complete" in stages', () => { + createComponent(); + + findPipelineStagesAt(0).vm.$emit('pipelineActionRequestComplete'); + findPipelineStagesAt(1).vm.$emit('pipelineActionRequestComplete'); + + expect(wrapper.emitted('pipelineActionRequestComplete')).toHaveLength(2); + }); + + it('update dropdown is false by default', () => { + createComponent(); + + expect(findPipelineStagesAt(0).props('updateDropdown')).toBe(false); + expect(findPipelineStagesAt(1).props('updateDropdown')).toBe(false); + }); + + it('update dropdown is set to true', () => { + createComponent({ updateDropdown: true }); + + expect(findPipelineStagesAt(0).props('updateDropdown')).toBe(true); + expect(findPipelineStagesAt(1).props('updateDropdown')).toBe(true); + }); + + it('is merge train is false by default', () => { + createComponent(); + + expect(findPipelineStagesAt(0).props('isMergeTrain')).toBe(false); + expect(findPipelineStagesAt(1).props('isMergeTrain')).toBe(false); + }); + + it('is merge train is set to true', () => { + createComponent({ isMergeTrain: true }); + + expect(findPipelineStagesAt(0).props('isMergeTrain')).toBe(true); + expect(findPipelineStagesAt(1).props('isMergeTrain')).toBe(true); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); +}); |