diff options
Diffstat (limited to 'spec/frontend/pipelines')
20 files changed, 249 insertions, 1161 deletions
diff --git a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js index b0dbba37b94..e0ba6b2e8da 100644 --- a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js +++ b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js @@ -20,6 +20,7 @@ describe('Pipelines filtered search', () => { const findTagToken = () => getSearchToken('tag'); const findUserToken = () => getSearchToken('username'); const findStatusToken = () => getSearchToken('status'); + const findSourceToken = () => getSearchToken('source'); const createComponent = (params = {}) => { wrapper = mount(PipelinesFilteredSearch, { @@ -32,6 +33,8 @@ describe('Pipelines filtered search', () => { }; beforeEach(() => { + window.gon = { features: { pipelineSourceFilter: true } }; + mock = new MockAdapter(axios); jest.spyOn(Api, 'projectUsers').mockResolvedValue(users); @@ -70,6 +73,14 @@ describe('Pipelines filtered search', () => { operators: OPERATOR_IS_ONLY, }); + expect(findSourceToken()).toMatchObject({ + type: 'source', + icon: 'trigger-source', + title: 'Source', + unique: true, + operators: OPERATOR_IS_ONLY, + }); + expect(findStatusToken()).toMatchObject({ type: 'status', icon: 'status', diff --git a/spec/frontend/pipelines/graph/graph_component_legacy_spec.js b/spec/frontend/pipelines/graph/graph_component_legacy_spec.js deleted file mode 100644 index a955572a481..00000000000 --- a/spec/frontend/pipelines/graph/graph_component_legacy_spec.js +++ /dev/null @@ -1,300 +0,0 @@ -import { GlLoadingIcon } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import { setHTMLFixture } from 'helpers/fixtures'; -import GraphComponentLegacy from '~/pipelines/components/graph/graph_component_legacy.vue'; -import LinkedPipelinesColumnLegacy from '~/pipelines/components/graph/linked_pipelines_column_legacy.vue'; -import StageColumnComponentLegacy from '~/pipelines/components/graph/stage_column_component_legacy.vue'; -import PipelinesMediator from '~/pipelines/pipeline_details_mediator'; -import PipelineStore from '~/pipelines/stores/pipeline_store'; -import linkedPipelineJSON from './linked_pipelines_mock_data'; -import graphJSON from './mock_data_legacy'; - -describe('graph component', () => { - let store; - let mediator; - let wrapper; - - 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); - - beforeEach(() => { - mediator = new PipelinesMediator({ endpoint: '' }); - store = new PipelineStore(); - store.storePipeline(linkedPipelineJSON); - - setHTMLFixture('<div class="layout-page"></div>'); - }); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - describe('while is loading', () => { - it('should render a loading icon', () => { - wrapper = mount(GraphComponentLegacy, { - propsData: { - isLoading: true, - pipeline: {}, - mediator, - }, - }); - - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); - }); - }); - - describe('with data', () => { - beforeEach(() => { - wrapper = mount(GraphComponentLegacy, { - propsData: { - isLoading: false, - pipeline: graphJSON, - mediator, - }, - }); - }); - - it('renders the graph', () => { - expect(wrapper.find('.js-pipeline-graph').exists()).toBe(true); - expect(wrapper.find('.loading-icon').exists()).toBe(false); - expect(wrapper.find('.stage-column-list').exists()).toBe(true); - }); - - it('renders columns in the graph', () => { - expect(findStageColumns()).toHaveLength(graphJSON.details.stages.length); - }); - }); - - describe('when linked pipelines are present', () => { - beforeEach(() => { - wrapper = mount(GraphComponentLegacy, { - propsData: { - isLoading: false, - pipeline: store.state.pipeline, - mediator, - }, - }); - }); - - describe('rendered output', () => { - it('should include the pipelines graph', () => { - expect(wrapper.find('.js-pipeline-graph').exists()).toBe(true); - }); - - it('should not include the loading icon', () => { - expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); - }); - - it('should include the stage column', () => { - expect(findStageColumnAt(0).exists()).toBe(true); - }); - - it('stage column should have no-margin, gl-mr-26, has-only-one-job classes if there is only one job', () => { - expect(findStageColumnAt(0).classes()).toEqual( - expect.arrayContaining(['no-margin', 'gl-mr-26', 'has-only-one-job']), - ); - }); - - it('should include the left-margin class on the second child', () => { - expect(findStageColumnAt(1).classes('left-margin')).toBe(true); - }); - - 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, - ); - }); - - it('should include the js-has-linked-pipelines flag', () => { - expect(wrapper.find('.js-has-linked-pipelines').exists()).toBe(true); - }); - }); - - describe('computeds and methods', () => { - describe('capitalizeStageName', () => { - it('it capitalizes the stage name', () => { - expect(wrapper.findAll('.stage-column .stage-name').at(1).text()).toBe('Prebuild'); - }); - }); - - describe('stageConnectorClass', () => { - it('it returns left-margin when there is a triggerer', () => { - expect(findStageColumnAt(1).classes('left-margin')).toBe(true); - }); - }); - }); - - describe('linked pipelines components', () => { - beforeEach(() => { - wrapper = mount(GraphComponentLegacy, { - propsData: { - isLoading: false, - pipeline: store.state.pipeline, - mediator, - }, - }); - }); - - it('should render an upstream pipelines column at first position', () => { - expect(wrapper.find(LinkedPipelinesColumnLegacy).exists()).toBe(true); - expect(wrapper.find('.stage-column .stage-name').text()).toBe('Upstream'); - }); - - it('should render a downstream pipelines column at last position', () => { - const stageColumnNames = wrapper.findAll('.stage-column .stage-name'); - - expect(wrapper.find(LinkedPipelinesColumnLegacy).exists()).toBe(true); - expect(stageColumnNames.at(stageColumnNames.length - 1).text()).toBe('Downstream'); - }); - - describe('triggered by', () => { - describe('on click', () => { - it('should emit `onClickUpstreamPipeline` when triggered by linked pipeline is clicked', async () => { - const btnWrapper = findExpandPipelineBtn(); - - btnWrapper.trigger('click'); - - await nextTick(); - expect(wrapper.emitted().onClickUpstreamPipeline).toEqual([ - store.state.pipeline.triggered_by, - ]); - }); - }); - - describe('with expanded pipeline', () => { - it('should render expanded pipeline', async () => { - // expand the pipeline - store.state.pipeline.triggered_by[0].isExpanded = true; - - wrapper = mount(GraphComponentLegacy, { - propsData: { - isLoading: false, - pipeline: store.state.pipeline, - mediator, - }, - }); - - await nextTick(); - expect(wrapper.find('.js-upstream-pipeline-12').exists()).toBe(true); - }); - }); - }); - - describe('triggered', () => { - describe('on click', () => { - // 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'); - - await nextTick(); - expect(wrapper.emitted().onClickDownstreamPipeline).toEqual([ - [store.state.pipeline.triggered[1]], - ]); - }); - }); - - describe('with expanded pipeline', () => { - it('should render expanded pipeline', async () => { - // expand the pipeline - store.state.pipeline.triggered[0].isExpanded = true; - - wrapper = mount(GraphComponentLegacy, { - propsData: { - isLoading: false, - pipeline: store.state.pipeline, - mediator, - }, - }); - - await nextTick(); - expect(wrapper.find('.js-downstream-pipeline-34993051')).not.toBeNull(); - }); - }); - - describe('when column requests a refresh', () => { - beforeEach(() => { - findStageColumnAt(0).vm.$emit('refreshPipelineGraph'); - }); - - it('refreshPipelineGraph is emitted', () => { - expect(wrapper.emitted().refreshPipelineGraph).toHaveLength(1); - }); - }); - }); - }); - }); - - describe('when linked pipelines are not present', () => { - beforeEach(() => { - const pipeline = Object.assign(linkedPipelineJSON, { triggered: null, triggered_by: null }); - wrapper = mount(GraphComponentLegacy, { - propsData: { - isLoading: false, - pipeline, - mediator, - }, - }); - }); - - describe('rendered output', () => { - it('should include the first column with a no margin', () => { - const firstColumn = wrapper.find('.stage-column'); - - expect(firstColumn.classes('no-margin')).toBe(true); - }); - - it('should not render a linked pipelines column', () => { - expect(wrapper.find('.linked-pipelines-column').exists()).toBe(false); - }); - }); - - describe('stageConnectorClass', () => { - it('it returns no-margin when no triggerer and there is one job', () => { - expect(findStageColumnAt(0).classes('no-margin')).toBe(true); - }); - - it('it returns left-margin when no triggerer and not the first stage', () => { - expect(findStageColumnAt(1).classes('left-margin')).toBe(true); - }); - }); - }); - - describe('capitalizeStageName', () => { - it('capitalizes and escapes stage name', () => { - wrapper = mount(GraphComponentLegacy, { - propsData: { - isLoading: false, - pipeline: graphJSON, - mediator, - }, - }); - - expect(findStageColumnAt(1).props('title')).toEqual( - 'Deploy <img src=x onerror=alert(document.domain)>', - ); - }); - }); -}); diff --git a/spec/frontend/pipelines/graph/graph_component_spec.js b/spec/frontend/pipelines/graph/graph_component_spec.js index 30914ba99a5..1fba3823161 100644 --- a/spec/frontend/pipelines/graph/graph_component_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_spec.js @@ -4,8 +4,8 @@ import PipelineGraph from '~/pipelines/components/graph/graph_component.vue'; import JobItem from '~/pipelines/components/graph/job_item.vue'; import LinkedPipelinesColumn from '~/pipelines/components/graph/linked_pipelines_column.vue'; import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue'; +import { calculatePipelineLayersInfo } from '~/pipelines/components/graph/utils'; import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue'; -import { listByLayers } from '~/pipelines/components/parsing_utils'; import { generateResponse, mockPipelineResponse, @@ -150,7 +150,7 @@ describe('graph component', () => { }, props: { viewType: LAYER_VIEW, - pipelineLayers: listByLayers(defaultProps.pipeline), + computedPipelineInfo: calculatePipelineLayersInfo(defaultProps.pipeline, 'layer', ''), }, }); }); diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js index bb7e27b5ec2..2e8979f2b9d 100644 --- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js @@ -1,11 +1,19 @@ import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; import { mount, shallowMount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql'; import getUserCallouts from '~/graphql_shared/queries/get_user_callouts.query.graphql'; +import axios from '~/lib/utils/axios_utils'; +import { + PIPELINES_DETAIL_LINK_DURATION, + PIPELINES_DETAIL_LINKS_TOTAL, + PIPELINES_DETAIL_LINKS_JOB_RATIO, +} from '~/performance/constants'; +import * as perfUtils from '~/performance/utils'; import { IID_FAILURE, LAYER_VIEW, @@ -16,8 +24,12 @@ import PipelineGraph from '~/pipelines/components/graph/graph_component.vue'; import PipelineGraphWrapper from '~/pipelines/components/graph/graph_component_wrapper.vue'; import GraphViewSelector from '~/pipelines/components/graph/graph_view_selector.vue'; import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue'; +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 * as sentryUtils from '~/pipelines/utils'; +import { mockRunningPipelineHeaderData } from '../mock_data'; import { mapCallouts, mockCalloutsResponse, mockPipelineResponse } from './mock_data'; const defaultProvide = { @@ -72,8 +84,10 @@ describe('Pipeline graph wrapper', () => { } = {}) => { const callouts = mapCallouts(calloutsList); const getUserCalloutsHandler = jest.fn().mockResolvedValue(mockCalloutsResponse(callouts)); + const getPipelineHeaderDataHandler = jest.fn().mockResolvedValue(mockRunningPipelineHeaderData); const requestHandlers = [ + [getPipelineHeaderData, getPipelineHeaderDataHandler], [getPipelineDetails, getPipelineDetailsHandler], [getUserCallouts, getUserCalloutsHandler], ]; @@ -111,6 +125,11 @@ describe('Pipeline graph wrapper', () => { createComponentWithApollo(); expect(getGraph().exists()).toBe(false); }); + + it('skips querying headerPipeline', () => { + createComponentWithApollo(); + expect(wrapper.vm.$apollo.queries.headerPipeline.skip).toBe(true); + }); }); describe('when data has loaded', () => { @@ -190,12 +209,15 @@ describe('Pipeline graph wrapper', () => { describe('when refresh action is emitted', () => { beforeEach(async () => { createComponentWithApollo(); + jest.spyOn(wrapper.vm.$apollo.queries.headerPipeline, 'refetch'); jest.spyOn(wrapper.vm.$apollo.queries.pipeline, 'refetch'); await wrapper.vm.$nextTick(); getGraph().vm.$emit('refreshPipelineGraph'); }); it('calls refetch', () => { + expect(wrapper.vm.$apollo.queries.headerPipeline.skip).toBe(false); + expect(wrapper.vm.$apollo.queries.headerPipeline.refetch).toHaveBeenCalled(); expect(wrapper.vm.$apollo.queries.pipeline.refetch).toHaveBeenCalled(); }); }); @@ -245,28 +267,11 @@ describe('Pipeline graph wrapper', () => { }); describe('view dropdown', () => { - describe('when pipelineGraphLayersView feature flag is off', () => { - beforeEach(async () => { - createComponentWithApollo(); - jest.runOnlyPendingTimers(); - await wrapper.vm.$nextTick(); - }); - - it('does not appear', () => { - expect(getViewSelector().exists()).toBe(false); - }); - }); - - describe('when pipelineGraphLayersView feature flag is on', () => { + describe('default', () => { let layersFn; beforeEach(async () => { layersFn = jest.spyOn(parsingUtils, 'listByLayers'); createComponentWithApollo({ - provide: { - glFeatures: { - pipelineGraphLayersView: true, - }, - }, mountFn: mount, }); @@ -304,14 +309,9 @@ describe('Pipeline graph wrapper', () => { }); }); - describe('when pipelineGraphLayersView feature flag is on and layers view is selected', () => { + describe('when layers view is selected', () => { beforeEach(async () => { createComponentWithApollo({ - provide: { - glFeatures: { - pipelineGraphLayersView: true, - }, - }, data: { currentViewType: LAYER_VIEW, }, @@ -334,14 +334,9 @@ describe('Pipeline graph wrapper', () => { }); }); - describe('when pipelineGraphLayersView feature flag is on, layers view is selected, and links are active', () => { + describe('when layers view is selected, and links are active', () => { beforeEach(async () => { createComponentWithApollo({ - provide: { - glFeatures: { - pipelineGraphLayersView: true, - }, - }, data: { currentViewType: LAYER_VIEW, showLinks: true, @@ -362,11 +357,6 @@ describe('Pipeline graph wrapper', () => { describe('when hover tip would otherwise show, but it has been previously dismissed', () => { beforeEach(async () => { createComponentWithApollo({ - provide: { - glFeatures: { - pipelineGraphLayersView: true, - }, - }, data: { currentViewType: LAYER_VIEW, showLinks: true, @@ -390,11 +380,6 @@ describe('Pipeline graph wrapper', () => { localStorage.setItem(VIEW_TYPE_KEY, LAYER_VIEW); createComponentWithApollo({ - provide: { - glFeatures: { - pipelineGraphLayersView: true, - }, - }, mountFn: mount, }); @@ -422,11 +407,6 @@ describe('Pipeline graph wrapper', () => { localStorage.setItem(VIEW_TYPE_KEY, LAYER_VIEW); createComponentWithApollo({ - provide: { - glFeatures: { - pipelineGraphLayersView: true, - }, - }, mountFn: mount, getPipelineDetailsHandler: jest.fn().mockResolvedValue(nonNeedsResponse), }); @@ -450,11 +430,6 @@ describe('Pipeline graph wrapper', () => { nonNeedsResponse.data.project.pipeline.usesNeeds = false; createComponentWithApollo({ - provide: { - glFeatures: { - pipelineGraphLayersView: true, - }, - }, mountFn: mount, getPipelineDetailsHandler: jest.fn().mockResolvedValue(nonNeedsResponse), }); @@ -468,4 +443,112 @@ describe('Pipeline graph wrapper', () => { }); }); }); + + describe('performance metrics', () => { + const metricsPath = '/root/project/-/ci/prometheus_metrics/histograms.json'; + let markAndMeasure; + let reportToSentry; + let reportPerformance; + let mock; + + beforeEach(() => { + jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => cb()); + markAndMeasure = jest.spyOn(perfUtils, 'performanceMarkAndMeasure'); + reportToSentry = jest.spyOn(sentryUtils, 'reportToSentry'); + reportPerformance = jest.spyOn(Api, 'reportPerformance'); + }); + + describe('with no metrics path', () => { + beforeEach(async () => { + createComponentWithApollo(); + jest.runOnlyPendingTimers(); + await wrapper.vm.$nextTick(); + }); + + it('is not called', () => { + expect(markAndMeasure).not.toHaveBeenCalled(); + expect(reportToSentry).not.toHaveBeenCalled(); + expect(reportPerformance).not.toHaveBeenCalled(); + }); + }); + + describe('with metrics path', () => { + const duration = 875; + const numLinks = 7; + const totalGroups = 8; + const metricsData = { + histograms: [ + { name: PIPELINES_DETAIL_LINK_DURATION, value: duration / 1000 }, + { name: PIPELINES_DETAIL_LINKS_TOTAL, value: numLinks }, + { + name: PIPELINES_DETAIL_LINKS_JOB_RATIO, + value: numLinks / totalGroups, + }, + ], + }; + + describe('when no duration is obtained', () => { + beforeEach(async () => { + jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => { + return []; + }); + + createComponentWithApollo({ + provide: { + metricsPath, + glFeatures: { + pipelineGraphLayersView: true, + }, + }, + data: { + currentViewType: LAYER_VIEW, + }, + }); + + jest.runOnlyPendingTimers(); + await wrapper.vm.$nextTick(); + }); + + it('attempts to collect metrics', () => { + expect(markAndMeasure).toHaveBeenCalled(); + expect(reportPerformance).not.toHaveBeenCalled(); + expect(reportToSentry).not.toHaveBeenCalled(); + }); + }); + + describe('with duration and no error', () => { + beforeEach(() => { + mock = new MockAdapter(axios); + mock.onPost(metricsPath).reply(200, {}); + + jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => { + return [{ duration }]; + }); + + createComponentWithApollo({ + provide: { + metricsPath, + glFeatures: { + pipelineGraphLayersView: true, + }, + }, + data: { + currentViewType: LAYER_VIEW, + }, + }); + }); + + afterEach(() => { + mock.restore(); + }); + + it('it calls reportPerformance with expected arguments', () => { + expect(markAndMeasure).toHaveBeenCalled(); + expect(reportPerformance).toHaveBeenCalled(); + expect(reportPerformance).toHaveBeenCalledWith(metricsPath, metricsData); + expect(reportToSentry).not.toHaveBeenCalled(); + }); + }); + }); + }); }); diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_legacy_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_legacy_spec.js deleted file mode 100644 index 200e3f48401..00000000000 --- a/spec/frontend/pipelines/graph/linked_pipelines_column_legacy_spec.js +++ /dev/null @@ -1,40 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { UPSTREAM } from '~/pipelines/components/graph/constants'; -import LinkedPipeline from '~/pipelines/components/graph/linked_pipeline.vue'; -import LinkedPipelinesColumnLegacy from '~/pipelines/components/graph/linked_pipelines_column_legacy.vue'; -import mockData from './linked_pipelines_mock_data'; - -describe('Linked Pipelines Column', () => { - const propsData = { - columnTitle: 'Upstream', - linkedPipelines: mockData.triggered, - graphPosition: 'right', - projectId: 19, - type: UPSTREAM, - }; - let wrapper; - - beforeEach(() => { - wrapper = shallowMount(LinkedPipelinesColumnLegacy, { propsData }); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders the pipeline orientation', () => { - const titleElement = wrapper.find('.linked-pipelines-column-title'); - - expect(titleElement.text()).toBe(propsData.columnTitle); - }); - - it('renders the correct number of linked pipelines', () => { - const linkedPipelineElements = wrapper.findAll(LinkedPipeline); - - expect(linkedPipelineElements.length).toBe(propsData.linkedPipelines.length); - }); - - it('renders cross project triangle when column is upstream', () => { - expect(wrapper.find('.cross-project-triangle').exists()).toBe(true); - }); -}); diff --git a/spec/frontend/pipelines/graph/mock_data_legacy.js b/spec/frontend/pipelines/graph/mock_data_legacy.js deleted file mode 100644 index e1c8b027121..00000000000 --- a/spec/frontend/pipelines/graph/mock_data_legacy.js +++ /dev/null @@ -1,261 +0,0 @@ -export default { - id: 123, - user: { - name: 'Root', - username: 'root', - id: 1, - state: 'active', - avatar_url: null, - web_url: 'http://localhost:3000/root', - }, - active: false, - coverage: null, - path: '/root/ci-mock/pipelines/123', - details: { - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - has_details: true, - details_path: '/root/ci-mock/pipelines/123', - favicon: - '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', - }, - duration: 9, - finished_at: '2017-04-19T14:30:27.542Z', - stages: [ - { - name: 'test', - title: 'test: passed', - groups: [ - { - name: 'test', - size: 1, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - has_details: true, - details_path: '/root/ci-mock/builds/4153', - favicon: - '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/root/ci-mock/builds/4153/retry', - method: 'post', - }, - }, - jobs: [ - { - id: 4153, - name: 'test', - build_path: '/root/ci-mock/builds/4153', - retry_path: '/root/ci-mock/builds/4153/retry', - playable: false, - created_at: '2017-04-13T09:25:18.959Z', - updated_at: '2017-04-13T09:25:23.118Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - has_details: true, - details_path: '/root/ci-mock/builds/4153', - favicon: - '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/root/ci-mock/builds/4153/retry', - method: 'post', - }, - }, - }, - ], - }, - ], - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - has_details: true, - details_path: '/root/ci-mock/pipelines/123#test', - favicon: - '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', - }, - path: '/root/ci-mock/pipelines/123#test', - dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=test', - }, - { - name: 'deploy <img src=x onerror=alert(document.domain)>', - title: 'deploy: passed', - groups: [ - { - name: 'deploy to production', - size: 1, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - has_details: true, - details_path: '/root/ci-mock/builds/4166', - favicon: - '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/root/ci-mock/builds/4166/retry', - method: 'post', - }, - }, - jobs: [ - { - id: 4166, - name: 'deploy to production', - build_path: '/root/ci-mock/builds/4166', - retry_path: '/root/ci-mock/builds/4166/retry', - playable: false, - created_at: '2017-04-19T14:29:46.463Z', - updated_at: '2017-04-19T14:30:27.498Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - has_details: true, - details_path: '/root/ci-mock/builds/4166', - favicon: - '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/root/ci-mock/builds/4166/retry', - method: 'post', - }, - }, - }, - ], - }, - { - name: 'deploy to staging', - size: 1, - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - has_details: true, - details_path: '/root/ci-mock/builds/4159', - favicon: - '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/root/ci-mock/builds/4159/retry', - method: 'post', - }, - }, - jobs: [ - { - id: 4159, - name: 'deploy to staging', - build_path: '/root/ci-mock/builds/4159', - retry_path: '/root/ci-mock/builds/4159/retry', - playable: false, - created_at: '2017-04-18T16:32:08.420Z', - updated_at: '2017-04-18T16:32:12.631Z', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - has_details: true, - details_path: '/root/ci-mock/builds/4159', - favicon: - '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', - action: { - icon: 'retry', - title: 'Retry', - path: '/root/ci-mock/builds/4159/retry', - method: 'post', - }, - }, - }, - ], - }, - ], - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - has_details: true, - details_path: '/root/ci-mock/pipelines/123#deploy', - favicon: - '/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png', - }, - path: '/root/ci-mock/pipelines/123#deploy', - dropdown_path: '/root/ci-mock/pipelines/123/stage.json?stage=deploy', - }, - ], - artifacts: [], - manual_actions: [ - { - name: 'deploy to production', - path: '/root/ci-mock/builds/4166/play', - playable: false, - }, - ], - }, - flags: { - latest: true, - triggered: false, - stuck: false, - yaml_errors: false, - retryable: false, - cancelable: false, - }, - ref: { - name: 'main', - path: '/root/ci-mock/tree/main', - tag: false, - branch: true, - }, - commit: { - id: '798e5f902592192afaba73f4668ae30e56eae492', - short_id: '798e5f90', - title: "Merge branch 'new-branch' into 'main'\r", - created_at: '2017-04-13T10:25:17.000+01:00', - parent_ids: [ - '54d483b1ed156fbbf618886ddf7ab023e24f8738', - 'c8e2d38a6c538822e81c57022a6e3a0cfedebbcc', - ], - message: - "Merge branch 'new-branch' into 'main'\r\n\r\nAdd new file\r\n\r\nSee merge request !1", - author_name: 'Root', - author_email: 'admin@example.com', - authored_date: '2017-04-13T10:25:17.000+01:00', - committer_name: 'Root', - committer_email: 'admin@example.com', - committed_date: '2017-04-13T10:25:17.000+01:00', - author: { - name: 'Root', - username: 'root', - id: 1, - state: 'active', - avatar_url: null, - web_url: 'http://localhost:3000/root', - }, - author_gravatar_url: null, - commit_url: - 'http://localhost:3000/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492', - commit_path: '/root/ci-mock/commit/798e5f902592192afaba73f4668ae30e56eae492', - }, - created_at: '2017-04-13T09:25:18.881Z', - updated_at: '2017-04-19T14:30:27.561Z', -}; diff --git a/spec/frontend/pipelines/graph/stage_column_component_legacy_spec.js b/spec/frontend/pipelines/graph/stage_column_component_legacy_spec.js deleted file mode 100644 index 2965325ea7c..00000000000 --- a/spec/frontend/pipelines/graph/stage_column_component_legacy_spec.js +++ /dev/null @@ -1,130 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import StageColumnComponentLegacy from '~/pipelines/components/graph/stage_column_component_legacy.vue'; - -describe('stage column component', () => { - const mockJob = { - id: 4250, - name: 'test', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - details_path: '/root/ci-mock/builds/4250', - action: { - icon: 'retry', - title: 'Retry', - path: '/root/ci-mock/builds/4250/retry', - method: 'post', - }, - }, - }; - - let wrapper; - - beforeEach(() => { - const mockGroups = []; - for (let i = 0; i < 3; i += 1) { - const mockedJob = { ...mockJob }; - mockedJob.id += i; - mockGroups.push(mockedJob); - } - - wrapper = shallowMount(StageColumnComponentLegacy, { - propsData: { - title: 'foo', - groups: mockGroups, - hasTriggeredBy: false, - }, - }); - }); - - it('should render provided title', () => { - expect(wrapper.find('.stage-name').text().trim()).toBe('foo'); - }); - - it('should render the provided groups', () => { - expect(wrapper.findAll('.builds-container > ul > li').length).toBe( - wrapper.props('groups').length, - ); - }); - - describe('jobId', () => { - it('escapes job name', () => { - wrapper = shallowMount(StageColumnComponentLegacy, { - propsData: { - 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', - hasTriggeredBy: false, - }, - }); - - expect(wrapper.find('.builds-container li').attributes('id')).toBe( - 'ci-badge-<img src=x onerror=alert(document.domain)>', - ); - }); - }); - - describe('with action', () => { - it('renders action button', () => { - wrapper = shallowMount(StageColumnComponentLegacy, { - propsData: { - 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', - hasTriggeredBy: false, - action: { - icon: 'play', - title: 'Play all', - path: 'action', - }, - }, - }); - - expect(wrapper.find('.js-stage-action').exists()).toBe(true); - }); - }); - - describe('without action', () => { - it('does not render action button', () => { - wrapper = shallowMount(StageColumnComponentLegacy, { - propsData: { - 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', - hasTriggeredBy: false, - }, - }); - - expect(wrapper.find('.js-stage-action').exists()).toBe(false); - }); - }); -}); diff --git a/spec/frontend/pipelines/graph_shared/links_inner_spec.js b/spec/frontend/pipelines/graph_shared/links_inner_spec.js index 8f39c8c2405..be422fac92c 100644 --- a/spec/frontend/pipelines/graph_shared/links_inner_spec.js +++ b/spec/frontend/pipelines/graph_shared/links_inner_spec.js @@ -31,7 +31,7 @@ describe('Links Inner component', () => { propsData: { ...defaultProps, ...props, - parsedData: parseData(currentPipelineData.flatMap(({ groups }) => groups)), + linksData: parseData(currentPipelineData.flatMap(({ groups }) => groups)).links, }, }); }; diff --git a/spec/frontend/pipelines/graph_shared/links_layer_spec.js b/spec/frontend/pipelines/graph_shared/links_layer_spec.js index 932a19f2f00..44ab60cbee7 100644 --- a/spec/frontend/pipelines/graph_shared/links_layer_spec.js +++ b/spec/frontend/pipelines/graph_shared/links_layer_spec.js @@ -1,16 +1,6 @@ import { shallowMount } from '@vue/test-utils'; -import MockAdapter from 'axios-mock-adapter'; -import axios from '~/lib/utils/axios_utils'; -import { - PIPELINES_DETAIL_LINK_DURATION, - PIPELINES_DETAIL_LINKS_TOTAL, - PIPELINES_DETAIL_LINKS_JOB_RATIO, -} from '~/performance/constants'; -import * as perfUtils from '~/performance/utils'; -import * as Api from '~/pipelines/components/graph_shared/api'; import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue'; import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue'; -import * as sentryUtils from '~/pipelines/utils'; import { generateResponse, mockPipelineResponse } from '../graph/mock_data'; describe('links layer component', () => { @@ -94,139 +84,4 @@ describe('links layer component', () => { expect(findLinksInner().exists()).toBe(false); }); }); - - describe('performance metrics', () => { - const metricsPath = '/root/project/-/ci/prometheus_metrics/histograms.json'; - let markAndMeasure; - let reportToSentry; - let reportPerformance; - let mock; - - beforeEach(() => { - jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => cb()); - markAndMeasure = jest.spyOn(perfUtils, 'performanceMarkAndMeasure'); - reportToSentry = jest.spyOn(sentryUtils, 'reportToSentry'); - reportPerformance = jest.spyOn(Api, 'reportPerformance'); - }); - - describe('with no metrics config object', () => { - beforeEach(() => { - createComponent(); - }); - - it('is not called', () => { - expect(markAndMeasure).not.toHaveBeenCalled(); - expect(reportToSentry).not.toHaveBeenCalled(); - expect(reportPerformance).not.toHaveBeenCalled(); - }); - }); - - describe('with metrics config set to false', () => { - beforeEach(() => { - createComponent({ - props: { - metricsConfig: { - collectMetrics: false, - metricsPath: '/path/to/metrics', - }, - }, - }); - }); - - it('is not called', () => { - expect(markAndMeasure).not.toHaveBeenCalled(); - expect(reportToSentry).not.toHaveBeenCalled(); - expect(reportPerformance).not.toHaveBeenCalled(); - }); - }); - - describe('with no metrics path', () => { - beforeEach(() => { - createComponent({ - props: { - metricsConfig: { - collectMetrics: true, - metricsPath: '', - }, - }, - }); - }); - - it('is not called', () => { - expect(markAndMeasure).not.toHaveBeenCalled(); - expect(reportToSentry).not.toHaveBeenCalled(); - expect(reportPerformance).not.toHaveBeenCalled(); - }); - }); - - describe('with metrics path and collect set to true', () => { - const duration = 875; - const numLinks = 7; - const totalGroups = 8; - const metricsData = { - histograms: [ - { name: PIPELINES_DETAIL_LINK_DURATION, value: duration / 1000 }, - { name: PIPELINES_DETAIL_LINKS_TOTAL, value: numLinks }, - { - name: PIPELINES_DETAIL_LINKS_JOB_RATIO, - value: numLinks / totalGroups, - }, - ], - }; - - describe('when no duration is obtained', () => { - beforeEach(() => { - jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => { - return []; - }); - - createComponent({ - props: { - metricsConfig: { - collectMetrics: true, - path: metricsPath, - }, - }, - }); - }); - - it('attempts to collect metrics', () => { - expect(markAndMeasure).toHaveBeenCalled(); - expect(reportPerformance).not.toHaveBeenCalled(); - expect(reportToSentry).not.toHaveBeenCalled(); - }); - }); - - describe('with duration and no error', () => { - beforeEach(() => { - mock = new MockAdapter(axios); - mock.onPost(metricsPath).reply(200, {}); - - jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => { - return [{ duration }]; - }); - - createComponent({ - props: { - metricsConfig: { - collectMetrics: true, - path: metricsPath, - }, - }, - }); - }); - - afterEach(() => { - mock.restore(); - }); - - it('it calls reportPerformance with expected arguments', () => { - expect(markAndMeasure).toHaveBeenCalled(); - expect(reportPerformance).toHaveBeenCalled(); - expect(reportPerformance).toHaveBeenCalledWith(metricsPath, metricsData); - expect(reportToSentry).not.toHaveBeenCalled(); - }); - }); - }); - }); }); diff --git a/spec/frontend/pipelines/header_component_spec.js b/spec/frontend/pipelines/header_component_spec.js index 31f0e72c279..e531e26a858 100644 --- a/spec/frontend/pipelines/header_component_spec.js +++ b/spec/frontend/pipelines/header_component_spec.js @@ -99,24 +99,6 @@ describe('Pipeline details header', () => { ); }); - describe('polling', () => { - it('is stopped when pipeline is finished', async () => { - wrapper = createComponent({ ...mockRunningPipelineHeader }); - - await wrapper.setData({ - pipeline: { ...mockCancelledPipelineHeader }, - }); - - expect(wrapper.vm.$apollo.queries.pipeline.stopPolling).toHaveBeenCalled(); - }); - - it('is not stopped when pipeline is not finished', () => { - wrapper = createComponent(); - - expect(wrapper.vm.$apollo.queries.pipeline.stopPolling).not.toHaveBeenCalled(); - }); - }); - describe('actions', () => { describe('Retry action', () => { beforeEach(() => { diff --git a/spec/frontend/pipelines/mock_data.js b/spec/frontend/pipelines/mock_data.js index 7e3c3727c9d..fdc78d48901 100644 --- a/spec/frontend/pipelines/mock_data.js +++ b/spec/frontend/pipelines/mock_data.js @@ -127,6 +127,28 @@ export const mockSuccessfulPipelineHeader = { }, }; +export const mockRunningPipelineHeaderData = { + data: { + project: { + pipeline: { + ...mockRunningPipelineHeader, + iid: '28', + user: { + name: 'Foo', + username: 'foobar', + webPath: '/foo', + email: 'foo@bar.com', + avatarUrl: 'link', + status: null, + __typename: 'UserCore', + }, + __typename: 'Pipeline', + }, + __typename: 'Project', + }, + }, +}; + export const stageReply = { name: 'deploy', title: 'deploy: running', diff --git a/spec/frontend/pipelines/parsing_utils_spec.js b/spec/frontend/pipelines/parsing_utils_spec.js index 074009ae056..3a270c1c1b5 100644 --- a/spec/frontend/pipelines/parsing_utils_spec.js +++ b/spec/frontend/pipelines/parsing_utils_spec.js @@ -120,8 +120,8 @@ describe('DAG visualization parsing utilities', () => { describe('generateColumnsFromLayersList', () => { const pipeline = generateResponse(mockPipelineResponse, 'root/fungi-xoxo'); - const layers = listByLayers(pipeline); - const columns = generateColumnsFromLayersListBare(pipeline, layers); + const { pipelineLayers } = listByLayers(pipeline); + const columns = generateColumnsFromLayersListBare(pipeline, pipelineLayers); it('returns stage-like objects with default name, id, and status', () => { columns.forEach((col, idx) => { @@ -136,7 +136,7 @@ describe('DAG visualization parsing utilities', () => { it('creates groups that match the list created in listByLayers', () => { columns.forEach((col, idx) => { const groupNames = col.groups.map(({ name }) => name); - expect(groupNames).toEqual(layers[idx]); + expect(groupNames).toEqual(pipelineLayers[idx]); }); }); diff --git a/spec/frontend/pipelines/pipeline_details_mediator_spec.js b/spec/frontend/pipelines/pipeline_details_mediator_spec.js deleted file mode 100644 index d6699a43b54..00000000000 --- a/spec/frontend/pipelines/pipeline_details_mediator_spec.js +++ /dev/null @@ -1,36 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import waitForPromises from 'helpers/wait_for_promises'; -import axios from '~/lib/utils/axios_utils'; -import PipelineMediator from '~/pipelines/pipeline_details_mediator'; - -describe('PipelineMdediator', () => { - let mediator; - let mock; - - beforeEach(() => { - mock = new MockAdapter(axios); - mediator = new PipelineMediator({ endpoint: 'foo.json' }); - }); - - afterEach(() => { - mock.restore(); - }); - - it('should set defaults', () => { - expect(mediator.options).toEqual({ endpoint: 'foo.json' }); - expect(mediator.state.isLoading).toEqual(false); - expect(mediator.store).toBeDefined(); - expect(mediator.service).toBeDefined(); - }); - - describe('request and store data', () => { - it('should store received data', () => { - mock.onGet('foo.json').reply(200, { id: '121123' }); - mediator.fetchPipeline(); - - return waitForPromises().then(() => { - expect(mediator.store.state.pipeline).toEqual({ id: '121123' }); - }); - }); - }); -}); diff --git a/spec/frontend/pipelines/pipeline_multi_actions_spec.js b/spec/frontend/pipelines/pipeline_multi_actions_spec.js index 88b3ef2032a..ce33b6011bf 100644 --- a/spec/frontend/pipelines/pipeline_multi_actions_spec.js +++ b/spec/frontend/pipelines/pipeline_multi_actions_spec.js @@ -53,6 +53,7 @@ describe('Pipeline Multi Actions Dropdown', () => { const findDropdown = () => wrapper.findComponent(GlDropdown); const findAllArtifactItems = () => wrapper.findAllByTestId(artifactItemTestId); const findFirstArtifactItem = () => wrapper.findByTestId(artifactItemTestId); + const findEmptyMessage = () => wrapper.findByTestId('artifacts-empty-message'); beforeEach(() => { mockAxios = new MockAdapter(axios); @@ -86,6 +87,7 @@ describe('Pipeline Multi Actions Dropdown', () => { createComponent({ mockData: { artifacts } }); expect(findAllArtifactItems()).toHaveLength(artifacts.length); + expect(findEmptyMessage().exists()).toBe(false); }); it('should render the correct artifact name and path', () => { @@ -95,6 +97,12 @@ describe('Pipeline Multi Actions Dropdown', () => { expect(findFirstArtifactItem().text()).toBe(`Download ${artifacts[0].name} artifact`); }); + it('should render empty message when no artifacts are found', () => { + createComponent({ mockData: { artifacts: [] } }); + + expect(findEmptyMessage().exists()).toBe(true); + }); + describe('with a failing request', () => { it('should render an error message', async () => { const endpoint = artifactsEndpoint.replace(artifactsEndpointPlaceholder, pipelineId); diff --git a/spec/frontend/pipelines/pipeline_store_spec.js b/spec/frontend/pipelines/pipeline_store_spec.js deleted file mode 100644 index 1d5754d1f05..00000000000 --- a/spec/frontend/pipelines/pipeline_store_spec.js +++ /dev/null @@ -1,27 +0,0 @@ -import PipelineStore from '~/pipelines/stores/pipeline_store'; - -describe('Pipeline Store', () => { - let store; - - beforeEach(() => { - store = new PipelineStore(); - }); - - it('should set defaults', () => { - expect(store.state.pipeline).toEqual({}); - }); - - describe('storePipeline', () => { - it('should store empty object if none is provided', () => { - store.storePipeline(); - - expect(store.state.pipeline).toEqual({}); - }); - - it('should store received object', () => { - store.storePipeline({ foo: 'bar' }); - - expect(store.state.pipeline).toEqual({ foo: 'bar' }); - }); - }); -}); diff --git a/spec/frontend/pipelines/pipeline_url_spec.js b/spec/frontend/pipelines/pipeline_url_spec.js index 367c7f2b2f6..912b5afe0e1 100644 --- a/spec/frontend/pipelines/pipeline_url_spec.js +++ b/spec/frontend/pipelines/pipeline_url_spec.js @@ -28,6 +28,7 @@ describe('Pipeline Url Component', () => { flags: {}, }, pipelineScheduleUrl: 'foo', + pipelineKey: 'id', }; const createComponent = (props) => { diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js index 2166961cedd..76feaaad1ec 100644 --- a/spec/frontend/pipelines/pipelines_spec.js +++ b/spec/frontend/pipelines/pipelines_spec.js @@ -4,6 +4,8 @@ import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import { chunk } from 'lodash'; import { nextTick } from 'vue'; +import setWindowLocation from 'helpers/set_window_location_helper'; +import { TEST_HOST } from 'helpers/test_constants'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import Api from '~/api'; @@ -40,7 +42,6 @@ const mockPipelineWithStages = mockPipelinesResponse.pipelines.find( describe('Pipelines', () => { let wrapper; let mock; - let origWindowLocation; const paths = { emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg', @@ -73,6 +74,7 @@ describe('Pipelines', () => { const findTablePagination = () => wrapper.findComponent(TablePagination); const findTab = (tab) => wrapper.findByTestId(`pipelines-tab-${tab}`); + const findPipelineKeyDropdown = () => wrapper.findByTestId('pipeline-key-dropdown'); const findRunPipelineButton = () => wrapper.findByTestId('run-pipeline-button'); const findCiLintButton = () => wrapper.findByTestId('ci-lint-button'); const findCleanCacheButton = () => wrapper.findByTestId('clear-cache-button'); @@ -98,20 +100,13 @@ describe('Pipelines', () => { ); }; - beforeAll(() => { - origWindowLocation = window.location; - delete window.location; - window.location = { - search: '', - protocol: 'https:', - }; - }); - - afterAll(() => { - window.location = origWindowLocation; + beforeEach(() => { + setWindowLocation(TEST_HOST); }); beforeEach(() => { + window.gon = { features: { pipelineSourceFilter: true } }; + mock = new MockAdapter(axios); jest.spyOn(window.history, 'pushState'); @@ -536,6 +531,10 @@ describe('Pipelines', () => { expect(findFilteredSearch().exists()).toBe(true); }); + it('renders the pipeline key dropdown', () => { + expect(findPipelineKeyDropdown().exists()).toBe(true); + }); + it('renders tab empty state finished scope', async () => { mock.onGet(mockPipelinesEndpoint, { params: { scope: 'finished', page: '1' } }).reply(200, { pipelines: [], @@ -631,6 +630,10 @@ describe('Pipelines', () => { expect(findFilteredSearch().exists()).toBe(false); }); + it('does not render the pipeline key dropdown', () => { + expect(findPipelineKeyDropdown().exists()).toBe(false); + }); + it('does not render tabs nor buttons', () => { expect(findNavigationTabs().exists()).toBe(false); expect(findTab('all').exists()).toBe(false); diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js index 68b0dfc018e..4472a5ae70d 100644 --- a/spec/frontend/pipelines/pipelines_table_spec.js +++ b/spec/frontend/pipelines/pipelines_table_spec.js @@ -8,6 +8,7 @@ import PipelineTriggerer from '~/pipelines/components/pipelines_list/pipeline_tr import PipelineUrl from '~/pipelines/components/pipelines_list/pipeline_url.vue'; import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue'; import PipelinesTimeago from '~/pipelines/components/pipelines_list/time_ago.vue'; +import { PipelineKeyOptions } from '~/pipelines/constants'; import eventHub from '~/pipelines/event_hub'; import CiBadge from '~/vue_shared/components/ci_badge_link.vue'; @@ -24,6 +25,7 @@ describe('Pipelines Table', () => { const defaultProps = { pipelines: [], viewType: 'root', + pipelineKeyOption: PipelineKeyOptions[0], }; const createMockPipeline = () => { @@ -80,7 +82,7 @@ describe('Pipelines Table', () => { it('should render table head with correct columns', () => { expect(findStatusTh().text()).toBe('Status'); - expect(findPipelineTh().text()).toBe('Pipeline'); + expect(findPipelineTh().text()).toBe('Pipeline ID'); expect(findTriggererTh().text()).toBe('Triggerer'); expect(findCommitTh().text()).toBe('Commit'); expect(findStagesTh().text()).toBe('Stages'); diff --git a/spec/frontend/pipelines/stores/pipeline_store_spec.js b/spec/frontend/pipelines/stores/pipeline_store_spec.js deleted file mode 100644 index 2daf7e4b324..00000000000 --- a/spec/frontend/pipelines/stores/pipeline_store_spec.js +++ /dev/null @@ -1,135 +0,0 @@ -import PipelineStore from '~/pipelines/stores/pipeline_store'; -import LinkedPipelines from '../linked_pipelines_mock.json'; - -describe('EE Pipeline store', () => { - let store; - let data; - - beforeEach(() => { - store = new PipelineStore(); - data = { ...LinkedPipelines }; - - store.storePipeline(data); - }); - - describe('storePipeline', () => { - describe('triggered_by', () => { - it('sets triggered_by as an array', () => { - expect(store.state.pipeline.triggered_by.length).toEqual(1); - }); - - it('adds isExpanding & isLoading keys set to false', () => { - expect(store.state.pipeline.triggered_by[0].isExpanded).toEqual(false); - expect(store.state.pipeline.triggered_by[0].isLoading).toEqual(false); - }); - - it('parses nested triggered_by', () => { - expect(store.state.pipeline.triggered_by[0].triggered_by.length).toEqual(1); - expect(store.state.pipeline.triggered_by[0].triggered_by[0].isExpanded).toEqual(false); - expect(store.state.pipeline.triggered_by[0].triggered_by[0].isLoading).toEqual(false); - }); - }); - - describe('triggered', () => { - it('adds isExpanding & isLoading keys set to false for each triggered pipeline', () => { - store.state.pipeline.triggered.forEach((pipeline) => { - expect(pipeline.isExpanded).toEqual(false); - expect(pipeline.isLoading).toEqual(false); - }); - }); - - it('parses nested triggered pipelines', () => { - store.state.pipeline.triggered[1].triggered.forEach((pipeline) => { - expect(pipeline.isExpanded).toEqual(false); - expect(pipeline.isLoading).toEqual(false); - }); - }); - }); - }); - - describe('resetTriggeredByPipeline', () => { - it('closes the pipeline & nested ones', () => { - store.state.pipeline.triggered_by[0].isExpanded = true; - store.state.pipeline.triggered_by[0].triggered_by[0].isExpanded = true; - - store.resetTriggeredByPipeline(store.state.pipeline, store.state.pipeline.triggered_by[0]); - - expect(store.state.pipeline.triggered_by[0].isExpanded).toEqual(false); - expect(store.state.pipeline.triggered_by[0].triggered_by[0].isExpanded).toEqual(false); - }); - }); - - describe('openTriggeredByPipeline', () => { - it('opens the given pipeline', () => { - store.openTriggeredByPipeline(store.state.pipeline, store.state.pipeline.triggered_by[0]); - - expect(store.state.pipeline.triggered_by[0].isExpanded).toEqual(true); - }); - }); - - describe('closeTriggeredByPipeline', () => { - it('closes the given pipeline', () => { - // open it first - store.openTriggeredByPipeline(store.state.pipeline, store.state.pipeline.triggered_by[0]); - - store.closeTriggeredByPipeline(store.state.pipeline, store.state.pipeline.triggered_by[0]); - - expect(store.state.pipeline.triggered_by[0].isExpanded).toEqual(false); - }); - }); - - describe('resetTriggeredPipelines', () => { - it('closes the pipeline & nested ones', () => { - store.state.pipeline.triggered[0].isExpanded = true; - store.state.pipeline.triggered[0].triggered[0].isExpanded = true; - - store.resetTriggeredPipelines(store.state.pipeline, store.state.pipeline.triggered[0]); - - expect(store.state.pipeline.triggered[0].isExpanded).toEqual(false); - expect(store.state.pipeline.triggered[0].triggered[0].isExpanded).toEqual(false); - }); - }); - - describe('openTriggeredPipeline', () => { - it('opens the given pipeline', () => { - store.openTriggeredPipeline(store.state.pipeline, store.state.pipeline.triggered[0]); - - expect(store.state.pipeline.triggered[0].isExpanded).toEqual(true); - }); - }); - - describe('closeTriggeredPipeline', () => { - it('closes the given pipeline', () => { - // open it first - store.openTriggeredPipeline(store.state.pipeline, store.state.pipeline.triggered[0]); - - store.closeTriggeredPipeline(store.state.pipeline, store.state.pipeline.triggered[0]); - - expect(store.state.pipeline.triggered[0].isExpanded).toEqual(false); - }); - }); - - describe('toggleLoading', () => { - it('toggles the isLoading property for the given pipeline', () => { - store.toggleLoading(store.state.pipeline.triggered[0]); - - expect(store.state.pipeline.triggered[0].isLoading).toEqual(true); - }); - }); - - describe('addExpandedPipelineToRequestData', () => { - it('pushes the given id to expandedPipelines array', () => { - store.addExpandedPipelineToRequestData('213231'); - - expect(store.state.expandedPipelines).toEqual(['213231']); - }); - }); - - describe('removeExpandedPipelineToRequestData', () => { - it('pushes the given id to expandedPipelines array', () => { - store.removeExpandedPipelineToRequestData('213231'); - - expect(store.state.expandedPipelines).toEqual([]); - }); - }); -}); diff --git a/spec/frontend/pipelines/tokens/pipeline_source_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_source_token_spec.js new file mode 100644 index 00000000000..5d15f0a3c55 --- /dev/null +++ b/spec/frontend/pipelines/tokens/pipeline_source_token_spec.js @@ -0,0 +1,50 @@ +import { GlFilteredSearchToken, GlFilteredSearchSuggestion } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { stubComponent } from 'helpers/stub_component'; +import PipelineSourceToken from '~/pipelines/components/pipelines_list/tokens/pipeline_source_token.vue'; + +describe('Pipeline Source Token', () => { + let wrapper; + + const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken); + const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion); + + const defaultProps = { + config: { + type: 'source', + icon: 'trigger-source', + title: 'Source', + unique: true, + }, + value: { + data: '', + }, + }; + + const createComponent = () => { + wrapper = shallowMount(PipelineSourceToken, { + propsData: { + ...defaultProps, + }, + stubs: { + GlFilteredSearchToken: stubComponent(GlFilteredSearchToken, { + template: `<div><slot name="suggestions"></slot></div>`, + }), + }, + }); + }; + + beforeEach(() => { + createComponent(); + }); + + it('passes config correctly', () => { + expect(findFilteredSearchToken().props('config')).toEqual(defaultProps.config); + }); + + describe('shows sources correctly', () => { + it('renders all pipeline sources available', () => { + expect(findAllFilteredSearchSuggestions()).toHaveLength(wrapper.vm.sources.length); + }); + }); +}); |