diff options
Diffstat (limited to 'spec/frontend/pipelines')
13 files changed, 208 insertions, 88 deletions
diff --git a/spec/frontend/pipelines/components/dag/mock_data.js b/spec/frontend/pipelines/components/dag/mock_data.js index e7e93804195..f27e7cf3d6b 100644 --- a/spec/frontend/pipelines/components/dag/mock_data.js +++ b/spec/frontend/pipelines/components/dag/mock_data.js @@ -398,6 +398,8 @@ export const multiNote = { }, }; +export const missingJob = 'missing_job'; + /* It is important that the base include parallel jobs as well as non-parallel jobs with spaces in the name to prevent @@ -657,4 +659,16 @@ export const mockParsedGraphQLNodes = [ ], __typename: 'CiGroup', }, + { + category: 'production', + name: 'production_e', + size: 1, + jobs: [ + { + name: 'production_e', + needs: [missingJob], + }, + ], + __typename: 'CiGroup', + }, ]; diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js index 4914a9a1ced..bb7e27b5ec2 100644 --- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js @@ -5,6 +5,7 @@ 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 { IID_FAILURE, LAYER_VIEW, @@ -17,7 +18,6 @@ import GraphViewSelector from '~/pipelines/components/graph/graph_view_selector. import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue'; import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue'; import * as parsingUtils from '~/pipelines/components/parsing_utils'; -import getUserCallouts from '~/pipelines/graphql/queries/get_user_callouts.query.graphql'; import { mapCallouts, mockCalloutsResponse, mockPipelineResponse } from './mock_data'; const defaultProvide = { diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js index 96f2cd1e371..c7d95526a0c 100644 --- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js +++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js @@ -14,6 +14,7 @@ describe('Linked pipeline', () => { let wrapper; const findButton = () => wrapper.find(GlButton); + const findDownstreamPipelineTitle = () => wrapper.find('[data-testid="downstream-title"]'); const findPipelineLabel = () => wrapper.find('[data-testid="downstream-pipeline-label"]'); const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' }); const findLoadingIcon = () => wrapper.find(GlLoadingIcon); @@ -119,6 +120,11 @@ describe('Linked pipeline', () => { expect(findPipelineLabel().exists()).toBe(true); }); + it('should have the name of the trigger job on the card when it is a child pipeline', () => { + createWrapper(downstreamProps); + expect(findDownstreamPipelineTitle().text()).toBe(mockPipeline.source_job.name); + }); + it('should display parent label when pipeline project id is the same as triggered_by pipeline project id', () => { createWrapper(upstreamProps); expect(findPipelineLabel().exists()).toBe(true); diff --git a/spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap b/spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap index c67b91ae190..16c28791514 100644 --- a/spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap +++ b/spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap @@ -21,3 +21,10 @@ exports[`Links Inner component with one need matches snapshot and has expected p <path d=\\"M202,118L42,118C72,118,72,138,102,138\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> </svg> </div>" `; + +exports[`Links Inner component with same stage needs matches snapshot and has expected path 1`] = ` +"<div class=\\"gl-display-flex gl-relative\\" totalgroups=\\"10\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\"> + <path d=\\"M192,108L22,108C52,108,52,118,82,118\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> + <path d=\\"M202,118L32,118C62,118,62,128,92,128\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> + </svg> </div>" +`; diff --git a/spec/frontend/pipelines/graph_shared/links_inner_spec.js b/spec/frontend/pipelines/graph_shared/links_inner_spec.js index bb1f0965469..8f39c8c2405 100644 --- a/spec/frontend/pipelines/graph_shared/links_inner_spec.js +++ b/spec/frontend/pipelines/graph_shared/links_inner_spec.js @@ -10,6 +10,7 @@ import { pipelineData, pipelineDataWithNoNeeds, rootRect, + sameStageNeeds, } from '../pipeline_graph/mock_data'; describe('Links Inner component', () => { @@ -40,7 +41,7 @@ describe('Links Inner component', () => { // We create fixture so that each job has an empty div that represent // the JobPill in the DOM. Each `JobPill` would have different coordinates, - // so we increment their coordinates on each iteration to simulat different positions. + // so we increment their coordinates on each iteration to simulate different positions. const setFixtures = ({ stages }) => { const jobs = createJobsHash(stages); const arrayOfJobs = Object.keys(jobs); @@ -81,7 +82,6 @@ describe('Links Inner component', () => { afterEach(() => { jest.restoreAllMocks(); wrapper.destroy(); - wrapper = null; }); describe('basic SVG creation', () => { @@ -160,6 +160,25 @@ describe('Links Inner component', () => { }); }); + describe('with same stage needs', () => { + beforeEach(() => { + setFixtures(sameStageNeeds); + createComponent({ pipelineData: sameStageNeeds.stages }); + }); + + it('renders the correct number of links', () => { + expect(findAllLinksPath()).toHaveLength(2); + }); + + it('path does not contain NaN values', () => { + expect(wrapper.html()).not.toContain('NaN'); + }); + + it('matches snapshot and has expected path', () => { + expect(wrapper.html()).toMatchSnapshot(); + }); + }); + describe('with a large number of needs', () => { beforeEach(() => { setFixtures(largePipelineData); diff --git a/spec/frontend/pipelines/header_component_spec.js b/spec/frontend/pipelines/header_component_spec.js index 57d846c53c8..31f0e72c279 100644 --- a/spec/frontend/pipelines/header_component_spec.js +++ b/spec/frontend/pipelines/header_component_spec.js @@ -7,7 +7,9 @@ import retryPipelineMutation from '~/pipelines/graphql/mutations/retry_pipeline. import { mockCancelledPipelineHeader, mockFailedPipelineHeader, + mockFailedPipelineNoPermissions, mockRunningPipelineHeader, + mockRunningPipelineNoPermissions, mockSuccessfulPipelineHeader, } from './mock_data'; @@ -168,5 +170,19 @@ describe('Pipeline details header', () => { }); }); }); + + describe('Permissions', () => { + it('should not display the cancel action if user does not have permission', () => { + wrapper = createComponent(mockRunningPipelineNoPermissions); + + expect(findCancelButton().exists()).toBe(false); + }); + + it('should not display the retry action if user does not have permission', () => { + wrapper = createComponent(mockFailedPipelineNoPermissions); + + expect(findRetryButton().exists()).toBe(false); + }); + }); }); }); diff --git a/spec/frontend/pipelines/mock_data.js b/spec/frontend/pipelines/mock_data.js index 16f15b20824..7e3c3727c9d 100644 --- a/spec/frontend/pipelines/mock_data.js +++ b/spec/frontend/pipelines/mock_data.js @@ -10,6 +10,7 @@ export const mockPipelineHeader = { id: 123, userPermissions: { destroyPipeline: true, + updatePipeline: true, }, createdAt: threeWeeksAgo.toISOString(), user: { @@ -34,6 +35,31 @@ export const mockFailedPipelineHeader = { }, }; +export const mockFailedPipelineNoPermissions = { + id: 123, + userPermissions: { + destroyPipeline: false, + updatePipeline: false, + }, + createdAt: threeWeeksAgo.toISOString(), + user: { + name: 'Foo', + username: 'foobar', + email: 'foo@bar.com', + avatarUrl: 'link', + }, + status: PIPELINE_RUNNING, + retryable: true, + cancelable: false, + detailedStatus: { + group: 'running', + icon: 'status_running', + label: 'running', + text: 'running', + detailsPath: 'path', + }, +}; + export const mockRunningPipelineHeader = { ...mockPipelineHeader, status: PIPELINE_RUNNING, @@ -48,6 +74,31 @@ export const mockRunningPipelineHeader = { }, }; +export const mockRunningPipelineNoPermissions = { + id: 123, + userPermissions: { + destroyPipeline: false, + updatePipeline: false, + }, + createdAt: threeWeeksAgo.toISOString(), + user: { + name: 'Foo', + username: 'foobar', + email: 'foo@bar.com', + avatarUrl: 'link', + }, + status: PIPELINE_RUNNING, + retryable: false, + cancelable: true, + detailedStatus: { + group: 'running', + icon: 'status_running', + label: 'running', + text: 'running', + detailsPath: 'path', + }, +}; + export const mockCancelledPipelineHeader = { ...mockPipelineHeader, status: PIPELINE_CANCELED, diff --git a/spec/frontend/pipelines/notification/pipeline_notification_spec.js b/spec/frontend/pipelines/notification/pipeline_notification_spec.js deleted file mode 100644 index 79aa337ba9d..00000000000 --- a/spec/frontend/pipelines/notification/pipeline_notification_spec.js +++ /dev/null @@ -1,79 +0,0 @@ -import { GlBanner } from '@gitlab/ui'; -import { createLocalVue, shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import VueApollo from 'vue-apollo'; -import createMockApollo from 'helpers/mock_apollo_helper'; -import PipelineNotification from '~/pipelines/components/notification/pipeline_notification.vue'; -import getUserCallouts from '~/pipelines/graphql/queries/get_user_callouts.query.graphql'; - -describe('Pipeline notification', () => { - const localVue = createLocalVue(); - - let wrapper; - const dagDocPath = 'my/dag/path'; - - const createWrapper = (apolloProvider) => { - return shallowMount(PipelineNotification, { - localVue, - provide: { - dagDocPath, - }, - apolloProvider, - }); - }; - - const createWrapperWithApollo = async ({ callouts = [], isLoading = false } = {}) => { - localVue.use(VueApollo); - - const mappedCallouts = callouts.map((callout) => { - return { featureName: callout, __typename: 'UserCallout' }; - }); - - const mockCalloutsResponse = { - data: { - currentUser: { - id: 45, - __typename: 'User', - callouts: { - id: 5, - __typename: 'UserCalloutConnection', - nodes: mappedCallouts, - }, - }, - }, - }; - const getUserCalloutsHandler = jest.fn().mockResolvedValue(mockCalloutsResponse); - const requestHandlers = [[getUserCallouts, getUserCalloutsHandler]]; - - const apolloWrapper = createWrapper(createMockApollo(requestHandlers)); - if (!isLoading) { - await nextTick(); - } - - return apolloWrapper; - }; - - const findBanner = () => wrapper.findComponent(GlBanner); - - afterEach(() => { - wrapper.destroy(); - }); - - it('shows the banner if the user has never seen it', async () => { - wrapper = await createWrapperWithApollo({ callouts: ['random'] }); - - expect(findBanner().exists()).toBe(true); - }); - - it('does not show the banner while the user callout query is loading', async () => { - wrapper = await createWrapperWithApollo({ callouts: ['random'], isLoading: true }); - - expect(findBanner().exists()).toBe(false); - }); - - it('does not show the banner if the user has previously dismissed it', async () => { - wrapper = await createWrapperWithApollo({ callouts: ['pipeline_needs_banner'.toUpperCase()] }); - - expect(findBanner().exists()).toBe(false); - }); -}); diff --git a/spec/frontend/pipelines/parsing_utils_spec.js b/spec/frontend/pipelines/parsing_utils_spec.js index 96748ae9e5c..074009ae056 100644 --- a/spec/frontend/pipelines/parsing_utils_spec.js +++ b/spec/frontend/pipelines/parsing_utils_spec.js @@ -10,7 +10,7 @@ import { getMaxNodes, } from '~/pipelines/components/parsing_utils'; -import { mockParsedGraphQLNodes } from './components/dag/mock_data'; +import { mockParsedGraphQLNodes, missingJob } from './components/dag/mock_data'; import { generateResponse, mockPipelineResponse } from './graph/mock_data'; describe('DAG visualization parsing utilities', () => { @@ -24,6 +24,12 @@ describe('DAG visualization parsing utilities', () => { expect(unfilteredLinks[0]).toHaveProperty('target', 'test_a'); expect(unfilteredLinks[0]).toHaveProperty('value', 10); }); + + it('does not generate a link for non-existing jobs', () => { + const sources = unfilteredLinks.map(({ source }) => source); + + expect(sources.includes(missingJob)).toBe(false); + }); }); describe('filterByAncestors', () => { @@ -88,7 +94,7 @@ describe('DAG visualization parsing utilities', () => { These lengths are determined by the mock data. If the data changes, the numbers may also change. */ - expect(parsed.nodes).toHaveLength(21); + expect(parsed.nodes).toHaveLength(mockParsedGraphQLNodes.length); expect(cleanedNodes).toHaveLength(12); }); }); diff --git a/spec/frontend/pipelines/pipeline_graph/mock_data.js b/spec/frontend/pipelines/pipeline_graph/mock_data.js index a79917bfd48..db77e0a0573 100644 --- a/spec/frontend/pipelines/pipeline_graph/mock_data.js +++ b/spec/frontend/pipelines/pipeline_graph/mock_data.js @@ -162,6 +162,38 @@ export const parallelNeedData = { ], }; +export const sameStageNeeds = { + stages: [ + { + name: 'build', + groups: [ + { + name: 'build_1', + jobs: [{ script: 'echo hello', stage: 'build', name: 'build_1' }], + }, + ], + }, + { + name: 'build', + groups: [ + { + name: 'build_2', + jobs: [{ script: 'yarn test', stage: 'build', needs: ['build_1'] }], + }, + ], + }, + { + name: 'build', + groups: [ + { + name: 'build_3', + jobs: [{ script: 'yarn test', stage: 'build', needs: ['build_2'] }], + }, + ], + }, + ], +}; + export const largePipelineData = { stages: [ { diff --git a/spec/frontend/pipelines/pipeline_graph/utils_spec.js b/spec/frontend/pipelines/pipeline_graph/utils_spec.js index 070d3bf7dac..5816bc06fe3 100644 --- a/spec/frontend/pipelines/pipeline_graph/utils_spec.js +++ b/spec/frontend/pipelines/pipeline_graph/utils_spec.js @@ -111,6 +111,28 @@ describe('utils functions', () => { }); }); + it('removes needs which are not in the data', () => { + const inexistantJobName = 'job5'; + const jobsWithNeeds = { + [jobName1]: job1, + [jobName2]: job2, + [jobName3]: job3, + [jobName4]: { + name: jobName4, + script: 'echo deploy', + stage: 'deploy', + needs: [inexistantJobName], + }, + }; + + expect(generateJobNeedsDict(jobsWithNeeds)).toEqual({ + [jobName1]: [], + [jobName2]: [], + [jobName3]: [jobName1, jobName2], + [jobName4]: [], + }); + }); + it('handles parallel jobs by adding the group name as a need', () => { const size = 3; const jobOptimize1 = 'optimize_1'; diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js index f9b59c5dc48..874ecbccf82 100644 --- a/spec/frontend/pipelines/pipelines_spec.js +++ b/spec/frontend/pipelines/pipelines_spec.js @@ -7,8 +7,8 @@ import { nextTick } from 'vue'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import Api from '~/api'; -import { getExperimentVariant } from '~/experimentation/utils'; -import { deprecatedCreateFlash as createFlash } from '~/flash'; +import { getExperimentData, getExperimentVariant } from '~/experimentation/utils'; +import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue'; import PipelinesComponent from '~/pipelines/components/pipelines_list/pipelines.vue'; @@ -23,6 +23,7 @@ import { stageReply, users, mockSearch, branches } from './mock_data'; jest.mock('~/flash'); jest.mock('~/experimentation/utils', () => ({ ...jest.requireActual('~/experimentation/utils'), + getExperimentData: jest.fn().mockReturnValue(false), getExperimentVariant: jest.fn().mockReturnValue('control'), })); @@ -48,6 +49,7 @@ describe('Pipelines', () => { resetCachePath: `${mockProjectPath}/settings/ci_cd/reset_cache`, newPipelinePath: `${mockProjectPath}/pipelines/new`, codeQualityPagePath: `${mockProjectPath}/-/new/master?commit_message=Add+.gitlab-ci.yml+and+create+a+code+quality+job&file_name=.gitlab-ci.yml&template=Code-Quality`, + ciRunnerSettingsPath: `${mockProjectPath}/-/settings/ci_cd#js-runners-settings`, }; const noPermissions = { @@ -349,7 +351,7 @@ describe('Pipelines', () => { it('displays a warning message if raw text search is used', () => { expect(createFlash).toHaveBeenCalledTimes(1); - expect(createFlash).toHaveBeenCalledWith(RAW_TEXT_WARNING, 'warning'); + expect(createFlash).toHaveBeenCalledWith({ message: RAW_TEXT_WARNING, type: 'warning' }); }); it('should update browser bar', () => { @@ -563,6 +565,7 @@ describe('Pipelines', () => { describe('when the code_quality_walkthrough experiment is active', () => { beforeAll(() => { + getExperimentData.mockImplementation((name) => name === 'code_quality_walkthrough'); getExperimentVariant.mockReturnValue('candidate'); }); @@ -574,6 +577,29 @@ describe('Pipelines', () => { }); }); + describe('when the ci_runner_templates experiment is active', () => { + beforeAll(() => { + getExperimentData.mockImplementation((name) => name === 'ci_runner_templates'); + getExperimentVariant.mockReturnValue('candidate'); + }); + + it('renders two buttons', () => { + expect(findEmptyState().findAllComponents(GlButton).length).toBe(2); + expect(findEmptyState().findAllComponents(GlButton).at(0).text()).toBe( + 'Install GitLab Runners', + ); + expect(findEmptyState().findAllComponents(GlButton).at(0).attributes('href')).toBe( + paths.ciRunnerSettingsPath, + ); + expect(findEmptyState().findAllComponents(GlButton).at(1).text()).toBe( + 'Learn about Runners', + ); + expect(findEmptyState().findAllComponents(GlButton).at(1).attributes('href')).toBe( + '/help/ci/quick_start/index.md', + ); + }); + }); + it('does not render filtered search', () => { expect(findFilteredSearch().exists()).toBe(false); }); diff --git a/spec/frontend/pipelines/test_reports/stores/actions_spec.js b/spec/frontend/pipelines/test_reports/stores/actions_spec.js index 6258b08dfbb..e931ddb8496 100644 --- a/spec/frontend/pipelines/test_reports/stores/actions_spec.js +++ b/spec/frontend/pipelines/test_reports/stores/actions_spec.js @@ -2,7 +2,7 @@ import MockAdapter from 'axios-mock-adapter'; import { getJSONFixture } from 'helpers/fixtures'; import { TEST_HOST } from 'helpers/test_constants'; import testAction from 'helpers/vuex_action_helper'; -import { deprecatedCreateFlash as createFlash } from '~/flash'; +import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import * as actions from '~/pipelines/stores/test_reports/actions'; import * as types from '~/pipelines/stores/test_reports/mutation_types'; |