diff options
Diffstat (limited to 'spec/frontend/pipelines')
37 files changed, 1006 insertions, 590 deletions
diff --git a/spec/frontend/pipelines/components/dag/dag_annotations_spec.js b/spec/frontend/pipelines/components/dag/dag_annotations_spec.js index 212f8e19a6d..28a08b6da0f 100644 --- a/spec/frontend/pipelines/components/dag/dag_annotations_spec.js +++ b/spec/frontend/pipelines/components/dag/dag_annotations_spec.js @@ -11,7 +11,7 @@ describe('The DAG annotations', () => { const getAllColorBlocks = () => wrapper.findAll('[data-testid="dag-color-block"]'); const getTextBlock = () => wrapper.find('[data-testid="dag-note-text"]'); const getAllTextBlocks = () => wrapper.findAll('[data-testid="dag-note-text"]'); - const getToggleButton = () => wrapper.find(GlButton); + const getToggleButton = () => wrapper.findComponent(GlButton); const createComponent = (propsData = {}, method = shallowMount) => { if (wrapper?.destroy) { diff --git a/spec/frontend/pipelines/components/dag/dag_spec.js b/spec/frontend/pipelines/components/dag/dag_spec.js index d78df3eb35e..b0c26976c85 100644 --- a/spec/frontend/pipelines/components/dag/dag_spec.js +++ b/spec/frontend/pipelines/components/dag/dag_spec.js @@ -18,12 +18,12 @@ import { describe('Pipeline DAG graph wrapper', () => { let wrapper; - const getAlert = () => wrapper.find(GlAlert); - const getAllAlerts = () => wrapper.findAll(GlAlert); - const getGraph = () => wrapper.find(DagGraph); - const getNotes = () => wrapper.find(DagAnnotations); + const getAlert = () => wrapper.findComponent(GlAlert); + const getAllAlerts = () => wrapper.findAllComponents(GlAlert); + const getGraph = () => wrapper.findComponent(DagGraph); + const getNotes = () => wrapper.findComponent(DagAnnotations); const getErrorText = (type) => wrapper.vm.$options.errorTexts[type]; - const getEmptyState = () => wrapper.find(GlEmptyState); + const getEmptyState = () => wrapper.findComponent(GlEmptyState); const createComponent = ({ graphData = mockParsedGraphQLNodes, 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/pipelines_list/pipeline_stage_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js index e712cdeaea2..52b440f18bb 100644 --- a/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js +++ b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js @@ -4,7 +4,7 @@ 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/pipelines_list/pipeline_stage.vue'; +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'; @@ -129,10 +129,11 @@ describe('Pipelines stage component', () => { await axios.waitForAll(); }); - it('renders the received data and emit `clickedDropdown` event', async () => { + 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 () => { diff --git a/spec/frontend/pipelines/components/pipelines_list/pipeline_mini_graph_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js index 1cb43c199aa..bfb780d5d39 100644 --- a/spec/frontend/pipelines/components/pipelines_list/pipeline_mini_graph_spec.js +++ b/spec/frontend/pipelines/components/pipeline_mini_graph/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 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 Mini Graph', () => { +describe('Pipeline Stages', () => { let wrapper; - const findPipelineStages = () => wrapper.findAll(PipelineStage); + const findPipelineStages = () => wrapper.findAllComponents(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/components/pipelines_filtered_search_spec.js b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js index f958f12acd4..ee3eaaf5ef3 100644 --- a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js +++ b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js @@ -2,17 +2,19 @@ import { GlFilteredSearch } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import { nextTick } from 'vue'; +import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import Api from '~/api'; import axios from '~/lib/utils/axios_utils'; import PipelinesFilteredSearch from '~/pipelines/components/pipelines_list/pipelines_filtered_search.vue'; import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; +import { TRACKING_CATEGORIES } from '~/pipelines/constants'; import { users, mockSearch, branches, tags } from '../mock_data'; describe('Pipelines filtered search', () => { let wrapper; let mock; - const findFilteredSearch = () => wrapper.find(GlFilteredSearch); + const findFilteredSearch = () => wrapper.findComponent(GlFilteredSearch); const getSearchToken = (type) => findFilteredSearch() .props('availableTokens') @@ -177,4 +179,20 @@ describe('Pipelines filtered search', () => { expect(findFilteredSearch().props('value')).toHaveLength(expectedValueProp.length); }); }); + + describe('tracking', () => { + afterEach(() => { + unmockTracking(); + }); + + it('tracks filtered search click', () => { + const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + + findFilteredSearch().vm.$emit('submit', mockSearch); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_filtered_search', { + label: TRACKING_CATEGORIES.search, + }); + }); + }); }); diff --git a/spec/frontend/pipelines/graph/action_component_spec.js b/spec/frontend/pipelines/graph/action_component_spec.js index 6e5aa572ec0..a823e029281 100644 --- a/spec/frontend/pipelines/graph/action_component_spec.js +++ b/spec/frontend/pipelines/graph/action_component_spec.js @@ -9,7 +9,7 @@ import ActionComponent from '~/pipelines/components/jobs_shared/action_component describe('pipeline graph action component', () => { let wrapper; let mock; - const findButton = () => wrapper.find(GlButton); + const findButton = () => wrapper.findComponent(GlButton); const findTooltipWrapper = () => wrapper.find('[data-testid="ci-action-icon-tooltip-wrapper"]'); beforeEach(() => { diff --git a/spec/frontend/pipelines/graph/graph_component_spec.js b/spec/frontend/pipelines/graph/graph_component_spec.js index 4b2b61c8edd..2abb5f7dc58 100644 --- a/spec/frontend/pipelines/graph/graph_component_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_spec.js @@ -15,9 +15,9 @@ import { describe('graph component', () => { let wrapper; - const findLinkedColumns = () => wrapper.findAll(LinkedPipelinesColumn); - const findLinksLayer = () => wrapper.find(LinksLayer); - const findStageColumns = () => wrapper.findAll(StageColumnComponent); + const findLinkedColumns = () => wrapper.findAllComponents(LinkedPipelinesColumn); + const findLinksLayer = () => wrapper.findComponent(LinksLayer); + const findStageColumns = () => wrapper.findAllComponents(StageColumnComponent); const findStageNameInJob = () => wrapper.find('[data-testid="stage-name-in-job"]'); const defaultProps = { @@ -107,7 +107,7 @@ describe('graph component', () => { }); it('dims unrelated jobs', () => { - const unrelatedJob = wrapper.find(JobItem); + const unrelatedJob = wrapper.findComponent(JobItem); expect(findLinksLayer().emitted().highlightedJobsChange).toHaveLength(1); expect(unrelatedJob.classes('gl-opacity-3')).toBe(true); }); diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js index 3eaf06e0656..587a3c67168 100644 --- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js @@ -30,16 +30,10 @@ import * as Api from '~/pipelines/components/graph_shared/api'; import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue'; import * as parsingUtils from '~/pipelines/components/parsing_utils'; import getPipelineHeaderData from '~/pipelines/graphql/queries/get_pipeline_header_data.query.graphql'; -import getPerformanceInsights from '~/pipelines/graphql/queries/get_performance_insights.query.graphql'; import * as sentryUtils from '~/pipelines/utils'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import { mockRunningPipelineHeaderData } from '../mock_data'; -import { - mapCallouts, - mockCalloutsResponse, - mockPipelineResponse, - mockPerformanceInsightsResponse, -} from './mock_data'; +import { mapCallouts, mockCalloutsResponse, mockPipelineResponse } from './mock_data'; const defaultProvide = { graphqlResourceEtag: 'frog/amphibirama/etag/', @@ -57,11 +51,11 @@ describe('Pipeline graph wrapper', () => { const getDependenciesToggle = () => wrapper.find('[data-testid="show-links-toggle"]'); const getLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const getLinksLayer = () => wrapper.findComponent(LinksLayer); - const getGraph = () => wrapper.find(PipelineGraph); + const getGraph = () => wrapper.findComponent(PipelineGraph); const getStageColumnTitle = () => wrapper.find('[data-testid="stage-column-title"]'); const getAllStageColumnGroupsInColumn = () => - wrapper.find(StageColumnComponent).findAll('[data-testid="stage-column-group"]'); - const getViewSelector = () => wrapper.find(GraphViewSelector); + wrapper.findComponent(StageColumnComponent).findAll('[data-testid="stage-column-group"]'); + const getViewSelector = () => wrapper.findComponent(GraphViewSelector); const getViewSelectorTrip = () => getViewSelector().findComponent(GlAlert); const getLocalStorageSync = () => wrapper.findComponent(LocalStorageSync); @@ -95,15 +89,11 @@ describe('Pipeline graph wrapper', () => { const callouts = mapCallouts(calloutsList); const getUserCalloutsHandler = jest.fn().mockResolvedValue(mockCalloutsResponse(callouts)); const getPipelineHeaderDataHandler = jest.fn().mockResolvedValue(mockRunningPipelineHeaderData); - const getPerformanceInsightsHandler = jest - .fn() - .mockResolvedValue(mockPerformanceInsightsResponse); const requestHandlers = [ [getPipelineHeaderData, getPipelineHeaderDataHandler], [getPipelineDetails, getPipelineDetailsHandler], [getUserCallouts, getUserCalloutsHandler], - [getPerformanceInsights, getPerformanceInsightsHandler], ]; const apolloProvider = createMockApollo(requestHandlers); @@ -309,7 +299,7 @@ describe('Pipeline graph wrapper', () => { const groupsInFirstColumn = mockPipelineResponse.data.project.pipeline.stages.nodes[0].groups.nodes.length; expect(getAllStageColumnGroupsInColumn()).toHaveLength(groupsInFirstColumn); - expect(getStageColumnTitle().text()).toBe('Build'); + expect(getStageColumnTitle().text()).toBe('build'); await getViewSelector().vm.$emit('updateViewType', LAYER_VIEW); expect(getAllStageColumnGroupsInColumn()).toHaveLength(groupsInFirstColumn + 1); expect(getStageColumnTitle().text()).toBe(''); @@ -418,7 +408,7 @@ describe('Pipeline graph wrapper', () => { it('reads the view type from localStorage when available', () => { const viewSelectorNeedsSegment = wrapper - .find(GlButtonGroup) + .findComponent(GlButtonGroup) .findAllComponents(GlButton) .at(1); expect(viewSelectorNeedsSegment.classes()).toContain('selected'); @@ -564,7 +554,7 @@ describe('Pipeline graph wrapper', () => { mock.restore(); }); - it('it calls reportPerformance with expected arguments', () => { + it('calls reportPerformance with expected arguments', () => { expect(markAndMeasure).toHaveBeenCalled(); expect(reportPerformance).toHaveBeenCalled(); expect(reportPerformance).toHaveBeenCalledWith(metricsPath, metricsData); diff --git a/spec/frontend/pipelines/graph/graph_view_selector_spec.js b/spec/frontend/pipelines/graph/graph_view_selector_spec.js index 1397500bdc7..43587bebedf 100644 --- a/spec/frontend/pipelines/graph/graph_view_selector_spec.js +++ b/spec/frontend/pipelines/graph/graph_view_selector_spec.js @@ -1,34 +1,23 @@ import { GlAlert, GlButton, GlButtonGroup, GlLoadingIcon } from '@gitlab/ui'; import { mount, shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import { LAYER_VIEW, STAGE_VIEW } from '~/pipelines/components/graph/constants'; import GraphViewSelector from '~/pipelines/components/graph/graph_view_selector.vue'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import getPerformanceInsights from '~/pipelines/graphql/queries/get_performance_insights.query.graphql'; -import { mockPerformanceInsightsResponse } from './mock_data'; - -Vue.use(VueApollo); describe('the graph view selector component', () => { let wrapper; - let trackingSpy; const findDependenciesToggle = () => wrapper.find('[data-testid="show-links-toggle"]'); const findViewTypeSelector = () => wrapper.findComponent(GlButtonGroup); const findStageViewButton = () => findViewTypeSelector().findAllComponents(GlButton).at(0); const findLayerViewButton = () => findViewTypeSelector().findAllComponents(GlButton).at(1); const findSwitcherLoader = () => wrapper.find('[data-testid="switcher-loading-state"]'); - const findToggleLoader = () => findDependenciesToggle().find(GlLoadingIcon); + const findToggleLoader = () => findDependenciesToggle().findComponent(GlLoadingIcon); const findHoverTip = () => wrapper.findComponent(GlAlert); - const findPipelineInsightsBtn = () => wrapper.find('[data-testid="pipeline-insights-btn"]'); const defaultProps = { showLinks: false, tipPreviouslyDismissed: false, type: STAGE_VIEW, - isPipelineComplete: true, }; const defaultData = { @@ -38,14 +27,6 @@ describe('the graph view selector component', () => { showLinksActive: false, }; - const getPerformanceInsightsHandler = jest - .fn() - .mockResolvedValue(mockPerformanceInsightsResponse); - - const requestHandlers = [[getPerformanceInsights, getPerformanceInsightsHandler]]; - - const apolloProvider = createMockApollo(requestHandlers); - const createComponent = ({ data = {}, mountFn = shallowMount, props = {} } = {}) => { wrapper = mountFn(GraphViewSelector, { propsData: { @@ -58,7 +39,6 @@ describe('the graph view selector component', () => { ...data, }; }, - apolloProvider, }); }; @@ -222,44 +202,5 @@ describe('the graph view selector component', () => { expect(findHoverTip().exists()).toBe(false); }); }); - - describe('pipeline insights', () => { - it.each` - isPipelineComplete | shouldShow - ${true} | ${true} - ${false} | ${false} - `( - 'button should display $shouldShow if isPipelineComplete is $isPipelineComplete ', - ({ isPipelineComplete, shouldShow }) => { - createComponent({ - props: { - isPipelineComplete, - }, - }); - - expect(findPipelineInsightsBtn().exists()).toBe(shouldShow); - }, - ); - }); - - describe('tracking', () => { - beforeEach(() => { - createComponent(); - - trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - }); - - afterEach(() => { - unmockTracking(); - }); - - it('tracks performance insights button click', () => { - findPipelineInsightsBtn().vm.$emit('click'); - - expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_insights_button', { - label: 'performance_insights', - }); - }); - }); }); }); diff --git a/spec/frontend/pipelines/graph/job_item_spec.js b/spec/frontend/pipelines/graph/job_item_spec.js index 4f0da09fec6..05776ec0706 100644 --- a/spec/frontend/pipelines/graph/job_item_spec.js +++ b/spec/frontend/pipelines/graph/job_item_spec.js @@ -59,7 +59,7 @@ describe('pipeline graph job item', () => { }); }); - it('it should render status and name', () => { + it('should render status and name', () => { expect(wrapper.find('.ci-status-icon-success').exists()).toBe(true); expect(wrapper.find('a').exists()).toBe(false); @@ -72,7 +72,7 @@ describe('pipeline graph job item', () => { }); describe('action icon', () => { - it('it should render the action icon', () => { + it('should render the action icon', () => { createWrapper({ job: mockJob }); const actionComponent = findActionComponent(); @@ -82,7 +82,7 @@ describe('pipeline graph job item', () => { expect(actionComponent.attributes('disabled')).not.toBe('disabled'); }); - it('it should render disabled action icon when user cannot run the action', () => { + it('should render disabled action icon when user cannot run the action', () => { createWrapper({ job: mockJobWithUnauthorizedAction }); const actionComponent = findActionComponent(); diff --git a/spec/frontend/pipelines/graph/job_name_component_spec.js b/spec/frontend/pipelines/graph/job_name_component_spec.js index d3008c046e8..ec432e98fdf 100644 --- a/spec/frontend/pipelines/graph/job_name_component_spec.js +++ b/spec/frontend/pipelines/graph/job_name_component_spec.js @@ -24,7 +24,7 @@ describe('job name component', () => { }); it('should render an icon with the provided status', () => { - expect(wrapper.find(ciIcon).exists()).toBe(true); + expect(wrapper.findComponent(ciIcon).exists()).toBe(true); expect(wrapper.find('.ci-status-icon-success').exists()).toBe(true); }); }); diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js index 7d1e4774a24..399d52c3dff 100644 --- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js +++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js @@ -36,13 +36,13 @@ describe('Linked pipeline', () => { type: UPSTREAM, }; - const findButton = () => wrapper.find(GlButton); + const findButton = () => wrapper.findComponent(GlButton); const findCancelButton = () => wrapper.findByLabelText('Cancel downstream pipeline'); const findCardTooltip = () => wrapper.findComponent(GlTooltip); const findDownstreamPipelineTitle = () => wrapper.findByTestId('downstream-title'); const findExpandButton = () => wrapper.findByTestId('expand-pipeline-button'); - const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' }); - const findLoadingIcon = () => wrapper.find(GlLoadingIcon); + const findLinkedPipeline = () => wrapper.findComponent({ ref: 'linkedPipeline' }); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findPipelineLabel = () => wrapper.findByTestId('downstream-pipeline-label'); const findPipelineLink = () => wrapper.findByTestId('pipelineLink'); const findRetryButton = () => wrapper.findByLabelText('Retry downstream pipeline'); @@ -80,7 +80,7 @@ describe('Linked pipeline', () => { }); it('should render an svg within the status container', () => { - const pipelineStatusElement = wrapper.find(CiStatus); + const pipelineStatusElement = wrapper.findComponent(CiStatus); expect(pipelineStatusElement.find('svg').exists()).toBe(true); }); @@ -90,7 +90,7 @@ describe('Linked pipeline', () => { }); it('should have a ci-status child component', () => { - expect(wrapper.find(CiStatus).exists()).toBe(true); + expect(wrapper.findComponent(CiStatus).exists()).toBe(true); }); it('should render the pipeline id', () => { @@ -214,7 +214,7 @@ describe('Linked pipeline', () => { await findRetryButton().trigger('click'); }); - it('calls the retry mutation ', () => { + it('calls the retry mutation', () => { expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1); expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ mutation: RetryPipelineMutation, @@ -255,7 +255,7 @@ describe('Linked pipeline', () => { createWrapper({ propsData: cancelablePipeline }); }); - it('shows only the cancel button ', () => { + it('shows only the cancel button', () => { expect(findCancelButton().exists()).toBe(true); expect(findRetryButton().exists()).toBe(false); }); @@ -375,7 +375,7 @@ describe('Linked pipeline', () => { ${'mouseover'} | ${'mouseout'} ${'focus'} | ${'blur'} `( - 'applies the class on $activateEventName and removes it on $deactivateEventName ', + 'applies the class on $activateEventName and removes it on $deactivateEventName', async ({ activateEventName, deactivateEventName }) => { const shadowClass = 'gl-shadow-none!'; diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js index 46000711110..63e2d8707ea 100644 --- a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js +++ b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js @@ -38,8 +38,8 @@ describe('Linked Pipelines Column', () => { let wrapper; const findLinkedColumnTitle = () => wrapper.find('[data-testid="linked-column-title"]'); - const findLinkedPipelineElements = () => wrapper.findAll(LinkedPipeline); - const findPipelineGraph = () => wrapper.find(PipelineGraph); + const findLinkedPipelineElements = () => wrapper.findAllComponents(LinkedPipeline); + const findPipelineGraph = () => wrapper.findComponent(PipelineGraph); const findExpandButton = () => wrapper.find('[data-testid="expand-pipeline-button"]'); Vue.use(VueApollo); diff --git a/spec/frontend/pipelines/graph/mock_data.js b/spec/frontend/pipelines/graph/mock_data.js index 959bbcefc98..6124d67af09 100644 --- a/spec/frontend/pipelines/graph/mock_data.js +++ b/spec/frontend/pipelines/graph/mock_data.js @@ -1038,245 +1038,3 @@ export const triggerJob = { action: null, }, }; - -export const mockPerformanceInsightsResponse = { - data: { - project: { - __typename: 'Project', - id: 'gid://gitlab/Project/20', - pipeline: { - __typename: 'Pipeline', - id: 'gid://gitlab/Ci::Pipeline/97', - jobs: { - __typename: 'CiJobConnection', - pageInfo: { - __typename: 'PageInfo', - hasNextPage: false, - }, - nodes: [ - { - __typename: 'CiJob', - id: 'gid://gitlab/Ci::Bridge/2502', - duration: null, - detailedStatus: { - __typename: 'DetailedStatus', - id: 'success-2502-2502', - detailsPath: '/root/lots-of-jobs-project/-/pipelines/98', - }, - name: 'trigger_job', - stage: { - __typename: 'CiStage', - id: 'gid://gitlab/Ci::Stage/303', - name: 'deploy', - }, - startedAt: null, - queuedDuration: 424850.376278, - }, - { - __typename: 'CiJob', - id: 'gid://gitlab/Ci::Build/2501', - duration: 10, - detailedStatus: { - __typename: 'DetailedStatus', - id: 'success-2501-2501', - detailsPath: '/root/ci-project/-/jobs/2501', - }, - name: 'artifact_job', - stage: { - __typename: 'CiStage', - id: 'gid://gitlab/Ci::Stage/303', - name: 'deploy', - }, - startedAt: '2022-07-01T16:31:41Z', - queuedDuration: 2.621553, - }, - { - __typename: 'CiJob', - id: 'gid://gitlab/Ci::Build/2500', - duration: 4, - detailedStatus: { - __typename: 'DetailedStatus', - id: 'success-2500-2500', - detailsPath: '/root/ci-project/-/jobs/2500', - }, - name: 'coverage_job', - stage: { - __typename: 'CiStage', - id: 'gid://gitlab/Ci::Stage/302', - name: 'test', - }, - startedAt: '2022-07-01T16:31:33Z', - queuedDuration: 14.388869, - }, - { - __typename: 'CiJob', - id: 'gid://gitlab/Ci::Build/2499', - duration: 4, - detailedStatus: { - __typename: 'DetailedStatus', - id: 'success-2499-2499', - detailsPath: '/root/ci-project/-/jobs/2499', - }, - name: 'test_job_two', - stage: { - __typename: 'CiStage', - id: 'gid://gitlab/Ci::Stage/302', - name: 'test', - }, - startedAt: '2022-07-01T16:31:28Z', - queuedDuration: 15.792664, - }, - { - __typename: 'CiJob', - id: 'gid://gitlab/Ci::Build/2498', - duration: 4, - detailedStatus: { - __typename: 'DetailedStatus', - id: 'success-2498-2498', - detailsPath: '/root/ci-project/-/jobs/2498', - }, - name: 'test_job_one', - stage: { - __typename: 'CiStage', - id: 'gid://gitlab/Ci::Stage/302', - name: 'test', - }, - startedAt: '2022-07-01T16:31:17Z', - queuedDuration: 8.317072, - }, - { - __typename: 'CiJob', - id: 'gid://gitlab/Ci::Build/2497', - duration: 5, - detailedStatus: { - __typename: 'DetailedStatus', - id: 'failed-2497-2497', - detailsPath: '/root/ci-project/-/jobs/2497', - }, - name: 'allow_failure_test_job', - stage: { - __typename: 'CiStage', - id: 'gid://gitlab/Ci::Stage/302', - name: 'test', - }, - startedAt: '2022-07-01T16:31:22Z', - queuedDuration: 3.547553, - }, - { - __typename: 'CiJob', - id: 'gid://gitlab/Ci::Build/2496', - duration: null, - detailedStatus: { - __typename: 'DetailedStatus', - id: 'manual-2496-2496', - detailsPath: '/root/ci-project/-/jobs/2496', - }, - name: 'test_manual_job', - stage: { - __typename: 'CiStage', - id: 'gid://gitlab/Ci::Stage/302', - name: 'test', - }, - startedAt: null, - queuedDuration: null, - }, - { - __typename: 'CiJob', - id: 'gid://gitlab/Ci::Build/2495', - duration: 5, - detailedStatus: { - __typename: 'DetailedStatus', - id: 'success-2495-2495', - detailsPath: '/root/ci-project/-/jobs/2495', - }, - name: 'large_log_output', - stage: { - __typename: 'CiStage', - id: 'gid://gitlab/Ci::Stage/301', - name: 'build', - }, - startedAt: '2022-07-01T16:31:11Z', - queuedDuration: 79.128625, - }, - { - __typename: 'CiJob', - id: 'gid://gitlab/Ci::Build/2494', - duration: 5, - detailedStatus: { - __typename: 'DetailedStatus', - id: 'success-2494-2494', - detailsPath: '/root/ci-project/-/jobs/2494', - }, - name: 'build_job', - stage: { - __typename: 'CiStage', - id: 'gid://gitlab/Ci::Stage/301', - name: 'build', - }, - startedAt: '2022-07-01T16:31:05Z', - queuedDuration: 73.286895, - }, - { - __typename: 'CiJob', - id: 'gid://gitlab/Ci::Build/2493', - duration: 16, - detailedStatus: { - __typename: 'DetailedStatus', - id: 'success-2493-2493', - detailsPath: '/root/ci-project/-/jobs/2493', - }, - name: 'wait_job', - stage: { - __typename: 'CiStage', - id: 'gid://gitlab/Ci::Stage/301', - name: 'build', - }, - startedAt: '2022-07-01T16:30:48Z', - queuedDuration: 56.258856, - }, - ], - }, - }, - }, - }, -}; - -export const mockPerformanceInsightsNextPageResponse = { - data: { - project: { - __typename: 'Project', - id: 'gid://gitlab/Project/20', - pipeline: { - __typename: 'Pipeline', - id: 'gid://gitlab/Ci::Pipeline/97', - jobs: { - __typename: 'CiJobConnection', - pageInfo: { - __typename: 'PageInfo', - hasNextPage: true, - }, - nodes: [ - { - __typename: 'CiJob', - id: 'gid://gitlab/Ci::Bridge/2502', - duration: null, - detailedStatus: { - __typename: 'DetailedStatus', - id: 'success-2502-2502', - detailsPath: '/root/lots-of-jobs-project/-/pipelines/98', - }, - name: 'trigger_job', - stage: { - __typename: 'CiStage', - id: 'gid://gitlab/Ci::Stage/303', - name: 'deploy', - }, - startedAt: null, - queuedDuration: 424850.376278, - }, - ], - }, - }, - }, - }, -}; diff --git a/spec/frontend/pipelines/graph/stage_column_component_spec.js b/spec/frontend/pipelines/graph/stage_column_component_spec.js index 99e8ea9d0a4..19f597a7267 100644 --- a/spec/frontend/pipelines/graph/stage_column_component_spec.js +++ b/spec/frontend/pipelines/graph/stage_column_component_spec.js @@ -42,8 +42,8 @@ describe('stage column component', () => { const findStageColumnTitle = () => wrapper.find('[data-testid="stage-column-title"]'); const findStageColumnGroup = () => wrapper.find('[data-testid="stage-column-group"]'); const findAllStageColumnGroups = () => wrapper.findAll('[data-testid="stage-column-group"]'); - const findJobItem = () => wrapper.find(JobItem); - const findActionComponent = () => wrapper.find(ActionComponent); + const findJobItem = () => wrapper.findComponent(JobItem); + const findActionComponent = () => wrapper.findComponent(ActionComponent); const createComponent = ({ method = shallowMount, props = {} } = {}) => { wrapper = method(StageColumnComponent, { @@ -126,9 +126,9 @@ describe('stage column component', () => { }); }); - it('capitalizes and escapes name', () => { - expect(findStageColumnTitle().text()).toBe( - 'Test <img src=x onerror=alert(document.domain)>', + it('escapes name', () => { + expect(findStageColumnTitle().html()).toContain( + 'test <img src=x onerror=alert(document.domain)>', ); }); diff --git a/spec/frontend/pipelines/graph_shared/links_layer_spec.js b/spec/frontend/pipelines/graph_shared/links_layer_spec.js index 44ab60cbee7..e2699d6ff2e 100644 --- a/spec/frontend/pipelines/graph_shared/links_layer_spec.js +++ b/spec/frontend/pipelines/graph_shared/links_layer_spec.js @@ -6,7 +6,7 @@ import { generateResponse, mockPipelineResponse } from '../graph/mock_data'; describe('links layer component', () => { let wrapper; - const findLinksInner = () => wrapper.find(LinksInner); + const findLinksInner = () => wrapper.findComponent(LinksInner); const pipeline = generateResponse(mockPipelineResponse, 'root/fungi-xoxo'); const containerId = `pipeline-links-container-${pipeline.id}`; diff --git a/spec/frontend/pipelines/header_component_spec.js b/spec/frontend/pipelines/header_component_spec.js index 859be8d342c..e583c0798f5 100644 --- a/spec/frontend/pipelines/header_component_spec.js +++ b/spec/frontend/pipelines/header_component_spec.js @@ -21,12 +21,12 @@ describe('Pipeline details header', () => { let glModalDirective; let mutate = jest.fn(); - const findAlert = () => wrapper.find(GlAlert); - const findDeleteModal = () => wrapper.find(GlModal); + const findAlert = () => wrapper.findComponent(GlAlert); + const findDeleteModal = () => wrapper.findComponent(GlModal); const findRetryButton = () => wrapper.find('[data-testid="retryPipeline"]'); const findCancelButton = () => wrapper.find('[data-testid="cancelPipeline"]'); const findDeleteButton = () => wrapper.find('[data-testid="deletePipeline"]'); - const findLoadingIcon = () => wrapper.find(GlLoadingIcon); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const defaultProvideOptions = { pipelineId: '14', diff --git a/spec/frontend/pipelines/performance_insights_modal_spec.js b/spec/frontend/pipelines/performance_insights_modal_spec.js deleted file mode 100644 index 8c802be7718..00000000000 --- a/spec/frontend/pipelines/performance_insights_modal_spec.js +++ /dev/null @@ -1,131 +0,0 @@ -import { GlAlert, GlLink, GlModal } from '@gitlab/ui'; -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import waitForPromises from 'helpers/wait_for_promises'; -import PerformanceInsightsModal from '~/pipelines/components/performance_insights_modal.vue'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import { trimText } from 'helpers/text_helper'; -import getPerformanceInsights from '~/pipelines/graphql/queries/get_performance_insights.query.graphql'; -import { - mockPerformanceInsightsResponse, - mockPerformanceInsightsNextPageResponse, -} from './graph/mock_data'; - -Vue.use(VueApollo); - -describe('Performance insights modal', () => { - let wrapper; - - const findModal = () => wrapper.findComponent(GlModal); - const findAlert = () => wrapper.findComponent(GlAlert); - const findLink = () => wrapper.findComponent(GlLink); - const findLimitText = () => wrapper.findByTestId('limit-alert-text'); - const findQueuedCardData = () => wrapper.findByTestId('insights-queued-card-data'); - const findQueuedCardLink = () => wrapper.findByTestId('insights-queued-card-link'); - const findExecutedCardData = () => wrapper.findByTestId('insights-executed-card-data'); - const findExecutedCardLink = () => wrapper.findByTestId('insights-executed-card-link'); - const findSlowJobsStage = (index) => wrapper.findAllByTestId('insights-slow-job-stage').at(index); - const findSlowJobsLink = (index) => wrapper.findAllByTestId('insights-slow-job-link').at(index); - - const getPerformanceInsightsHandler = jest - .fn() - .mockResolvedValue(mockPerformanceInsightsResponse); - - const getPerformanceInsightsNextPageHandler = jest - .fn() - .mockResolvedValue(mockPerformanceInsightsNextPageResponse); - - const requestHandlers = [[getPerformanceInsights, getPerformanceInsightsHandler]]; - - const createComponent = (handlers = requestHandlers) => { - wrapper = shallowMountExtended(PerformanceInsightsModal, { - provide: { - pipelineIid: '1', - pipelineProjectPath: 'root/ci-project', - }, - apolloProvider: createMockApollo(handlers), - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - describe('without next page', () => { - beforeEach(async () => { - createComponent(); - - await waitForPromises(); - }); - - it('displays modal', () => { - expect(findModal().exists()).toBe(true); - }); - - it('displays alert', () => { - expect(findAlert().exists()).toBe(true); - }); - - it('displays feedback issue link', () => { - expect(findLink().text()).toBe('Feedback issue'); - expect(findLink().attributes('href')).toBe( - 'https://gitlab.com/gitlab-org/gitlab/-/issues/365902', - ); - }); - - it('does not display limit text', () => { - expect(findLimitText().exists()).toBe(false); - }); - - describe('queued duration card', () => { - it('displays card data', () => { - expect(trimText(findQueuedCardData().text())).toBe('4.9 days'); - }); - it('displays card link', () => { - expect(findQueuedCardLink().attributes('href')).toBe( - '/root/lots-of-jobs-project/-/pipelines/98', - ); - }); - }); - - describe('executed duration card', () => { - it('displays card data', () => { - expect(trimText(findExecutedCardData().text())).toBe('trigger_job'); - }); - it('displays card link', () => { - expect(findExecutedCardLink().attributes('href')).toBe( - '/root/lots-of-jobs-project/-/pipelines/98', - ); - }); - }); - - describe('slow jobs', () => { - it.each` - index | expectedStage | expectedName | expectedLink - ${0} | ${'build'} | ${'wait_job'} | ${'/root/ci-project/-/jobs/2493'} - ${1} | ${'deploy'} | ${'artifact_job'} | ${'/root/ci-project/-/jobs/2501'} - ${2} | ${'test'} | ${'allow_failure_test_job'} | ${'/root/ci-project/-/jobs/2497'} - ${3} | ${'build'} | ${'large_log_output'} | ${'/root/ci-project/-/jobs/2495'} - ${4} | ${'build'} | ${'build_job'} | ${'/root/ci-project/-/jobs/2494'} - `( - 'should display slow job correctly', - ({ index, expectedStage, expectedName, expectedLink }) => { - expect(findSlowJobsStage(index).text()).toBe(expectedStage); - expect(findSlowJobsLink(index).text()).toBe(expectedName); - expect(findSlowJobsLink(index).attributes('href')).toBe(expectedLink); - }, - ); - }); - }); - - describe('with next page', () => { - it('displays limit text when there is a next page', async () => { - createComponent([[getPerformanceInsights, getPerformanceInsightsNextPageHandler]]); - - await waitForPromises(); - - expect(findLimitText().exists()).toBe(true); - }); - }); -}); diff --git a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js index 1b89e322d31..d9199f3b0f7 100644 --- a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js +++ b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js @@ -34,7 +34,7 @@ describe('pipeline graph component', () => { }; const findAlert = () => wrapper.findComponent(GlAlert); - const findAllJobPills = () => wrapper.findAll(JobPill); + const findAllJobPills = () => wrapper.findAllComponents(JobPill); const findAllStageNames = () => wrapper.findAllComponents(StageName); const findLinksLayer = () => wrapper.findComponent(LinksLayer); const findPipelineGraph = () => wrapper.find('[data-testid="graph-container"]'); diff --git a/spec/frontend/pipelines/pipeline_multi_actions_spec.js b/spec/frontend/pipelines/pipeline_multi_actions_spec.js index f554166da33..149b40330e2 100644 --- a/spec/frontend/pipelines/pipeline_multi_actions_spec.js +++ b/spec/frontend/pipelines/pipeline_multi_actions_spec.js @@ -1,12 +1,14 @@ import { GlAlert, GlDropdown, GlSprintf, GlLoadingIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; +import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; import PipelineMultiActions, { i18n, } from '~/pipelines/components/pipelines_list/pipeline_multi_actions.vue'; +import { TRACKING_CATEGORIES } from '~/pipelines/constants'; describe('Pipeline Multi Actions Dropdown', () => { let wrapper; @@ -136,4 +138,22 @@ describe('Pipeline Multi Actions Dropdown', () => { }); }); }); + + describe('tracking', () => { + afterEach(() => { + unmockTracking(); + }); + + it('tracks artifacts dropdown click', () => { + const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + + createComponent(); + + findDropdown().vm.$emit('show'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_artifacts_dropdown', { + label: TRACKING_CATEGORIES.table, + }); + }); + }); }); diff --git a/spec/frontend/pipelines/pipeline_url_spec.js b/spec/frontend/pipelines/pipeline_url_spec.js index 25a97ecf49d..1d66607e72b 100644 --- a/spec/frontend/pipelines/pipeline_url_spec.js +++ b/spec/frontend/pipelines/pipeline_url_spec.js @@ -1,12 +1,15 @@ +import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import PipelineUrlComponent from '~/pipelines/components/pipelines_list/pipeline_url.vue'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; +import { TRACKING_CATEGORIES } from '~/pipelines/constants'; import { mockPipeline, mockPipelineBranch, mockPipelineTag } from './mock_data'; const projectPath = 'test/test'; describe('Pipeline Url Component', () => { let wrapper; + let trackingSpy; const findTableCell = () => wrapper.findByTestId('pipeline-url-table-cell'); const findPipelineUrlLink = () => wrapper.findByTestId('pipeline-url-link'); @@ -14,6 +17,7 @@ describe('Pipeline Url Component', () => { const findCommitShortSha = () => wrapper.findByTestId('commit-short-sha'); const findCommitIcon = () => wrapper.findByTestId('commit-icon'); const findCommitIconType = () => wrapper.findByTestId('commit-icon-type'); + const findCommitRefName = () => wrapper.findByTestId('commit-ref-name'); const findCommitTitleContainer = () => wrapper.findByTestId('commit-title-container'); const findCommitTitle = (commitWrapper) => commitWrapper.find('[data-testid="commit-title"]'); @@ -31,7 +35,6 @@ describe('Pipeline Url Component', () => { afterEach(() => { wrapper.destroy(); - wrapper = null; }); it('should render pipeline url table cell', () => { @@ -49,7 +52,7 @@ describe('Pipeline Url Component', () => { }); it('should render the commit title, commit reference and commit-short-sha', () => { - createComponent({}, true); + createComponent(); const commitWrapper = findCommitTitleContainer(); @@ -83,7 +86,7 @@ describe('Pipeline Url Component', () => { }); it('should render commit icon tooltip', () => { - createComponent({}, true); + createComponent(); expect(findCommitIcon().attributes('title')).toBe('Commit'); }); @@ -94,8 +97,68 @@ describe('Pipeline Url Component', () => { ${mockPipelineBranch()} | ${'Branch'} ${mockPipeline()} | ${'Merge Request'} `('should render tooltip $expectedTitle for commit icon type', ({ pipeline, expectedTitle }) => { - createComponent(pipeline, true); + createComponent(pipeline); expect(findCommitIconType().attributes('title')).toBe(expectedTitle); }); + + describe('tracking', () => { + beforeEach(() => { + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + }); + + afterEach(() => { + unmockTracking(); + }); + + it('tracks pipeline id click', () => { + createComponent(); + + findPipelineUrlLink().vm.$emit('click'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_pipeline_id', { + label: TRACKING_CATEGORIES.table, + }); + }); + + it('tracks merge request ref click', () => { + createComponent(); + + findRefName().vm.$emit('click'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_mr_ref', { + label: TRACKING_CATEGORIES.table, + }); + }); + + it('tracks commit ref name click', () => { + createComponent(mockPipelineBranch()); + + findCommitRefName().vm.$emit('click'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_commit_name', { + label: TRACKING_CATEGORIES.table, + }); + }); + + it('tracks commit title click', () => { + createComponent(mockPipelineBranch()); + + findCommitTitle(findCommitTitleContainer()).vm.$emit('click'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_commit_title', { + label: TRACKING_CATEGORIES.table, + }); + }); + + it('tracks commit short sha click', () => { + createComponent(mockPipelineBranch()); + + findCommitShortSha().vm.$emit('click'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_commit_sha', { + label: TRACKING_CATEGORIES.table, + }); + }); + }); }); diff --git a/spec/frontend/pipelines/pipelines_actions_spec.js b/spec/frontend/pipelines/pipelines_actions_spec.js index 9b2ee6b8278..fdfced38dca 100644 --- a/spec/frontend/pipelines/pipelines_actions_spec.js +++ b/spec/frontend/pipelines/pipelines_actions_spec.js @@ -2,6 +2,7 @@ import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import { nextTick } from 'vue'; +import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { TEST_HOST } from 'spec/test_constants'; import createFlash from '~/flash'; @@ -9,6 +10,7 @@ import axios from '~/lib/utils/axios_utils'; import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; import PipelinesManualActions from '~/pipelines/components/pipelines_list/pipelines_manual_actions.vue'; import GlCountdown from '~/vue_shared/components/gl_countdown.vue'; +import { TRACKING_CATEGORIES } from '~/pipelines/constants'; jest.mock('~/flash'); jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal', () => { @@ -29,9 +31,9 @@ describe('Pipelines Actions dropdown', () => { }); }; - const findDropdown = () => wrapper.find(GlDropdown); - const findAllDropdownItems = () => wrapper.findAll(GlDropdownItem); - const findAllCountdowns = () => wrapper.findAll(GlCountdown); + const findDropdown = () => wrapper.findComponent(GlDropdown); + const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); + const findAllCountdowns = () => wrapper.findAllComponents(GlCountdown); beforeEach(() => { mock = new MockAdapter(axios); @@ -96,6 +98,22 @@ describe('Pipelines Actions dropdown', () => { expect(createFlash).toHaveBeenCalledTimes(1); }); }); + + describe('tracking', () => { + afterEach(() => { + unmockTracking(); + }); + + it('tracks manual actions click', () => { + const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + + findDropdown().vm.$emit('shown'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_manual_actions', { + label: TRACKING_CATEGORIES.table, + }); + }); + }); }); describe('scheduled jobs', () => { diff --git a/spec/frontend/pipelines/pipelines_artifacts_spec.js b/spec/frontend/pipelines/pipelines_artifacts_spec.js index 2d876841e06..e3e54716a7b 100644 --- a/spec/frontend/pipelines/pipelines_artifacts_spec.js +++ b/spec/frontend/pipelines/pipelines_artifacts_spec.js @@ -30,8 +30,9 @@ describe('Pipelines Artifacts dropdown', () => { }; const findDropdown = () => wrapper.findComponent(GlDropdown); - const findFirstGlDropdownItem = () => wrapper.find(GlDropdownItem); - const findAllGlDropdownItems = () => wrapper.find(GlDropdown).findAll(GlDropdownItem); + const findFirstGlDropdownItem = () => wrapper.findComponent(GlDropdownItem); + const findAllGlDropdownItems = () => + wrapper.findComponent(GlDropdown).findAllComponents(GlDropdownItem); afterEach(() => { wrapper.destroy(); diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js index 0bed24e588e..cc2ff90de57 100644 --- a/spec/frontend/pipelines/pipelines_spec.js +++ b/spec/frontend/pipelines/pipelines_spec.js @@ -7,6 +7,7 @@ import { nextTick } from 'vue'; import mockPipelinesResponse from 'test_fixtures/pipelines/pipelines.json'; import setWindowLocation from 'helpers/set_window_location_helper'; import { TEST_HOST } from 'helpers/test_constants'; +import { mockTracking } from 'helpers/tracking_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import Api from '~/api'; @@ -16,7 +17,7 @@ import NavigationControls from '~/pipelines/components/pipelines_list/nav_contro import PipelinesComponent from '~/pipelines/components/pipelines_list/pipelines.vue'; import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue'; import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue'; -import { RAW_TEXT_WARNING } from '~/pipelines/constants'; +import { RAW_TEXT_WARNING, TRACKING_CATEGORIES } from '~/pipelines/constants'; import Store from '~/pipelines/stores/pipelines_store'; import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue'; import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; @@ -37,6 +38,7 @@ const mockPipelineWithStages = mockPipelinesResponse.pipelines.find( describe('Pipelines', () => { let wrapper; let mock; + let trackingSpy; const paths = { emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg', @@ -123,7 +125,7 @@ describe('Pipelines', () => { }); it('shows loading state when the app is loading', () => { - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); }); it('does not display tabs when the first request has not yet been made', () => { @@ -236,6 +238,8 @@ describe('Pipelines', () => { count: mockPipelinesResponse.count, }); + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + goToTab('finished'); await waitForPromises(); @@ -256,6 +260,12 @@ describe('Pipelines', () => { `${window.location.pathname}?scope=finished&page=1`, ); }); + + it('tracks tab change click', () => { + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_filter_tabs', { + label: TRACKING_CATEGORIES.tabs, + }); + }); }); describe('when the scope in the tab is empty', () => { @@ -375,7 +385,7 @@ describe('Pipelines', () => { const [firstPage, secondPage] = chunk(mockPipelinesResponse.pipelines, mockPageSize); const goToPage = (page) => { - findTablePagination().find(GlPagination).vm.$emit('input', page); + findTablePagination().findComponent(GlPagination).vm.$emit('input', page); }; beforeEach(async () => { @@ -583,7 +593,7 @@ describe('Pipelines', () => { 'This project is not currently set up to run pipelines.', ); - expect(findEmptyState().find(GlButton).exists()).toBe(false); + expect(findEmptyState().findComponent(GlButton).exists()).toBe(false); }); it('does not render tabs or buttons', () => { diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js index 7b49baa5a20..044683ce533 100644 --- a/spec/frontend/pipelines/pipelines_table_spec.js +++ b/spec/frontend/pipelines/pipelines_table_spec.js @@ -2,8 +2,9 @@ import '~/commons'; import { GlTableLite } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import fixture from 'test_fixtures/pipelines/pipelines.json'; +import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'; +import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue'; import PipelineOperations from '~/pipelines/components/pipelines_list/pipeline_operations.vue'; import PipelineTriggerer from '~/pipelines/components/pipelines_list/pipeline_triggerer.vue'; import PipelineUrl from '~/pipelines/components/pipelines_list/pipeline_url.vue'; @@ -13,6 +14,7 @@ import { PipelineKeyOptions, BUTTON_TOOLTIP_RETRY, BUTTON_TOOLTIP_CANCEL, + TRACKING_CATEGORIES, } from '~/pipelines/constants'; import eventHub from '~/pipelines/event_hub'; @@ -23,6 +25,7 @@ jest.mock('~/pipelines/event_hub'); describe('Pipelines Table', () => { let pipeline; let wrapper; + let trackingSpy; const defaultProps = { pipelines: [], @@ -69,6 +72,7 @@ describe('Pipelines Table', () => { afterEach(() => { wrapper.destroy(); + wrapper = null; }); @@ -96,10 +100,6 @@ describe('Pipelines Table', () => { it('should render a status badge', () => { expect(findStatusBadge().exists()).toBe(true); }); - - it('should render status badge with correct path', () => { - expect(findStatusBadge().attributes('href')).toBe(pipeline.path); - }); }); describe('pipeline cell', () => { @@ -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'); @@ -179,5 +167,47 @@ describe('Pipelines Table', () => { expect(findTriggerer().exists()).toBe(true); }); }); + + describe('tracking', () => { + beforeEach(() => { + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + }); + + afterEach(() => { + unmockTracking(); + }); + + it('tracks status badge click', () => { + findStatusBadge().vm.$emit('ciStatusBadgeClick'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_ci_status_badge', { + label: TRACKING_CATEGORIES.table, + }); + }); + + it('tracks retry pipeline button click', () => { + findRetryBtn().vm.$emit('click'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_retry_button', { + label: TRACKING_CATEGORIES.table, + }); + }); + + it('tracks cancel pipeline button click', () => { + findCancelBtn().vm.$emit('click'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_cancel_button', { + label: TRACKING_CATEGORIES.table, + }); + }); + + it('tracks pipeline mini graph stage click', () => { + findPipelineMiniGraph().vm.$emit('miniGraphStageClick'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_minigraph', { + label: TRACKING_CATEGORIES.table, + }); + }); + }); }); }); diff --git a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js index c372ac06c35..da13df833e7 100644 --- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js +++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js @@ -26,10 +26,10 @@ describe('Test reports suite table', () => { const noCasesMessage = () => wrapper.findByTestId('no-test-cases'); const artifactsExpiredMessage = () => wrapper.findByTestId('artifacts-expired'); - const artifactsExpiredEmptyState = () => wrapper.find(GlEmptyState); + const artifactsExpiredEmptyState = () => wrapper.findComponent(GlEmptyState); const allCaseRows = () => wrapper.findAllByTestId('test-case-row'); const findCaseRowAtIndex = (index) => wrapper.findAllByTestId('test-case-row').at(index); - const findLinkForRow = (row) => row.find(GlLink); + const findLinkForRow = (row) => row.findComponent(GlLink); const findIconForRow = (row, status) => row.find(`.ci-status-icon-${status}`); const createComponent = ({ suite = testSuite, perPage = 20, errorMessage } = {}) => { @@ -113,7 +113,7 @@ describe('Test reports suite table', () => { const filePath = `${blobPath}/${relativeFile}`; const row = findCaseRowAtIndex(0); const fileLink = findLinkForRow(row); - const button = row.find(GlButton); + const button = row.findComponent(GlButton); expect(fileLink.attributes('href')).toBe(filePath); expect(row.text()).toContain(file); @@ -134,7 +134,7 @@ describe('Test reports suite table', () => { }); it('renders a pagination component', () => { - expect(wrapper.find(GlPagination).exists()).toBe(true); + expect(wrapper.findComponent(GlPagination).exists()).toBe(true); }); }); diff --git a/spec/frontend/pipelines/test_reports/test_summary_table_spec.js b/spec/frontend/pipelines/test_reports/test_summary_table_spec.js index 0e1229f7067..cfe9ff564dc 100644 --- a/spec/frontend/pipelines/test_reports/test_summary_table_spec.js +++ b/spec/frontend/pipelines/test_reports/test_summary_table_spec.js @@ -44,7 +44,7 @@ describe('Test reports summary table', () => { describe('when test reports are supplied', () => { beforeEach(() => createComponent()); - const findErrorIcon = () => wrapper.find({ ref: 'suiteErrorIcon' }); + const findErrorIcon = () => wrapper.findComponent({ ref: 'suiteErrorIcon' }); it('renders the correct number of rows', () => { expect(noSuitesToShow().exists()).toBe(false); diff --git a/spec/frontend/pipelines/time_ago_spec.js b/spec/frontend/pipelines/time_ago_spec.js index 3de7995b476..f0da0df2ba6 100644 --- a/spec/frontend/pipelines/time_ago_spec.js +++ b/spec/frontend/pipelines/time_ago_spec.js @@ -48,7 +48,7 @@ describe('Timeago component', () => { }); it('should render duration and timer svg', () => { - const icon = duration().find(GlIcon); + const icon = duration().findComponent(GlIcon); expect(duration().exists()).toBe(true); expect(icon.props('name')).toBe('timer'); @@ -71,7 +71,7 @@ describe('Timeago component', () => { }); it('should render time and calendar icon', () => { - const icon = finishedAt().find(GlIcon); + const icon = finishedAt().findComponent(GlIcon); const time = finishedAt().find('time'); expect(finishedAt().exists()).toBe(true); diff --git a/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js index ba478363d04..caa66502e11 100644 --- a/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js +++ b/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js @@ -9,9 +9,10 @@ import { branches, mockBranchesAfterMap } from '../mock_data'; describe('Pipeline Branch Name Token', () => { let wrapper; - const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken); - const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion); - const findLoadingIcon = () => wrapper.find(GlLoadingIcon); + const findFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken); + const findAllFilteredSearchSuggestions = () => + wrapper.findAllComponents(GlFilteredSearchSuggestion); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const getBranchSuggestions = () => findAllFilteredSearchSuggestions().wrappers.map((w) => w.text()); diff --git a/spec/frontend/pipelines/tokens/pipeline_source_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_source_token_spec.js index b8abf2c1727..60abb63a7e0 100644 --- a/spec/frontend/pipelines/tokens/pipeline_source_token_spec.js +++ b/spec/frontend/pipelines/tokens/pipeline_source_token_spec.js @@ -7,8 +7,9 @@ import PipelineSourceToken from '~/pipelines/components/pipelines_list/tokens/pi describe('Pipeline Source Token', () => { let wrapper; - const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken); - const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion); + const findFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken); + const findAllFilteredSearchSuggestions = () => + wrapper.findAllComponents(GlFilteredSearchSuggestion); const defaultProps = { config: { diff --git a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js index 2c5fa8b00e2..94f9a37f707 100644 --- a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js +++ b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js @@ -6,9 +6,10 @@ import PipelineStatusToken from '~/pipelines/components/pipelines_list/tokens/pi describe('Pipeline Status Token', () => { let wrapper; - const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken); - const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion); - const findAllGlIcons = () => wrapper.findAll(GlIcon); + const findFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken); + const findAllFilteredSearchSuggestions = () => + wrapper.findAllComponents(GlFilteredSearchSuggestion); + const findAllGlIcons = () => wrapper.findAllComponents(GlIcon); const defaultProps = { config: { diff --git a/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js index 596a9218c39..7311a5d2f5a 100644 --- a/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js +++ b/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js @@ -7,9 +7,10 @@ import { tags, mockTagsAfterMap } from '../mock_data'; describe('Pipeline Branch Name Token', () => { let wrapper; - const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken); - const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion); - const findLoadingIcon = () => wrapper.find(GlLoadingIcon); + const findFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken); + const findAllFilteredSearchSuggestions = () => + wrapper.findAllComponents(GlFilteredSearchSuggestion); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const stubs = { GlFilteredSearchToken: { diff --git a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js index 397dbdf95a9..c763bfe1b27 100644 --- a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js +++ b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js @@ -8,9 +8,10 @@ import { users } from '../mock_data'; describe('Pipeline Trigger Author Token', () => { let wrapper; - const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken); - const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion); - const findLoadingIcon = () => wrapper.find(GlLoadingIcon); + const findFilteredSearchToken = () => wrapper.findComponent(GlFilteredSearchToken); + const findAllFilteredSearchSuggestions = () => + wrapper.findAllComponents(GlFilteredSearchSuggestion); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const defaultProps = { config: { diff --git a/spec/frontend/pipelines/utils_spec.js b/spec/frontend/pipelines/utils_spec.js index a82390fae22..1c23a7e4fcf 100644 --- a/spec/frontend/pipelines/utils_spec.js +++ b/spec/frontend/pipelines/utils_spec.js @@ -8,14 +8,10 @@ import { removeOrphanNodes, getMaxNodes, } from '~/pipelines/components/parsing_utils'; -import { createNodeDict, calculateJobStats, calculateSlowestFiveJobs } from '~/pipelines/utils'; +import { createNodeDict } from '~/pipelines/utils'; import { mockParsedGraphQLNodes, missingJob } from './components/dag/mock_data'; -import { - generateResponse, - mockPipelineResponse, - mockPerformanceInsightsResponse, -} from './graph/mock_data'; +import { generateResponse, mockPipelineResponse } from './graph/mock_data'; describe('DAG visualization parsing utilities', () => { const nodeDict = createNodeDict(mockParsedGraphQLNodes); @@ -162,40 +158,4 @@ describe('DAG visualization parsing utilities', () => { expect(columns).toMatchSnapshot(); }); }); - - describe('performance insights', () => { - const { - data: { - project: { - pipeline: { jobs }, - }, - }, - } = mockPerformanceInsightsResponse; - - describe('calculateJobStats', () => { - const expectedJob = jobs.nodes[0]; - - it('returns the job that spent this longest time queued', () => { - expect(calculateJobStats(jobs, 'queuedDuration')).toEqual(expectedJob); - }); - - it('returns the job that was executed last', () => { - expect(calculateJobStats(jobs, 'startedAt')).toEqual(expectedJob); - }); - }); - - describe('calculateSlowestFiveJobs', () => { - it('returns the slowest five jobs of the pipeline', () => { - const expectedJobs = [ - jobs.nodes[9], - jobs.nodes[1], - jobs.nodes[5], - jobs.nodes[7], - jobs.nodes[8], - ]; - - expect(calculateSlowestFiveJobs(jobs)).toEqual(expectedJobs); - }); - }); - }); }); |