diff options
Diffstat (limited to 'spec/frontend/pipelines/graph')
9 files changed, 176 insertions, 116 deletions
diff --git a/spec/frontend/pipelines/graph/action_component_spec.js b/spec/frontend/pipelines/graph/action_component_spec.js index ab477292bc1..95d96e127c6 100644 --- a/spec/frontend/pipelines/graph/action_component_spec.js +++ b/spec/frontend/pipelines/graph/action_component_spec.js @@ -33,7 +33,7 @@ describe('pipeline graph action component', () => { expect(wrapper.attributes('title')).toBe('bar'); }); - it('should update bootstrap tooltip when title changes', done => { + it('should update bootstrap tooltip when title changes', (done) => { wrapper.setProps({ tooltipText: 'changed' }); wrapper.vm @@ -51,7 +51,7 @@ describe('pipeline graph action component', () => { }); describe('on click', () => { - it('emits `pipelineActionRequestComplete` after a successful request', done => { + it('emits `pipelineActionRequestComplete` after a successful request', (done) => { jest.spyOn(wrapper.vm, '$emit'); findButton().trigger('click'); @@ -64,7 +64,7 @@ describe('pipeline graph action component', () => { .catch(done.fail); }); - it('renders a loading icon while waiting for request', done => { + it('renders a loading icon while waiting for request', (done) => { findButton().trigger('click'); wrapper.vm.$nextTick(() => { diff --git a/spec/frontend/pipelines/graph/graph_component_legacy_spec.js b/spec/frontend/pipelines/graph/graph_component_legacy_spec.js index 3b1909b6564..840b1f8baf5 100644 --- a/spec/frontend/pipelines/graph/graph_component_legacy_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_legacy_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import { nextTick } from 'vue'; import { mount } from '@vue/test-utils'; import { GlLoadingIcon } from '@gitlab/ui'; import { setHTMLFixture } from 'helpers/fixtures'; @@ -18,7 +18,7 @@ describe('graph component', () => { const findExpandPipelineBtn = () => wrapper.find('[data-testid="expand-pipeline-button"]'); const findAllExpandPipelineBtns = () => wrapper.findAll('[data-testid="expand-pipeline-button"]'); const findStageColumns = () => wrapper.findAll(StageColumnComponentLegacy); - const findStageColumnAt = i => findStageColumns().at(i); + const findStageColumnAt = (i) => findStageColumns().at(i); beforeEach(() => { mediator = new PipelinesMediator({ endpoint: '' }); @@ -104,11 +104,9 @@ describe('graph component', () => { }); it('should include the left-connector class in the build of the second child', () => { - expect( - findStageColumnAt(1) - .find('.build:nth-child(1)') - .classes('left-connector'), - ).toBe(true); + expect(findStageColumnAt(1).find('.build:nth-child(1)').classes('left-connector')).toBe( + true, + ); }); it('should include the js-has-linked-pipelines flag', () => { @@ -119,12 +117,7 @@ describe('graph component', () => { describe('computeds and methods', () => { describe('capitalizeStageName', () => { it('it capitalizes the stage name', () => { - expect( - wrapper - .findAll('.stage-column .stage-name') - .at(1) - .text(), - ).toBe('Prebuild'); + expect(wrapper.findAll('.stage-column .stage-name').at(1).text()).toBe('Prebuild'); }); }); @@ -160,21 +153,20 @@ describe('graph component', () => { describe('triggered by', () => { describe('on click', () => { - it('should emit `onClickUpstreamPipeline` when triggered by linked pipeline is clicked', () => { + it('should emit `onClickUpstreamPipeline` when triggered by linked pipeline is clicked', async () => { const btnWrapper = findExpandPipelineBtn(); btnWrapper.trigger('click'); - btnWrapper.vm.$nextTick(() => { - expect(wrapper.emitted().onClickUpstreamPipeline).toEqual([ - store.state.pipeline.triggered_by, - ]); - }); + await nextTick(); + expect(wrapper.emitted().onClickUpstreamPipeline).toEqual([ + store.state.pipeline.triggered_by, + ]); }); }); describe('with expanded pipeline', () => { - it('should render expanded pipeline', done => { + it('should render expanded pipeline', async () => { // expand the pipeline store.state.pipeline.triggered_by[0].isExpanded = true; @@ -186,40 +178,46 @@ describe('graph component', () => { }, }); - Vue.nextTick() - .then(() => { - expect(wrapper.find('.js-upstream-pipeline-12').exists()).toBe(true); - }) - .then(done) - .catch(done.fail); + await nextTick(); + expect(wrapper.find('.js-upstream-pipeline-12').exists()).toBe(true); }); }); }); describe('triggered', () => { describe('on click', () => { - it('should emit `onClickTriggered`', () => { - // We have to mock this method since we do both style change and - // emit and event, not mocking returns an error. - wrapper.setMethods({ - handleClickedDownstream: jest.fn(() => - wrapper.vm.$emit('onClickTriggered', ...store.state.pipeline.triggered), - ), + // We have to mock this property of HTMLElement since component relies on it + let offsetParentDescriptor; + beforeAll(() => { + offsetParentDescriptor = Object.getOwnPropertyDescriptor( + HTMLElement.prototype, + 'offsetParent', + ); + Object.defineProperty(HTMLElement.prototype, 'offsetParent', { + get() { + return this.parentNode; + }, }); + }); + afterAll(() => { + Object.defineProperty(HTMLElement.prototype, offsetParentDescriptor); + }); + it('should emit `onClickDownstreamPipeline`', async () => { const btnWrappers = findAllExpandPipelineBtns(); const downstreamBtnWrapper = btnWrappers.at(btnWrappers.length - 1); downstreamBtnWrapper.trigger('click'); - downstreamBtnWrapper.vm.$nextTick(() => { - expect(wrapper.emitted().onClickTriggered).toEqual([store.state.pipeline.triggered]); - }); + await nextTick(); + expect(wrapper.emitted().onClickDownstreamPipeline).toEqual([ + [store.state.pipeline.triggered[1]], + ]); }); }); describe('with expanded pipeline', () => { - it('should render expanded pipeline', done => { + it('should render expanded pipeline', async () => { // expand the pipeline store.state.pipeline.triggered[0].isExpanded = true; @@ -231,12 +229,8 @@ describe('graph component', () => { }, }); - Vue.nextTick() - .then(() => { - expect(wrapper.find('.js-downstream-pipeline-34993051')).not.toBeNull(); - }) - .then(done) - .catch(done.fail); + await nextTick(); + expect(wrapper.find('.js-downstream-pipeline-34993051')).not.toBeNull(); }); }); diff --git a/spec/frontend/pipelines/graph/graph_component_spec.js b/spec/frontend/pipelines/graph/graph_component_spec.js index 7572dd83798..cfc3b7af282 100644 --- a/spec/frontend/pipelines/graph/graph_component_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_spec.js @@ -1,7 +1,9 @@ import { mount, shallowMount } from '@vue/test-utils'; import PipelineGraph from '~/pipelines/components/graph/graph_component.vue'; import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue'; +import JobItem from '~/pipelines/components/graph/job_item.vue'; import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue'; +import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue'; import { GRAPHQL } from '~/pipelines/components/graph/constants'; import { generateResponse, @@ -13,21 +15,37 @@ describe('graph component', () => { let wrapper; const findLinkedColumns = () => wrapper.findAll(LinkedPipelinesColumn); + const findLinksLayer = () => wrapper.find(LinksLayer); const findStageColumns = () => wrapper.findAll(StageColumnComponent); const defaultProps = { pipeline: generateResponse(mockPipelineResponse, 'root/fungi-xoxo'), }; - const createComponent = ({ mountFn = shallowMount, props = {} } = {}) => { + const createComponent = ({ + data = {}, + mountFn = shallowMount, + props = {}, + stubOverride = {}, + } = {}) => { wrapper = mountFn(PipelineGraph, { propsData: { ...defaultProps, ...props, }, + data() { + return { ...data }; + }, provide: { dataMethod: GRAPHQL, }, + stubs: { + 'links-inner': true, + 'linked-pipeline': true, + 'job-item': true, + 'job-group-dropdown': true, + ...stubOverride, + }, }); }; @@ -45,17 +63,36 @@ describe('graph component', () => { expect(findStageColumns()).toHaveLength(defaultProps.pipeline.stages.length); }); + it('renders the links layer', () => { + expect(findLinksLayer().exists()).toBe(true); + }); + describe('when column requests a refresh', () => { beforeEach(() => { - findStageColumns() - .at(0) - .vm.$emit('refreshPipelineGraph'); + findStageColumns().at(0).vm.$emit('refreshPipelineGraph'); }); it('refreshPipelineGraph is emitted', () => { expect(wrapper.emitted().refreshPipelineGraph).toHaveLength(1); }); }); + + describe('when links are present', () => { + beforeEach(async () => { + createComponent({ + mountFn: mount, + stubOverride: { 'job-item': false }, + data: { hoveredJobName: 'test_a' }, + }); + findLinksLayer().vm.$emit('highlightedJobsChange', ['test_c', 'build_c']); + }); + + it('dims unrelated jobs', () => { + const unrelatedJob = wrapper.find(JobItem); + expect(findLinksLayer().emitted().highlightedJobsChange).toHaveLength(1); + expect(unrelatedJob.classes('gl-opacity-3')).toBe(true); + }); + }); }); describe('when linked pipelines are not present', () => { diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js index 875aaa48037..54593c527cb 100644 --- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js @@ -2,10 +2,10 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import { shallowMount } from '@vue/test-utils'; import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; -import createMockApollo from 'jest/helpers/mock_apollo_helper'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql'; import PipelineGraphWrapper from '~/pipelines/components/graph/graph_component_wrapper.vue'; import PipelineGraph from '~/pipelines/components/graph/graph_component.vue'; -import getPipelineDetails from '~/pipelines/graphql/queries/get_pipeline_details.query.graphql'; import { mockPipelineResponse } from './mock_data'; const defaultProvide = { diff --git a/spec/frontend/pipelines/graph/job_item_spec.js b/spec/frontend/pipelines/graph/job_item_spec.js index 8aabb2f9cdd..cb2837cbb39 100644 --- a/spec/frontend/pipelines/graph/job_item_spec.js +++ b/spec/frontend/pipelines/graph/job_item_spec.js @@ -7,7 +7,7 @@ describe('pipeline graph job item', () => { const findJobWithoutLink = () => wrapper.find('[data-testid="job-without-link"]'); const findJobWithLink = () => wrapper.find('[data-testid="job-with-link"]'); - const createWrapper = propsData => { + const createWrapper = (propsData) => { wrapper = mount(JobItem, { propsData, }); @@ -52,7 +52,7 @@ describe('pipeline graph job item', () => { }); describe('name with link', () => { - it('should render the job name and status with a link', done => { + it('should render the job name and status with a link', (done) => { createWrapper({ job: mockJob }); wrapper.vm.$nextTick(() => { diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js index 37eb5f900dd..6db152f2607 100644 --- a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js +++ b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js @@ -1,10 +1,10 @@ import VueApollo from 'vue-apollo'; import { mount, shallowMount, createLocalVue } from '@vue/test-utils'; -import createMockApollo from 'jest/helpers/mock_apollo_helper'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql'; import PipelineGraph from '~/pipelines/components/graph/graph_component.vue'; import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue'; import LinkedPipeline from '~/pipelines/components/graph/linked_pipeline.vue'; -import getPipelineDetails from '~/pipelines/graphql/queries/get_pipeline_details.query.graphql'; import { DOWNSTREAM, GRAPHQL } from '~/pipelines/components/graph/constants'; import { LOAD_FAILURE } from '~/pipelines/constants'; import { diff --git a/spec/frontend/pipelines/graph/mock_data.js b/spec/frontend/pipelines/graph/mock_data.js index d53a11eea0e..7650cbd2d5c 100644 --- a/spec/frontend/pipelines/graph/mock_data.js +++ b/spec/frontend/pipelines/graph/mock_data.js @@ -56,7 +56,7 @@ export const mockPipelineResponse = { }, }, needs: { - __typename: 'CiJobConnection', + __typename: 'CiBuildNeedConnection', nodes: [], }, }, @@ -96,7 +96,7 @@ export const mockPipelineResponse = { }, }, needs: { - __typename: 'CiJobConnection', + __typename: 'CiBuildNeedConnection', nodes: [], }, }, @@ -136,7 +136,7 @@ export const mockPipelineResponse = { }, }, needs: { - __typename: 'CiJobConnection', + __typename: 'CiBuildNeedConnection', nodes: [], }, }, @@ -176,7 +176,7 @@ export const mockPipelineResponse = { }, }, needs: { - __typename: 'CiJobConnection', + __typename: 'CiBuildNeedConnection', nodes: [], }, }, @@ -200,7 +200,7 @@ export const mockPipelineResponse = { }, }, needs: { - __typename: 'CiJobConnection', + __typename: 'CiBuildNeedConnection', nodes: [], }, }, @@ -224,7 +224,7 @@ export const mockPipelineResponse = { }, }, needs: { - __typename: 'CiJobConnection', + __typename: 'CiBuildNeedConnection', nodes: [], }, }, @@ -277,18 +277,18 @@ export const mockPipelineResponse = { }, }, needs: { - __typename: 'CiJobConnection', + __typename: 'CiBuildNeedConnection', nodes: [ { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_c', }, { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_b', }, { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl', }, @@ -331,26 +331,26 @@ export const mockPipelineResponse = { }, }, needs: { - __typename: 'CiJobConnection', + __typename: 'CiBuildNeedConnection', nodes: [ { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_d 3/3', }, { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_d 2/3', }, { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_d 1/3', }, { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_b', }, { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl', }, @@ -377,26 +377,26 @@ export const mockPipelineResponse = { }, }, needs: { - __typename: 'CiJobConnection', + __typename: 'CiBuildNeedConnection', nodes: [ { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_d 3/3', }, { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_d 2/3', }, { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_d 1/3', }, { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_b', }, { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl', }, @@ -433,18 +433,18 @@ export const mockPipelineResponse = { action: null, }, needs: { - __typename: 'CiJobConnection', + __typename: 'CiBuildNeedConnection', nodes: [ { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_c', }, { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_b', }, { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_a_nlfjkdnlvskfnksvjknlfdjvlvnjdkjdf_nvjkenjkrlngjeknjkl', }, @@ -481,10 +481,10 @@ export const mockPipelineResponse = { action: null, }, needs: { - __typename: 'CiJobConnection', + __typename: 'CiBuildNeedConnection', nodes: [ { - __typename: 'CiJob', + __typename: 'CiBuildNeed', name: 'build_b', }, ], @@ -578,41 +578,54 @@ export const upstream = { export const wrappedPipelineReturn = { data: { project: { + __typename: 'Project', pipeline: { + __typename: 'Pipeline', id: 'gid://gitlab/Ci::Pipeline/175', iid: '38', downstream: { + __typename: 'PipelineConnection', nodes: [], }, upstream: { id: 'gid://gitlab/Ci::Pipeline/174', iid: '37', path: '/root/elemenohpee/-/pipelines/174', + __typename: 'Pipeline', status: { + __typename: 'DetailedStatus', group: 'success', label: 'passed', icon: 'status_success', }, sourceJob: { name: 'test_c', + __typename: 'CiJob', }, project: { id: 'gid://gitlab/Project/25', name: 'elemenohpee', fullPath: 'root/elemenohpee', + __typename: 'Project', }, }, stages: { + __typename: 'CiStageConnection', nodes: [ { name: 'build', + __typename: 'CiStage', status: { action: null, + __typename: 'DetailedStatus', }, groups: { + __typename: 'CiGroupConnection', nodes: [ { + __typename: 'CiGroup', status: { + __typename: 'DetailedStatus', label: 'passed', group: 'success', icon: 'status_success', @@ -620,20 +633,25 @@ export const wrappedPipelineReturn = { name: 'build_n', size: 1, jobs: { + __typename: 'CiJobConnection', nodes: [ { + __typename: 'CiJob', name: 'build_n', scheduledAt: null, needs: { + __typename: 'CiBuildNeedConnection', nodes: [], }, status: { + __typename: 'DetailedStatus', icon: 'status_success', tooltip: 'passed', hasDetails: true, detailsPath: '/root/elemenohpee/-/jobs/1662', group: 'success', action: { + __typename: 'StatusAction', buttonTitle: 'Retry this job', icon: 'retry', path: '/root/elemenohpee/-/jobs/1662/retry', @@ -656,7 +674,7 @@ export const wrappedPipelineReturn = { export const generateResponse = (raw, mockPath) => unwrapPipelineData(mockPath, raw.data); -export const pipelineWithUpstreamDownstream = base => { +export const pipelineWithUpstreamDownstream = (base) => { const pip = { ...base }; pip.data.project.pipeline.downstream = downstream; pip.data.project.pipeline.upstream = upstream; diff --git a/spec/frontend/pipelines/graph/stage_column_component_legacy_spec.js b/spec/frontend/pipelines/graph/stage_column_component_legacy_spec.js index 463e4c12c7d..2965325ea7c 100644 --- a/spec/frontend/pipelines/graph/stage_column_component_legacy_spec.js +++ b/spec/frontend/pipelines/graph/stage_column_component_legacy_spec.js @@ -40,12 +40,7 @@ describe('stage column component', () => { }); it('should render provided title', () => { - expect( - wrapper - .find('.stage-name') - .text() - .trim(), - ).toBe('foo'); + expect(wrapper.find('.stage-name').text().trim()).toBe('foo'); }); it('should render the provided groups', () => { diff --git a/spec/frontend/pipelines/graph/stage_column_component_spec.js b/spec/frontend/pipelines/graph/stage_column_component_spec.js index 44803929f6d..202e25ccda3 100644 --- a/spec/frontend/pipelines/graph/stage_column_component_spec.js +++ b/spec/frontend/pipelines/graph/stage_column_component_spec.js @@ -30,6 +30,7 @@ const mockGroups = Array(4) const defaultProps = { title: 'Fish', groups: mockGroups, + pipelineId: 159, }; describe('stage column component', () => { @@ -92,36 +93,51 @@ describe('stage column component', () => { }); describe('job', () => { - beforeEach(() => { - createComponent({ - method: mount, - props: { - groups: [ - { - id: 4259, - name: '<img src=x onerror=alert(document.domain)>', - status: { - icon: 'status_success', - label: 'success', - tooltip: '<img src=x onerror=alert(document.domain)>', + describe('text handling', () => { + beforeEach(() => { + createComponent({ + method: mount, + props: { + groups: [ + { + id: 4259, + name: '<img src=x onerror=alert(document.domain)>', + status: { + icon: 'status_success', + label: 'success', + tooltip: '<img src=x onerror=alert(document.domain)>', + }, }, - }, - ], - title: 'test <img src=x onerror=alert(document.domain)>', - }, + ], + title: 'test <img src=x onerror=alert(document.domain)>', + }, + }); }); - }); - it('capitalizes and escapes name', () => { - expect(findStageColumnTitle().text()).toBe( - 'Test <img src=x onerror=alert(document.domain)>', - ); + it('capitalizes and escapes name', () => { + expect(findStageColumnTitle().text()).toBe( + 'Test <img src=x onerror=alert(document.domain)>', + ); + }); + + it('escapes id', () => { + expect(findStageColumnGroup().attributes('id')).toBe( + 'ci-badge-<img src=x onerror=alert(document.domain)>', + ); + }); }); - it('escapes id', () => { - expect(findStageColumnGroup().attributes('id')).toBe( - 'ci-badge-<img src=x onerror=alert(document.domain)>', - ); + describe('interactions', () => { + beforeEach(() => { + createComponent({ method: mount }); + }); + + it('emits jobHovered event on mouseenter and mouseleave', async () => { + await findStageColumnGroup().trigger('mouseenter'); + expect(wrapper.emitted().jobHover).toEqual([[defaultProps.groups[0].name]]); + await findStageColumnGroup().trigger('mouseleave'); + expect(wrapper.emitted().jobHover).toEqual([[defaultProps.groups[0].name], ['']]); + }); }); }); |