summaryrefslogtreecommitdiff
path: root/spec/frontend/pipelines
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/pipelines')
-rw-r--r--spec/frontend/pipelines/components/dag/mock_data.js14
-rw-r--r--spec/frontend/pipelines/graph/graph_component_wrapper_spec.js2
-rw-r--r--spec/frontend/pipelines/graph/linked_pipeline_spec.js6
-rw-r--r--spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap7
-rw-r--r--spec/frontend/pipelines/graph_shared/links_inner_spec.js23
-rw-r--r--spec/frontend/pipelines/header_component_spec.js16
-rw-r--r--spec/frontend/pipelines/mock_data.js51
-rw-r--r--spec/frontend/pipelines/notification/pipeline_notification_spec.js79
-rw-r--r--spec/frontend/pipelines/parsing_utils_spec.js10
-rw-r--r--spec/frontend/pipelines/pipeline_graph/mock_data.js32
-rw-r--r--spec/frontend/pipelines/pipeline_graph/utils_spec.js22
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js32
-rw-r--r--spec/frontend/pipelines/test_reports/stores/actions_spec.js2
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';