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/dag_annotations_spec.js9
-rw-r--r--spec/frontend/pipelines/components/dag/dag_graph_spec.js5
-rw-r--r--spec/frontend/pipelines/components/dag/dag_spec.js21
-rw-r--r--spec/frontend/pipelines/components/jobs/failed_jobs_app_spec.js15
-rw-r--r--spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js46
-rw-r--r--spec/frontend/pipelines/components/jobs/jobs_app_spec.js10
-rw-r--r--spec/frontend/pipelines/components/jobs/utils_spec.js14
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js12
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_mini_graph_spec.js15
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js7
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js5
-rw-r--r--spec/frontend/pipelines/components/pipeline_tabs_spec.js5
-rw-r--r--spec/frontend/pipelines/components/pipelines_filtered_search_spec.js2
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/empty_state/ci_templates_spec.js5
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/empty_state/ios_templates_spec.js5
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates_spec.js109
-rw-r--r--spec/frontend/pipelines/empty_state_spec.js5
-rw-r--r--spec/frontend/pipelines/graph/action_component_spec.js1
-rw-r--r--spec/frontend/pipelines/graph/graph_component_wrapper_spec.js175
-rw-r--r--spec/frontend/pipelines/graph/graph_view_selector_spec.js19
-rw-r--r--spec/frontend/pipelines/graph/job_group_dropdown_spec.js4
-rw-r--r--spec/frontend/pipelines/graph/job_item_spec.js51
-rw-r--r--spec/frontend/pipelines/graph/linked_pipeline_spec.js170
-rw-r--r--spec/frontend/pipelines/graph/linked_pipelines_column_spec.js4
-rw-r--r--spec/frontend/pipelines/graph/stage_column_component_spec.js4
-rw-r--r--spec/frontend/pipelines/graph_shared/links_inner_spec.js1
-rw-r--r--spec/frontend/pipelines/graph_shared/links_layer_spec.js4
-rw-r--r--spec/frontend/pipelines/header_component_spec.js11
-rw-r--r--spec/frontend/pipelines/mock_data.js87
-rw-r--r--spec/frontend/pipelines/nav_controls_spec.js37
-rw-r--r--spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js4
-rw-r--r--spec/frontend/pipelines/pipeline_labels_spec.js4
-rw-r--r--spec/frontend/pipelines/pipeline_multi_actions_spec.js2
-rw-r--r--spec/frontend/pipelines/pipeline_operations_spec.js77
-rw-r--r--spec/frontend/pipelines/pipeline_tabs_spec.js2
-rw-r--r--spec/frontend/pipelines/pipeline_triggerer_spec.js6
-rw-r--r--spec/frontend/pipelines/pipeline_url_spec.js4
-rw-r--r--spec/frontend/pipelines/pipelines_actions_spec.js171
-rw-r--r--spec/frontend/pipelines/pipelines_artifacts_spec.js5
-rw-r--r--spec/frontend/pipelines/pipelines_manual_actions_spec.js216
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js33
-rw-r--r--spec/frontend/pipelines/pipelines_table_spec.js6
-rw-r--r--spec/frontend/pipelines/test_reports/stores/actions_spec.js6
-rw-r--r--spec/frontend/pipelines/test_reports/stores/mutations_spec.js6
-rw-r--r--spec/frontend/pipelines/test_reports/test_case_details_spec.js5
-rw-r--r--spec/frontend/pipelines/test_reports/test_reports_spec.js4
-rw-r--r--spec/frontend/pipelines/test_reports/test_suite_table_spec.js4
-rw-r--r--spec/frontend/pipelines/time_ago_spec.js5
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js5
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_status_token_spec.js5
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js5
-rw-r--r--spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js5
52 files changed, 661 insertions, 777 deletions
diff --git a/spec/frontend/pipelines/components/dag/dag_annotations_spec.js b/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
index 28a08b6da0f..124f02bcec7 100644
--- a/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_annotations_spec.js
@@ -14,10 +14,6 @@ describe('The DAG annotations', () => {
const getToggleButton = () => wrapper.findComponent(GlButton);
const createComponent = (propsData = {}, method = shallowMount) => {
- if (wrapper?.destroy) {
- wrapper.destroy();
- }
-
wrapper = method(DagAnnotations, {
propsData,
data() {
@@ -28,11 +24,6 @@ describe('The DAG annotations', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when there is one annotation', () => {
const currentNote = singleNote['dag-link103'];
diff --git a/spec/frontend/pipelines/components/dag/dag_graph_spec.js b/spec/frontend/pipelines/components/dag/dag_graph_spec.js
index 4619548d1bb..6b46be3dd49 100644
--- a/spec/frontend/pipelines/components/dag/dag_graph_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_graph_spec.js
@@ -36,11 +36,6 @@ describe('The DAG graph', () => {
createComponent({ graphData: parsedData });
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('in the basic case', () => {
beforeEach(() => {
/*
diff --git a/spec/frontend/pipelines/components/dag/dag_spec.js b/spec/frontend/pipelines/components/dag/dag_spec.js
index b0c26976c85..53719065611 100644
--- a/spec/frontend/pipelines/components/dag/dag_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_spec.js
@@ -30,10 +30,6 @@ describe('Pipeline DAG graph wrapper', () => {
provideOverride = {},
method = shallowMount,
} = {}) => {
- if (wrapper?.destroy) {
- wrapper.destroy();
- }
-
wrapper = method(Dag, {
provide: {
pipelineProjectPath: 'root/abc-dag',
@@ -51,11 +47,6 @@ describe('Pipeline DAG graph wrapper', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when a query argument is undefined', () => {
beforeEach(() => {
createComponent({
@@ -64,7 +55,7 @@ describe('Pipeline DAG graph wrapper', () => {
});
});
- it('does not render the graph', async () => {
+ it('does not render the graph', () => {
expect(getGraph().exists()).toBe(false);
});
@@ -75,7 +66,7 @@ describe('Pipeline DAG graph wrapper', () => {
describe('when all query variables are defined', () => {
describe('but the parse fails', () => {
- beforeEach(async () => {
+ beforeEach(() => {
createComponent({
graphData: unparseableGraph,
});
@@ -93,7 +84,7 @@ describe('Pipeline DAG graph wrapper', () => {
});
describe('parse succeeds', () => {
- beforeEach(async () => {
+ beforeEach(() => {
createComponent({ method: mount });
});
@@ -107,7 +98,7 @@ describe('Pipeline DAG graph wrapper', () => {
});
describe('parse succeeds, but the resulting graph is too small', () => {
- beforeEach(async () => {
+ beforeEach(() => {
createComponent({
graphData: tooSmallGraph,
});
@@ -125,7 +116,7 @@ describe('Pipeline DAG graph wrapper', () => {
});
describe('the returned data is empty', () => {
- beforeEach(async () => {
+ beforeEach(() => {
createComponent({
method: mount,
graphData: graphWithoutDependencies,
@@ -144,7 +135,7 @@ describe('Pipeline DAG graph wrapper', () => {
});
describe('annotations', () => {
- beforeEach(async () => {
+ beforeEach(() => {
createComponent();
});
diff --git a/spec/frontend/pipelines/components/jobs/failed_jobs_app_spec.js b/spec/frontend/pipelines/components/jobs/failed_jobs_app_spec.js
index d1da7cb3acf..6a2453704db 100644
--- a/spec/frontend/pipelines/components/jobs/failed_jobs_app_spec.js
+++ b/spec/frontend/pipelines/components/jobs/failed_jobs_app_spec.js
@@ -4,15 +4,15 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import FailedJobsApp from '~/pipelines/components/jobs/failed_jobs_app.vue';
import FailedJobsTable from '~/pipelines/components/jobs/failed_jobs_table.vue';
import GetFailedJobsQuery from '~/pipelines/graphql/queries/get_failed_jobs.query.graphql';
-import { mockFailedJobsQueryResponse, mockFailedJobsSummaryData } from '../../mock_data';
+import { mockFailedJobsQueryResponse } from '../../mock_data';
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Failed Jobs App', () => {
let wrapper;
@@ -27,15 +27,12 @@ describe('Failed Jobs App', () => {
return createMockApollo(requestHandlers);
};
- const createComponent = (resolver, failedJobsSummaryData = mockFailedJobsSummaryData) => {
+ const createComponent = (resolver) => {
wrapper = shallowMount(FailedJobsApp, {
provide: {
fullPath: 'root/ci-project',
pipelineIid: 1,
},
- propsData: {
- failedJobsSummary: failedJobsSummaryData,
- },
apolloProvider: createMockApolloProvider(resolver),
});
};
@@ -44,10 +41,6 @@ describe('Failed Jobs App', () => {
resolverSpy = jest.fn().mockResolvedValue(mockFailedJobsQueryResponse);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('loading spinner', () => {
it('displays loading spinner when fetching failed jobs', () => {
createComponent(resolverSpy);
diff --git a/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js b/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js
index 0df15afd70d..d5307b87a11 100644
--- a/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js
+++ b/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js
@@ -4,18 +4,18 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { mountExtended } from 'helpers/vue_test_utils_helper';
-import { createAlert } from '~/flash';
-import { redirectTo } from '~/lib/utils/url_utility';
+import { createAlert } from '~/alert';
+import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated
import FailedJobsTable from '~/pipelines/components/jobs/failed_jobs_table.vue';
import RetryFailedJobMutation from '~/pipelines/graphql/mutations/retry_failed_job.mutation.graphql';
import {
successRetryMutationResponse,
failedRetryMutationResponse,
- mockPreparedFailedJobsData,
- mockPreparedFailedJobsDataNoPermission,
+ mockFailedJobsData,
+ mockFailedJobsDataNoPermission,
} from '../../mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
jest.mock('~/lib/utils/url_utility');
Vue.use(VueApollo);
@@ -30,13 +30,15 @@ describe('Failed Jobs Table', () => {
const findRetryButton = () => wrapper.findComponent(GlButton);
const findJobLink = () => wrapper.findComponent(GlLink);
const findJobLog = () => wrapper.findByTestId('job-log');
+ const findSummary = (index) => wrapper.findAllByTestId('job-trace-summary').at(index);
+ const findFirstFailureMessage = () => wrapper.findAllByTestId('job-failure-message').at(0);
const createMockApolloProvider = (resolver) => {
const requestHandlers = [[RetryFailedJobMutation, resolver]];
return createMockApollo(requestHandlers);
};
- const createComponent = (resolver, failedJobsData = mockPreparedFailedJobsData) => {
+ const createComponent = (resolver, failedJobsData = mockFailedJobsData) => {
wrapper = mountExtended(FailedJobsTable, {
propsData: {
failedJobs: failedJobsData,
@@ -45,23 +47,37 @@ describe('Failed Jobs Table', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('displays the failed jobs table', () => {
createComponent();
expect(findJobsTable().exists()).toBe(true);
});
+ it('displays failed job summary', () => {
+ createComponent();
+
+ expect(findSummary(0).text()).toBe('Html Summary');
+ });
+
+ it('displays no job log when no trace', () => {
+ createComponent();
+
+ expect(findSummary(1).text()).toBe('No job log');
+ });
+
+ it('displays failure reason', () => {
+ createComponent();
+
+ expect(findFirstFailureMessage().text()).toBe('Job failed');
+ });
+
it('calls the retry failed job mutation correctly', () => {
createComponent(successRetryMutationHandler);
findRetryButton().trigger('click');
expect(successRetryMutationHandler).toHaveBeenCalledWith({
- id: mockPreparedFailedJobsData[0].id,
+ id: mockFailedJobsData[0].id,
});
});
@@ -78,7 +94,7 @@ describe('Failed Jobs Table', () => {
await waitForPromises();
- expect(redirectTo).toHaveBeenCalledWith(job.detailedStatus.detailsPath);
+ expect(redirectTo).toHaveBeenCalledWith(job.detailedStatus.detailsPath); // eslint-disable-line import/no-deprecated
});
it('shows error message if the retry failed job mutation fails', async () => {
@@ -94,7 +110,7 @@ describe('Failed Jobs Table', () => {
});
it('hides the job log and retry button if a user does not have permission', () => {
- createComponent([[]], mockPreparedFailedJobsDataNoPermission);
+ createComponent([[]], mockFailedJobsDataNoPermission);
expect(findJobLog().exists()).toBe(false);
expect(findRetryButton().exists()).toBe(false);
@@ -110,8 +126,6 @@ describe('Failed Jobs Table', () => {
it('job name links to the correct job', () => {
createComponent();
- expect(findJobLink().attributes('href')).toBe(
- mockPreparedFailedJobsData[0].detailedStatus.detailsPath,
- );
+ expect(findJobLink().attributes('href')).toBe(mockFailedJobsData[0].detailedStatus.detailsPath);
});
});
diff --git a/spec/frontend/pipelines/components/jobs/jobs_app_spec.js b/spec/frontend/pipelines/components/jobs/jobs_app_spec.js
index 9bc14266593..39475788fe2 100644
--- a/spec/frontend/pipelines/components/jobs/jobs_app_spec.js
+++ b/spec/frontend/pipelines/components/jobs/jobs_app_spec.js
@@ -4,7 +4,7 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import JobsApp from '~/pipelines/components/jobs/jobs_app.vue';
import JobsTable from '~/jobs/components/table/jobs_table.vue';
import getPipelineJobsQuery from '~/pipelines/graphql/queries/get_pipeline_jobs.query.graphql';
@@ -12,7 +12,7 @@ import { mockPipelineJobsQueryResponse } from '../../mock_data';
Vue.use(VueApollo);
-jest.mock('~/flash');
+jest.mock('~/alert');
describe('Jobs app', () => {
let wrapper;
@@ -34,7 +34,7 @@ describe('Jobs app', () => {
const createComponent = (resolver) => {
wrapper = shallowMount(JobsApp, {
provide: {
- fullPath: 'root/ci-project',
+ projectPath: 'root/ci-project',
pipelineIid: 1,
},
apolloProvider: createMockApolloProvider(resolver),
@@ -45,10 +45,6 @@ describe('Jobs app', () => {
resolverSpy = jest.fn().mockResolvedValue(mockPipelineJobsQueryResponse);
});
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('loading spinner', () => {
const setup = async () => {
createComponent(resolverSpy);
diff --git a/spec/frontend/pipelines/components/jobs/utils_spec.js b/spec/frontend/pipelines/components/jobs/utils_spec.js
deleted file mode 100644
index 720446cfda3..00000000000
--- a/spec/frontend/pipelines/components/jobs/utils_spec.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import { prepareFailedJobs } from '~/pipelines/components/jobs/utils';
-import {
- mockFailedJobsData,
- mockFailedJobsSummaryData,
- mockPreparedFailedJobsData,
-} from '../../mock_data';
-
-describe('Utils', () => {
- it('prepares failed jobs data correctly', () => {
- expect(prepareFailedJobs(mockFailedJobsData, mockFailedJobsSummaryData)).toEqual(
- mockPreparedFailedJobsData,
- );
- });
-});
diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js
index 5ea57c51e70..a4ecb9041c9 100644
--- a/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js
+++ b/spec/frontend/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list_spec.js
@@ -19,7 +19,7 @@ describe('Linked pipeline mini list', () => {
const createComponent = (props = {}) => {
wrapper = mount(LinkedPipelinesMiniList, {
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
propsData: {
...props,
@@ -34,11 +34,6 @@ describe('Linked pipeline mini list', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should render one linked pipeline item', () => {
expect(findLinkedPipelineMiniItem().exists()).toBe(true);
});
@@ -102,11 +97,6 @@ describe('Linked pipeline mini list', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should render three linked pipeline items', () => {
expect(findLinkedPipelineMiniItems().exists()).toBe(true);
expect(findLinkedPipelineMiniItems().length).toBe(3);
diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_mini_graph_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_mini_graph_spec.js
index 036b82530d5..e7415a6c596 100644
--- a/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_mini_graph_spec.js
+++ b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_mini_graph_spec.js
@@ -33,11 +33,6 @@ describe('Pipeline Mini Graph', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should render the pipeline stages', () => {
expect(findPipelineStages().exists()).toBe(true);
});
@@ -71,11 +66,6 @@ describe('Pipeline Mini Graph', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should have the correct props', () => {
expect(findPipelineMiniGraph().props()).toMatchObject({
downstreamPipelines: [],
@@ -118,11 +108,6 @@ describe('Pipeline Mini Graph', () => {
});
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should render the downstream linked pipelines mini list only', () => {
expect(findLinkedPipelineDownstream().exists()).toBe(true);
expect(findLinkedPipelineUpstream().exists()).toBe(false);
diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js
index ab2056b4035..21d92fec9bf 100644
--- a/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js
+++ b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js
@@ -45,11 +45,10 @@ describe('Pipelines stage component', () => {
});
afterEach(() => {
- wrapper.destroy();
- wrapper = null;
-
eventHub.$emit.mockRestore();
mock.restore();
+ // eslint-disable-next-line @gitlab/vtu-no-explicit-wrapper-destroy
+ wrapper.destroy();
});
const findCiActionBtn = () => wrapper.find('.js-ci-action');
@@ -130,7 +129,7 @@ describe('Pipelines stage component', () => {
await axios.waitForAll();
});
- it('renders the received data and emits the correct events', async () => {
+ it('renders the received data and emits the correct events', () => {
expect(findDropdownMenu().text()).toContain(stageReply.latest_statuses[0].name);
expect(findDropdownMenuTitle().text()).toContain(stageReply.name);
expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js
index c123f53886e..73e810bde99 100644
--- a/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js
+++ b/spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js
@@ -60,9 +60,4 @@ describe('Pipeline Stages', () => {
expect(findPipelineStagesAt(0).props('isMergeTrain')).toBe(true);
expect(findPipelineStagesAt(1).props('isMergeTrain')).toBe(true);
});
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
});
diff --git a/spec/frontend/pipelines/components/pipeline_tabs_spec.js b/spec/frontend/pipelines/components/pipeline_tabs_spec.js
index c2cb95d4320..fde13128662 100644
--- a/spec/frontend/pipelines/components/pipeline_tabs_spec.js
+++ b/spec/frontend/pipelines/components/pipeline_tabs_spec.js
@@ -19,7 +19,6 @@ describe('The Pipeline Tabs', () => {
const defaultProvide = {
defaultTabValue: '',
failedJobsCount: 1,
- failedJobsSummary: [],
totalJobCount: 10,
testsCount: 123,
};
@@ -39,10 +38,6 @@ describe('The Pipeline Tabs', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('Tabs', () => {
it.each`
tabName | tabComponent
diff --git a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
index ba7262353f0..51a4487a3ef 100644
--- a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js
@@ -51,8 +51,6 @@ describe('Pipelines filtered search', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
- wrapper = null;
});
it('displays UI elements', () => {
diff --git a/spec/frontend/pipelines/components/pipelines_list/empty_state/ci_templates_spec.js b/spec/frontend/pipelines/components/pipelines_list/empty_state/ci_templates_spec.js
index 6531a15ab8e..b560eea4882 100644
--- a/spec/frontend/pipelines/components/pipelines_list/empty_state/ci_templates_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_list/empty_state/ci_templates_spec.js
@@ -29,11 +29,6 @@ describe('CI Templates', () => {
const findTemplateName = () => wrapper.findByTestId('template-name');
const findTemplateLogo = () => wrapper.findByTestId('template-logo');
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('renders template list', () => {
beforeEach(() => {
createWrapper();
diff --git a/spec/frontend/pipelines/components/pipelines_list/empty_state/ios_templates_spec.js b/spec/frontend/pipelines/components/pipelines_list/empty_state/ios_templates_spec.js
index 0c2938921d6..700be076e0c 100644
--- a/spec/frontend/pipelines/components/pipelines_list/empty_state/ios_templates_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_list/empty_state/ios_templates_spec.js
@@ -37,11 +37,6 @@ describe('iOS Templates', () => {
const findSetupRunnerLink = () => wrapper.findByText('Set up a runner');
const configurePipelineLink = () => wrapper.findByTestId('configure-pipeline-link');
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when ios runners are not available', () => {
beforeEach(() => {
wrapper = createWrapper({ iosRunnersAvailable: false });
diff --git a/spec/frontend/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates_spec.js b/spec/frontend/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates_spec.js
index f255e0d857f..4bf4257f462 100644
--- a/spec/frontend/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates_spec.js
@@ -1,24 +1,10 @@
import '~/commons';
-import { GlButton, GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
-import { stubExperiments } from 'helpers/experimentation_helper';
-import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue';
-import ExperimentTracking from '~/experimentation/experiment_tracking';
import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue';
import CiTemplates from '~/pipelines/components/pipelines_list/empty_state/ci_templates.vue';
-import {
- RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
- RUNNERS_SETTINGS_LINK_CLICKED_EVENT,
- RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT,
- RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT,
- I18N,
-} from '~/ci/pipeline_editor/constants';
const pipelineEditorPath = '/-/ci/editor';
-const ciRunnerSettingsPath = '/-/settings/ci_cd';
-
-jest.mock('~/experimentation/experiment_tracking');
describe('Pipelines CI Templates', () => {
let wrapper;
@@ -28,8 +14,6 @@ describe('Pipelines CI Templates', () => {
return shallowMountExtended(PipelinesCiTemplates, {
provide: {
pipelineEditorPath,
- ciRunnerSettingsPath,
- anyRunnersAvailable: true,
...propsData,
},
stubs,
@@ -38,24 +22,17 @@ describe('Pipelines CI Templates', () => {
const findTestTemplateLink = () => wrapper.findByTestId('test-template-link');
const findCiTemplates = () => wrapper.findComponent(CiTemplates);
- const findSettingsLink = () => wrapper.findByTestId('settings-link');
- const findDocumentationLink = () => wrapper.findByTestId('documentation-link');
- const findSettingsButton = () => wrapper.findByTestId('settings-button');
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
- describe('renders test template', () => {
+ describe('templates', () => {
beforeEach(() => {
wrapper = createWrapper();
});
- it('links to the getting started template', () => {
+ it('renders test template and Ci templates', () => {
expect(findTestTemplateLink().attributes('href')).toBe(
pipelineEditorPath.concat('?template=Getting-Started'),
);
+ expect(findCiTemplates().exists()).toBe(true);
});
});
@@ -78,84 +55,4 @@ describe('Pipelines CI Templates', () => {
});
});
});
-
- describe('when the runners_availability_section experiment is active', () => {
- beforeEach(() => {
- stubExperiments({ runners_availability_section: 'candidate' });
- });
-
- describe('when runners are available', () => {
- beforeEach(() => {
- wrapper = createWrapper({ anyRunnersAvailable: true }, { GitlabExperiment, GlSprintf });
- });
-
- it('show the runners available section', () => {
- expect(wrapper.text()).toContain(I18N.runners.title);
- });
-
- it('tracks an event when clicking the settings link', () => {
- findSettingsLink().vm.$emit('click');
-
- expect(ExperimentTracking).toHaveBeenCalledWith(
- RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
- );
- expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(
- RUNNERS_SETTINGS_LINK_CLICKED_EVENT,
- );
- });
-
- it('tracks an event when clicking the documentation link', () => {
- findDocumentationLink().vm.$emit('click');
-
- expect(ExperimentTracking).toHaveBeenCalledWith(
- RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
- );
- expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(
- RUNNERS_DOCUMENTATION_LINK_CLICKED_EVENT,
- );
- });
- });
-
- describe('when runners are not available', () => {
- beforeEach(() => {
- wrapper = createWrapper({ anyRunnersAvailable: false }, { GitlabExperiment, GlButton });
- });
-
- it('show the no runners available section', () => {
- expect(wrapper.text()).toContain(I18N.noRunners.title);
- });
-
- it('tracks an event when clicking the settings button', () => {
- findSettingsButton().trigger('click');
-
- expect(ExperimentTracking).toHaveBeenCalledWith(
- RUNNERS_AVAILABILITY_SECTION_EXPERIMENT_NAME,
- );
- expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(
- RUNNERS_SETTINGS_BUTTON_CLICKED_EVENT,
- );
- });
- });
- });
-
- describe.each`
- experimentVariant | anyRunnersAvailable | templatesRendered
- ${'control'} | ${true} | ${true}
- ${'control'} | ${false} | ${true}
- ${'candidate'} | ${true} | ${true}
- ${'candidate'} | ${false} | ${false}
- `(
- 'when the runners_availability_section experiment variant is $experimentVariant and runners are available: $anyRunnersAvailable',
- ({ experimentVariant, anyRunnersAvailable, templatesRendered }) => {
- beforeEach(() => {
- stubExperiments({ runners_availability_section: experimentVariant });
- wrapper = createWrapper({ anyRunnersAvailable });
- });
-
- it(`renders the templates: ${templatesRendered}`, () => {
- expect(findTestTemplateLink().exists()).toBe(templatesRendered);
- expect(findCiTemplates().exists()).toBe(templatesRendered);
- });
- },
- );
});
diff --git a/spec/frontend/pipelines/empty_state_spec.js b/spec/frontend/pipelines/empty_state_spec.js
index 0abf7f59717..5465e4d77da 100644
--- a/spec/frontend/pipelines/empty_state_spec.js
+++ b/spec/frontend/pipelines/empty_state_spec.js
@@ -35,11 +35,6 @@ describe('Pipelines Empty State', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('when user can configure CI', () => {
describe('when the ios_specific_templates experiment is active', () => {
beforeEach(() => {
diff --git a/spec/frontend/pipelines/graph/action_component_spec.js b/spec/frontend/pipelines/graph/action_component_spec.js
index e3eea503b46..890255f225e 100644
--- a/spec/frontend/pipelines/graph/action_component_spec.js
+++ b/spec/frontend/pipelines/graph/action_component_spec.js
@@ -33,7 +33,6 @@ describe('pipeline graph action component', () => {
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
describe('render', () => {
diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
index 99bccd21656..cc952eac1d7 100644
--- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
+++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
@@ -1,10 +1,10 @@
-import { GlAlert, GlButton, GlButtonGroup, GlLoadingIcon } from '@gitlab/ui';
-import { mount, shallowMount } from '@vue/test-utils';
+import { GlAlert, GlButton, GlButtonGroup, GlLoadingIcon, GlToggle } from '@gitlab/ui';
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 { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { stubPerformanceWebAPI } from 'helpers/performance';
import waitForPromises from 'helpers/wait_for_promises';
import getPipelineDetails from 'shared_queries/pipelines/get_pipeline_details.query.graphql';
@@ -48,23 +48,25 @@ describe('Pipeline graph wrapper', () => {
useLocalStorageSpy();
let wrapper;
- const getAlert = () => wrapper.findComponent(GlAlert);
- const getDependenciesToggle = () => wrapper.find('[data-testid="show-links-toggle"]');
- const getLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
- const getLinksLayer = () => wrapper.findComponent(LinksLayer);
- const getGraph = () => wrapper.findComponent(PipelineGraph);
- const getStageColumnTitle = () => wrapper.find('[data-testid="stage-column-title"]');
- const getAllStageColumnGroupsInColumn = () =>
+ let requestHandlers;
+ const findAlert = () => wrapper.findComponent(GlAlert);
+ const findDependenciesToggle = () => wrapper.findByTestId('show-links-toggle');
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findLinksLayer = () => wrapper.findComponent(LinksLayer);
+ const findGraph = () => wrapper.findComponent(PipelineGraph);
+ const findStageColumnTitle = () => wrapper.findByTestId('stage-column-title');
+ const findAllStageColumnGroupsInColumn = () =>
wrapper.findComponent(StageColumnComponent).findAll('[data-testid="stage-column-group"]');
- const getViewSelector = () => wrapper.findComponent(GraphViewSelector);
- const getViewSelectorTrip = () => getViewSelector().findComponent(GlAlert);
+ const findViewSelector = () => wrapper.findComponent(GraphViewSelector);
+ const findViewSelectorToggle = () => findViewSelector().findComponent(GlToggle);
+ const findViewSelectorTrip = () => findViewSelector().findComponent(GlAlert);
const getLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
const createComponent = ({
apolloProvider,
data = {},
provide = {},
- mountFn = shallowMount,
+ mountFn = shallowMountExtended,
} = {}) => {
wrapper = mountFn(PipelineGraphWrapper, {
provide: {
@@ -84,41 +86,41 @@ describe('Pipeline graph wrapper', () => {
calloutsList = [],
data = {},
getPipelineDetailsHandler = jest.fn().mockResolvedValue(mockPipelineResponse),
- mountFn = shallowMount,
+ mountFn = shallowMountExtended,
provide = {},
} = {}) => {
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],
+ requestHandlers = {
+ getUserCalloutsHandler: jest.fn().mockResolvedValue(mockCalloutsResponse(callouts)),
+ getPipelineHeaderDataHandler: jest.fn().mockResolvedValue(mockRunningPipelineHeaderData),
+ getPipelineDetailsHandler,
+ };
+
+ const handlers = [
+ [getPipelineHeaderData, requestHandlers.getPipelineHeaderDataHandler],
+ [getPipelineDetails, requestHandlers.getPipelineDetailsHandler],
+ [getUserCallouts, requestHandlers.getUserCalloutsHandler],
];
- const apolloProvider = createMockApollo(requestHandlers);
+ const apolloProvider = createMockApollo(handlers);
createComponent({ apolloProvider, data, provide, mountFn });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when data is loading', () => {
it('displays the loading icon', () => {
createComponentWithApollo();
- expect(getLoadingIcon().exists()).toBe(true);
+ expect(findLoadingIcon().exists()).toBe(true);
});
it('does not display the alert', () => {
createComponentWithApollo();
- expect(getAlert().exists()).toBe(false);
+ expect(findAlert().exists()).toBe(false);
});
it('does not display the graph', () => {
createComponentWithApollo();
- expect(getGraph().exists()).toBe(false);
+ expect(findGraph().exists()).toBe(false);
});
it('skips querying headerPipeline', () => {
@@ -134,19 +136,19 @@ describe('Pipeline graph wrapper', () => {
});
it('does not display the loading icon', () => {
- expect(getLoadingIcon().exists()).toBe(false);
+ expect(findLoadingIcon().exists()).toBe(false);
});
it('does not display the alert', () => {
- expect(getAlert().exists()).toBe(false);
+ expect(findAlert().exists()).toBe(false);
});
it('displays the graph', () => {
- expect(getGraph().exists()).toBe(true);
+ expect(findGraph().exists()).toBe(true);
});
it('passes the etag resource and metrics path to the graph', () => {
- expect(getGraph().props('configPaths')).toMatchObject({
+ expect(findGraph().props('configPaths')).toMatchObject({
graphqlResourceEtag: defaultProvide.graphqlResourceEtag,
metricsPath: defaultProvide.metricsPath,
});
@@ -162,15 +164,15 @@ describe('Pipeline graph wrapper', () => {
});
it('does not display the loading icon', () => {
- expect(getLoadingIcon().exists()).toBe(false);
+ expect(findLoadingIcon().exists()).toBe(false);
});
it('displays the alert', () => {
- expect(getAlert().exists()).toBe(true);
+ expect(findAlert().exists()).toBe(true);
});
it('does not display the graph', () => {
- expect(getGraph().exists()).toBe(false);
+ expect(findGraph().exists()).toBe(false);
});
});
@@ -185,18 +187,18 @@ describe('Pipeline graph wrapper', () => {
});
it('does not display the loading icon', () => {
- expect(getLoadingIcon().exists()).toBe(false);
+ expect(findLoadingIcon().exists()).toBe(false);
});
it('displays the no iid alert', () => {
- expect(getAlert().exists()).toBe(true);
- expect(getAlert().text()).toBe(
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(
'The data in this pipeline is too old to be rendered as a graph. Please check the Jobs tab to access historical data.',
);
});
it('does not display the graph', () => {
- expect(getGraph().exists()).toBe(false);
+ expect(findGraph().exists()).toBe(false);
});
});
@@ -207,11 +209,11 @@ describe('Pipeline graph wrapper', () => {
});
describe('when receiving `setSkipRetryModal` event', () => {
it('passes down `skipRetryModal` value as true', async () => {
- expect(getGraph().props('skipRetryModal')).toBe(false);
+ expect(findGraph().props('skipRetryModal')).toBe(false);
- await getGraph().vm.$emit('setSkipRetryModal');
+ await findGraph().vm.$emit('setSkipRetryModal');
- expect(getGraph().props('skipRetryModal')).toBe(true);
+ expect(findGraph().props('skipRetryModal')).toBe(true);
});
});
});
@@ -220,36 +222,37 @@ describe('Pipeline graph wrapper', () => {
beforeEach(async () => {
createComponentWithApollo();
await waitForPromises();
- await getGraph().vm.$emit('error', { type: ACTION_FAILURE });
+ await findGraph().vm.$emit('error', { type: ACTION_FAILURE });
});
it('does not display the loading icon', () => {
- expect(getLoadingIcon().exists()).toBe(false);
+ expect(findLoadingIcon().exists()).toBe(false);
});
it('displays the action error alert', () => {
- expect(getAlert().exists()).toBe(true);
- expect(getAlert().text()).toBe('An error occurred while performing this action.');
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe('An error occurred while performing this action.');
});
it('displays the graph', () => {
- expect(getGraph().exists()).toBe(true);
+ expect(findGraph().exists()).toBe(true);
});
});
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 waitForPromises();
- getGraph().vm.$emit('refreshPipelineGraph');
+ findGraph().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();
+ expect(requestHandlers.getPipelineHeaderDataHandler).toHaveBeenCalledWith({
+ fullPath: 'frog/amphibirama',
+ iid: '22',
+ });
+ expect(requestHandlers.getPipelineDetailsHandler).toHaveBeenCalledTimes(2);
+ expect(requestHandlers.getUserCalloutsHandler).toHaveBeenCalledWith({});
});
});
@@ -281,18 +284,18 @@ describe('Pipeline graph wrapper', () => {
it('shows correct errors and does not overwrite populated data when data is empty', async () => {
/* fails at first, shows error, no data yet */
- expect(getAlert().exists()).toBe(true);
- expect(getGraph().exists()).toBe(false);
+ expect(findAlert().exists()).toBe(true);
+ expect(findGraph().exists()).toBe(false);
/* succeeds, clears error, shows graph */
await advanceApolloTimers();
- expect(getAlert().exists()).toBe(false);
- expect(getGraph().exists()).toBe(true);
+ expect(findAlert().exists()).toBe(false);
+ expect(findGraph().exists()).toBe(true);
/* fails again, alert returns but data persists */
await advanceApolloTimers();
- expect(getAlert().exists()).toBe(true);
- expect(getGraph().exists()).toBe(true);
+ expect(findAlert().exists()).toBe(true);
+ expect(findGraph().exists()).toBe(true);
});
});
@@ -302,38 +305,38 @@ describe('Pipeline graph wrapper', () => {
beforeEach(async () => {
layersFn = jest.spyOn(parsingUtils, 'listByLayers');
createComponentWithApollo({
- mountFn: mount,
+ mountFn: mountExtended,
});
await waitForPromises();
});
it('appears when pipeline uses needs', () => {
- expect(getViewSelector().exists()).toBe(true);
+ expect(findViewSelector().exists()).toBe(true);
});
it('switches between views', async () => {
const groupsInFirstColumn =
mockPipelineResponse.data.project.pipeline.stages.nodes[0].groups.nodes.length;
- expect(getAllStageColumnGroupsInColumn()).toHaveLength(groupsInFirstColumn);
- expect(getStageColumnTitle().text()).toBe('build');
- await getViewSelector().vm.$emit('updateViewType', LAYER_VIEW);
- expect(getAllStageColumnGroupsInColumn()).toHaveLength(groupsInFirstColumn + 1);
- expect(getStageColumnTitle().text()).toBe('');
+ expect(findAllStageColumnGroupsInColumn()).toHaveLength(groupsInFirstColumn);
+ expect(findStageColumnTitle().text()).toBe('build');
+ await findViewSelector().vm.$emit('updateViewType', LAYER_VIEW);
+ expect(findAllStageColumnGroupsInColumn()).toHaveLength(groupsInFirstColumn + 1);
+ expect(findStageColumnTitle().text()).toBe('');
});
it('saves the view type to local storage', async () => {
- await getViewSelector().vm.$emit('updateViewType', LAYER_VIEW);
+ await findViewSelector().vm.$emit('updateViewType', LAYER_VIEW);
expect(localStorage.setItem.mock.calls).toEqual([[VIEW_TYPE_KEY, LAYER_VIEW]]);
});
it('calls listByLayers only once no matter how many times view is switched', async () => {
expect(layersFn).not.toHaveBeenCalled();
- await getViewSelector().vm.$emit('updateViewType', LAYER_VIEW);
+ await findViewSelector().vm.$emit('updateViewType', LAYER_VIEW);
expect(layersFn).toHaveBeenCalledTimes(1);
- await getViewSelector().vm.$emit('updateViewType', STAGE_VIEW);
- await getViewSelector().vm.$emit('updateViewType', LAYER_VIEW);
- await getViewSelector().vm.$emit('updateViewType', STAGE_VIEW);
+ await findViewSelector().vm.$emit('updateViewType', STAGE_VIEW);
+ await findViewSelector().vm.$emit('updateViewType', LAYER_VIEW);
+ await findViewSelector().vm.$emit('updateViewType', STAGE_VIEW);
expect(layersFn).toHaveBeenCalledTimes(1);
});
});
@@ -344,7 +347,7 @@ describe('Pipeline graph wrapper', () => {
data: {
currentViewType: LAYER_VIEW,
},
- mountFn: mount,
+ mountFn: mountExtended,
});
jest.runOnlyPendingTimers();
@@ -353,10 +356,10 @@ describe('Pipeline graph wrapper', () => {
it('sets showLinks to true', async () => {
/* This spec uses .props for performance reasons. */
- expect(getLinksLayer().exists()).toBe(true);
- expect(getLinksLayer().props('showLinks')).toBe(false);
- expect(getViewSelector().props('type')).toBe(LAYER_VIEW);
- await getDependenciesToggle().vm.$emit('change', true);
+ expect(findLinksLayer().exists()).toBe(true);
+ expect(findLinksLayer().props('showLinks')).toBe(false);
+ expect(findViewSelector().props('type')).toBe(LAYER_VIEW);
+ await findDependenciesToggle().vm.$emit('change', true);
jest.runOnlyPendingTimers();
await waitForPromises();
@@ -371,15 +374,15 @@ describe('Pipeline graph wrapper', () => {
currentViewType: LAYER_VIEW,
showLinks: true,
},
- mountFn: mount,
+ mountFn: mountExtended,
});
await waitForPromises();
});
it('shows the hover tip in the view selector', async () => {
- await getViewSelector().setData({ showLinksActive: true });
- expect(getViewSelectorTrip().exists()).toBe(true);
+ await findViewSelectorToggle().vm.$emit('change', true);
+ expect(findViewSelectorTrip().exists()).toBe(true);
});
});
@@ -390,7 +393,7 @@ describe('Pipeline graph wrapper', () => {
currentViewType: LAYER_VIEW,
showLinks: true,
},
- mountFn: mount,
+ mountFn: mountExtended,
calloutsList: ['pipeline_needs_hover_tip'.toUpperCase()],
});
@@ -399,8 +402,8 @@ describe('Pipeline graph wrapper', () => {
});
it('does not show the hover tip', async () => {
- await getViewSelector().setData({ showLinksActive: true });
- expect(getViewSelectorTrip().exists()).toBe(false);
+ await findViewSelectorToggle().vm.$emit('change', true);
+ expect(findViewSelectorTrip().exists()).toBe(false);
});
});
@@ -409,7 +412,7 @@ describe('Pipeline graph wrapper', () => {
localStorage.setItem(VIEW_TYPE_KEY, LAYER_VIEW);
createComponentWithApollo({
- mountFn: mount,
+ mountFn: mountExtended,
});
await waitForPromises();
@@ -440,7 +443,7 @@ describe('Pipeline graph wrapper', () => {
localStorage.setItem(VIEW_TYPE_KEY, LAYER_VIEW);
createComponentWithApollo({
- mountFn: mount,
+ mountFn: mountExtended,
getPipelineDetailsHandler: jest.fn().mockResolvedValue(nonNeedsResponse),
});
@@ -452,7 +455,7 @@ describe('Pipeline graph wrapper', () => {
});
it('still passes stage type to graph', () => {
- expect(getGraph().props('viewType')).toBe(STAGE_VIEW);
+ expect(findGraph().props('viewType')).toBe(STAGE_VIEW);
});
});
@@ -462,7 +465,7 @@ describe('Pipeline graph wrapper', () => {
nonNeedsResponse.data.project.pipeline.usesNeeds = false;
createComponentWithApollo({
- mountFn: mount,
+ mountFn: mountExtended,
getPipelineDetailsHandler: jest.fn().mockResolvedValue(nonNeedsResponse),
});
@@ -471,7 +474,7 @@ describe('Pipeline graph wrapper', () => {
});
it('does not appear when pipeline does not use needs', () => {
- expect(getViewSelector().exists()).toBe(false);
+ expect(findViewSelector().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/pipelines/graph/graph_view_selector_spec.js b/spec/frontend/pipelines/graph/graph_view_selector_spec.js
index 43587bebedf..65ae9d19978 100644
--- a/spec/frontend/pipelines/graph/graph_view_selector_spec.js
+++ b/spec/frontend/pipelines/graph/graph_view_selector_spec.js
@@ -42,10 +42,6 @@ describe('the graph view selector component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when showing stage view', () => {
beforeEach(() => {
createComponent({ mountFn: mount });
@@ -148,6 +144,7 @@ describe('the graph view selector component', () => {
createComponent({
props: {
showLinks: true,
+ type: LAYER_VIEW,
},
data: {
showLinksActive: true,
@@ -166,6 +163,18 @@ describe('the graph view selector component', () => {
await findHoverTip().find('button').trigger('click');
expect(wrapper.emitted().dismissHoverTip).toHaveLength(1);
});
+
+ it('is displayed at first then hidden on swith to STAGE_VIEW then displayed on switch to LAYER_VIEW', async () => {
+ expect(findHoverTip().exists()).toBe(true);
+ expect(findHoverTip().text()).toBe(wrapper.vm.$options.i18n.hoverTipText);
+
+ await findStageViewButton().trigger('click');
+ expect(findHoverTip().exists()).toBe(false);
+
+ await findLayerViewButton().trigger('click');
+ expect(findHoverTip().exists()).toBe(true);
+ expect(findHoverTip().text()).toBe(wrapper.vm.$options.i18n.hoverTipText);
+ });
});
describe('when links are live and it has been previously dismissed', () => {
@@ -174,6 +183,7 @@ describe('the graph view selector component', () => {
props: {
showLinks: true,
tipPreviouslyDismissed: true,
+ type: LAYER_VIEW,
},
data: {
showLinksActive: true,
@@ -191,6 +201,7 @@ describe('the graph view selector component', () => {
createComponent({
props: {
showLinks: true,
+ type: LAYER_VIEW,
},
data: {
showLinksActive: false,
diff --git a/spec/frontend/pipelines/graph/job_group_dropdown_spec.js b/spec/frontend/pipelines/graph/job_group_dropdown_spec.js
index d8afb33e148..1419a7b9982 100644
--- a/spec/frontend/pipelines/graph/job_group_dropdown_spec.js
+++ b/spec/frontend/pipelines/graph/job_group_dropdown_spec.js
@@ -69,10 +69,6 @@ describe('job group dropdown component', () => {
wrapper = mountFn(JobGroupDropdown, { propsData: { group } });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
beforeEach(() => {
createComponent({ mountFn: mount });
});
diff --git a/spec/frontend/pipelines/graph/job_item_spec.js b/spec/frontend/pipelines/graph/job_item_spec.js
index 3224c87ab6b..2a5dfd7e0ee 100644
--- a/spec/frontend/pipelines/graph/job_item_spec.js
+++ b/spec/frontend/pipelines/graph/job_item_spec.js
@@ -1,10 +1,11 @@
import MockAdapter from 'axios-mock-adapter';
-import { mount } from '@vue/test-utils';
-import { nextTick } from 'vue';
-import { GlBadge, GlModal } from '@gitlab/ui';
+import { shallowMount, mount } from '@vue/test-utils';
+import Vue, { nextTick } from 'vue';
+import { GlBadge, GlModal, GlToast } from '@gitlab/ui';
import JobItem from '~/pipelines/components/graph/job_item.vue';
import axios from '~/lib/utils/axios_utils';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
+import ActionComponent from '~/pipelines/components/jobs_shared/action_component.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import {
@@ -19,12 +20,14 @@ import {
describe('pipeline graph job item', () => {
useLocalStorageSpy();
+ Vue.use(GlToast);
let wrapper;
let mockAxios;
const findJobWithoutLink = () => wrapper.findByTestId('job-without-link');
const findJobWithLink = () => wrapper.findByTestId('job-with-link');
+ const findActionVueComponent = () => wrapper.findComponent(ActionComponent);
const findActionComponent = () => wrapper.findByTestId('ci-action-component');
const findBadge = () => wrapper.findComponent(GlBadge);
const findJobLink = () => wrapper.findByTestId('job-with-link');
@@ -41,9 +44,9 @@ describe('pipeline graph job item', () => {
job: mockJob,
};
- const createWrapper = ({ props, data } = {}) => {
+ const createWrapper = ({ props, data, mountFn = mount, mocks = {} } = {}) => {
wrapper = extendedWrapper(
- mount(JobItem, {
+ mountFn(JobItem, {
data() {
return {
...data,
@@ -53,6 +56,9 @@ describe('pipeline graph job item', () => {
...defaultProps,
...props,
},
+ mocks: {
+ ...mocks,
+ },
}),
);
};
@@ -115,7 +121,7 @@ describe('pipeline graph job item', () => {
expect(actionComponent.exists()).toBe(true);
expect(actionComponent.props('actionIcon')).toBe('retry');
- expect(actionComponent.attributes('disabled')).not.toBe('disabled');
+ expect(actionComponent.attributes('disabled')).toBeUndefined();
});
it('should render disabled action icon when user cannot run the action', () => {
@@ -129,7 +135,7 @@ describe('pipeline graph job item', () => {
expect(actionComponent.exists()).toBe(true);
expect(actionComponent.props('actionIcon')).toBe('stop');
- expect(actionComponent.attributes('disabled')).toBe('disabled');
+ expect(actionComponent.attributes('disabled')).toBeDefined();
});
it('action icon tooltip text when job has passed but can be ran again', () => {
@@ -238,6 +244,37 @@ describe('pipeline graph job item', () => {
});
});
+ describe('when retrying', () => {
+ const mockToastShow = jest.fn();
+
+ beforeEach(async () => {
+ createWrapper({
+ mountFn: shallowMount,
+ data: {
+ currentSkipModalValue: true,
+ },
+ props: {
+ skipRetryModal: true,
+ job: triggerJobWithRetryAction,
+ },
+ mocks: {
+ $toast: {
+ show: mockToastShow,
+ },
+ },
+ });
+
+ jest.spyOn(wrapper.vm.$toast, 'show');
+
+ await findActionVueComponent().vm.$emit('pipelineActionRequestComplete');
+ await nextTick();
+ });
+
+ it('shows a toast message that the downstream is being created', () => {
+ expect(mockToastShow).toHaveBeenCalledTimes(1);
+ });
+ });
+
describe('highlighting', () => {
it.each`
job | jobName | expanded | link
diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
index f396fe2aff4..bf92cd585d9 100644
--- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
@@ -1,11 +1,10 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { GlButton, GlLoadingIcon, GlTooltip } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
+import { createWrapper } from '@vue/test-utils';
import createMockApollo from 'helpers/mock_apollo_helper';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import { convertToGraphQLId } from '~/graphql_shared/utils';
-import { TYPENAME_CI_PIPELINE } from '~/graphql_shared/constants';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { ACTION_FAILURE, UPSTREAM, DOWNSTREAM } from '~/pipelines/components/graph/constants';
import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue';
@@ -14,10 +13,9 @@ import RetryPipelineMutation from '~/pipelines/graphql/mutations/retry_pipeline.
import CiStatus from '~/vue_shared/components/ci_icon.vue';
import mockPipeline from './linked_pipelines_mock_data';
-Vue.use(VueApollo);
-
describe('Linked pipeline', () => {
let wrapper;
+ let requestHandlers;
const downstreamProps = {
pipeline: {
@@ -47,20 +45,29 @@ describe('Linked pipeline', () => {
const findPipelineLink = () => wrapper.findByTestId('pipelineLink');
const findRetryButton = () => wrapper.findByLabelText('Retry downstream pipeline');
- const createWrapper = ({ propsData }) => {
- const mockApollo = createMockApollo();
+ const defaultHandlers = {
+ cancelPipeline: jest.fn().mockResolvedValue({ data: { pipelineCancel: { errors: [] } } }),
+ retryPipeline: jest.fn().mockResolvedValue({ data: { pipelineRetry: { errors: [] } } }),
+ };
- wrapper = extendedWrapper(
- mount(LinkedPipelineComponent, {
- propsData,
- apolloProvider: mockApollo,
- }),
- );
+ const createMockApolloProvider = (handlers) => {
+ Vue.use(VueApollo);
+
+ requestHandlers = handlers;
+ return createMockApollo([
+ [CancelPipelineMutation, requestHandlers.cancelPipeline],
+ [RetryPipelineMutation, requestHandlers.retryPipeline],
+ ]);
};
- afterEach(() => {
- wrapper.destroy();
- });
+ const createComponent = ({ propsData, handlers = defaultHandlers }) => {
+ const mockApollo = createMockApolloProvider(handlers);
+
+ wrapper = mountExtended(LinkedPipelineComponent, {
+ propsData,
+ apolloProvider: mockApollo,
+ });
+ };
describe('rendered output', () => {
const props = {
@@ -72,7 +79,7 @@ describe('Linked pipeline', () => {
};
beforeEach(() => {
- createWrapper({ propsData: props });
+ createComponent({ propsData: props });
});
it('should render the project name', () => {
@@ -113,7 +120,7 @@ describe('Linked pipeline', () => {
describe('upstream pipelines', () => {
beforeEach(() => {
- createWrapper({ propsData: upstreamProps });
+ createComponent({ propsData: upstreamProps });
});
it('should display parent label when pipeline project id is the same as triggered_by pipeline project id', () => {
@@ -133,7 +140,7 @@ describe('Linked pipeline', () => {
describe('downstream pipelines', () => {
describe('styling', () => {
beforeEach(() => {
- createWrapper({ propsData: downstreamProps });
+ createComponent({ propsData: downstreamProps });
});
it('parent/child label container should exist', () => {
@@ -168,7 +175,7 @@ describe('Linked pipeline', () => {
pipeline: { ...mockPipeline, retryable: true },
};
- createWrapper({ propsData: retryablePipeline });
+ createComponent({ propsData: retryablePipeline });
});
it('does not show the retry or cancel button', () => {
@@ -179,14 +186,14 @@ describe('Linked pipeline', () => {
});
describe('on a downstream', () => {
+ const retryablePipeline = {
+ ...downstreamProps,
+ pipeline: { ...mockPipeline, retryable: true },
+ };
+
describe('when retryable', () => {
beforeEach(() => {
- const retryablePipeline = {
- ...downstreamProps,
- pipeline: { ...mockPipeline, retryable: true },
- };
-
- createWrapper({ propsData: retryablePipeline });
+ createComponent({ propsData: retryablePipeline });
});
it('shows only the retry button', () => {
@@ -209,50 +216,51 @@ describe('Linked pipeline', () => {
describe('and the retry button is clicked', () => {
describe('on success', () => {
beforeEach(async () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
- jest.spyOn(wrapper.vm, '$emit');
await findRetryButton().trigger('click');
});
it('calls the retry mutation', () => {
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: RetryPipelineMutation,
- variables: {
- id: convertToGraphQLId(TYPENAME_CI_PIPELINE, mockPipeline.id),
- },
+ expect(requestHandlers.retryPipeline).toHaveBeenCalledTimes(1);
+ expect(requestHandlers.retryPipeline).toHaveBeenCalledWith({
+ id: 'gid://gitlab/Ci::Pipeline/195',
});
});
- it('emits the refreshPipelineGraph event', () => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('refreshPipelineGraph');
+ it('emits the refreshPipelineGraph event', async () => {
+ await waitForPromises();
+ expect(wrapper.emitted('refreshPipelineGraph')).toHaveLength(1);
});
});
describe('on failure', () => {
beforeEach(async () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue({ errors: [] });
- jest.spyOn(wrapper.vm, '$emit');
+ createComponent({
+ propsData: retryablePipeline,
+ handlers: {
+ retryPipeline: jest.fn().mockRejectedValue({ errors: [] }),
+ cancelPipeline: jest.fn().mockRejectedValue({ errors: [] }),
+ },
+ });
+
await findRetryButton().trigger('click');
});
- it('emits an error event', () => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('error', {
- type: ACTION_FAILURE,
- });
+ it('emits an error event', async () => {
+ await waitForPromises();
+ expect(wrapper.emitted('error')).toEqual([[{ type: ACTION_FAILURE }]]);
});
});
});
});
describe('when cancelable', () => {
- beforeEach(() => {
- const cancelablePipeline = {
- ...downstreamProps,
- pipeline: { ...mockPipeline, cancelable: true },
- };
+ const cancelablePipeline = {
+ ...downstreamProps,
+ pipeline: { ...mockPipeline, cancelable: true },
+ };
- createWrapper({ propsData: cancelablePipeline });
+ beforeEach(() => {
+ createComponent({ propsData: cancelablePipeline });
});
it('shows only the cancel button', () => {
@@ -275,34 +283,37 @@ describe('Linked pipeline', () => {
describe('and the cancel button is clicked', () => {
describe('on success', () => {
beforeEach(async () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
- jest.spyOn(wrapper.vm, '$emit');
await findCancelButton().trigger('click');
});
it('calls the cancel mutation', () => {
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: CancelPipelineMutation,
- variables: {
- id: convertToGraphQLId(TYPENAME_CI_PIPELINE, mockPipeline.id),
- },
+ expect(requestHandlers.cancelPipeline).toHaveBeenCalledTimes(1);
+ expect(requestHandlers.cancelPipeline).toHaveBeenCalledWith({
+ id: 'gid://gitlab/Ci::Pipeline/195',
});
});
- it('emits the refreshPipelineGraph event', () => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('refreshPipelineGraph');
+ it('emits the refreshPipelineGraph event', async () => {
+ await waitForPromises();
+ expect(wrapper.emitted('refreshPipelineGraph')).toHaveLength(1);
});
});
+
describe('on failure', () => {
beforeEach(async () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue({ errors: [] });
- jest.spyOn(wrapper.vm, '$emit');
+ createComponent({
+ propsData: cancelablePipeline,
+ handlers: {
+ retryPipeline: jest.fn().mockRejectedValue({ errors: [] }),
+ cancelPipeline: jest.fn().mockRejectedValue({ errors: [] }),
+ },
+ });
+
await findCancelButton().trigger('click');
});
- it('emits an error event', () => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('error', {
- type: ACTION_FAILURE,
- });
+
+ it('emits an error event', async () => {
+ await waitForPromises();
+ expect(wrapper.emitted('error')).toEqual([[{ type: ACTION_FAILURE }]]);
});
});
});
@@ -315,7 +326,7 @@ describe('Linked pipeline', () => {
pipeline: { ...mockPipeline, cancelable: true, retryable: true },
};
- createWrapper({ propsData: pipelineWithTwoActions });
+ createComponent({ propsData: pipelineWithTwoActions });
});
it('only shows the cancel button', () => {
@@ -338,7 +349,7 @@ describe('Linked pipeline', () => {
},
};
- createWrapper({ propsData: pipelineWithTwoActions });
+ createComponent({ propsData: pipelineWithTwoActions });
});
it('does not show any action button', () => {
@@ -359,7 +370,7 @@ describe('Linked pipeline', () => {
`(
'$pipelineType.columnTitle pipeline button icon should be $chevronPosition with $buttonBorderClasses if expanded state is $expanded',
({ pipelineType, chevronPosition, buttonBorderClasses, expanded }) => {
- createWrapper({ propsData: { ...pipelineType, expanded } });
+ createComponent({ propsData: { ...pipelineType, expanded } });
expect(findExpandButton().props('icon')).toBe(chevronPosition);
expect(findExpandButton().classes()).toContain(buttonBorderClasses);
},
@@ -367,7 +378,7 @@ describe('Linked pipeline', () => {
describe('shadow border', () => {
beforeEach(() => {
- createWrapper({ propsData: downstreamProps });
+ createComponent({ propsData: downstreamProps });
});
it.each`
@@ -401,7 +412,7 @@ describe('Linked pipeline', () => {
};
beforeEach(() => {
- createWrapper({ propsData: props });
+ createComponent({ propsData: props });
});
it('loading icon is visible', () => {
@@ -419,36 +430,35 @@ describe('Linked pipeline', () => {
};
beforeEach(() => {
- createWrapper({ propsData: props });
+ createComponent({ propsData: props });
});
it('emits `pipelineClicked` event', () => {
- jest.spyOn(wrapper.vm, '$emit');
findButton().trigger('click');
- expect(wrapper.emitted().pipelineClicked).toHaveLength(1);
+ expect(wrapper.emitted('pipelineClicked')).toHaveLength(1);
});
- it(`should emit ${BV_HIDE_TOOLTIP} to close the tooltip`, () => {
- jest.spyOn(wrapper.vm.$root, '$emit');
- findButton().trigger('click');
+ it(`should emit ${BV_HIDE_TOOLTIP} to close the tooltip`, async () => {
+ const root = createWrapper(wrapper.vm.$root);
+ await findButton().vm.$emit('click');
- expect(wrapper.vm.$root.$emit.mock.calls[0]).toEqual([BV_HIDE_TOOLTIP]);
+ expect(root.emitted(BV_HIDE_TOOLTIP)).toHaveLength(1);
});
it('should emit downstreamHovered with job name on mouseover', () => {
findLinkedPipeline().trigger('mouseover');
- expect(wrapper.emitted().downstreamHovered).toStrictEqual([['test_c']]);
+ expect(wrapper.emitted('downstreamHovered')).toStrictEqual([['test_c']]);
});
it('should emit downstreamHovered with empty string on mouseleave', () => {
findLinkedPipeline().trigger('mouseleave');
- expect(wrapper.emitted().downstreamHovered).toStrictEqual([['']]);
+ expect(wrapper.emitted('downstreamHovered')).toStrictEqual([['']]);
});
it('should emit pipelineExpanded with job name and expanded state on click', () => {
findExpandButton().trigger('click');
- expect(wrapper.emitted().pipelineExpandToggle).toStrictEqual([['test_c', true]]);
+ expect(wrapper.emitted('pipelineExpandToggle')).toStrictEqual([['test_c', true]]);
});
});
});
diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
index 63e2d8707ea..6e4b9498918 100644
--- a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
@@ -65,10 +65,6 @@ describe('Linked Pipelines Column', () => {
createComponent({ apolloProvider, mountFn, props });
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('it renders correctly', () => {
beforeEach(() => {
createComponentWithApollo();
diff --git a/spec/frontend/pipelines/graph/stage_column_component_spec.js b/spec/frontend/pipelines/graph/stage_column_component_spec.js
index 19f597a7267..d4d7f1618c5 100644
--- a/spec/frontend/pipelines/graph/stage_column_component_spec.js
+++ b/spec/frontend/pipelines/graph/stage_column_component_spec.js
@@ -54,10 +54,6 @@ describe('stage column component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when mounted', () => {
beforeEach(() => {
createComponent({ method: mount });
diff --git a/spec/frontend/pipelines/graph_shared/links_inner_spec.js b/spec/frontend/pipelines/graph_shared/links_inner_spec.js
index 2c6d126e12c..50f754393fe 100644
--- a/spec/frontend/pipelines/graph_shared/links_inner_spec.js
+++ b/spec/frontend/pipelines/graph_shared/links_inner_spec.js
@@ -81,7 +81,6 @@ describe('Links Inner component', () => {
afterEach(() => {
jest.restoreAllMocks();
- wrapper.destroy();
resetHTMLFixture();
});
diff --git a/spec/frontend/pipelines/graph_shared/links_layer_spec.js b/spec/frontend/pipelines/graph_shared/links_layer_spec.js
index e2699d6ff2e..9d39c86ed5e 100644
--- a/spec/frontend/pipelines/graph_shared/links_layer_spec.js
+++ b/spec/frontend/pipelines/graph_shared/links_layer_spec.js
@@ -35,10 +35,6 @@ describe('links layer component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with show links off', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/pipelines/header_component_spec.js b/spec/frontend/pipelines/header_component_spec.js
index e583c0798f5..18def4ab62c 100644
--- a/spec/frontend/pipelines/header_component_spec.js
+++ b/spec/frontend/pipelines/header_component_spec.js
@@ -6,7 +6,7 @@ import HeaderComponent from '~/pipelines/components/header_component.vue';
import cancelPipelineMutation from '~/pipelines/graphql/mutations/cancel_pipeline.mutation.graphql';
import deletePipelineMutation from '~/pipelines/graphql/mutations/delete_pipeline.mutation.graphql';
import retryPipelineMutation from '~/pipelines/graphql/mutations/retry_pipeline.mutation.graphql';
-import { BUTTON_TOOLTIP_RETRY } from '~/pipelines/constants';
+import { BUTTON_TOOLTIP_RETRY, BUTTON_TOOLTIP_CANCEL } from '~/pipelines/constants';
import {
mockCancelledPipelineHeader,
mockFailedPipelineHeader,
@@ -71,11 +71,6 @@ describe('Pipeline details header', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('initial loading', () => {
beforeEach(() => {
wrapper = createComponent(null, { isLoading: true });
@@ -174,6 +169,10 @@ describe('Pipeline details header', () => {
});
});
+ it('should render cancel action tooltip', () => {
+ expect(findCancelButton().attributes('title')).toBe(BUTTON_TOOLTIP_CANCEL);
+ });
+
it('should display error message on failure', async () => {
const failureMessage = 'failure message';
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
diff --git a/spec/frontend/pipelines/mock_data.js b/spec/frontend/pipelines/mock_data.js
index dd7e81f3f22..a4b8d223a0c 100644
--- a/spec/frontend/pipelines/mock_data.js
+++ b/spec/frontend/pipelines/mock_data.js
@@ -1190,6 +1190,10 @@ export const mockFailedJobsQueryResponse = {
readBuild: true,
updateBuild: true,
},
+ trace: {
+ htmlSummary: '<span>Html Summary</span>',
+ },
+ failureMessage: 'Failed',
},
{
__typename: 'CiJob',
@@ -1218,6 +1222,8 @@ export const mockFailedJobsQueryResponse = {
readBuild: true,
updateBuild: true,
},
+ trace: null,
+ failureMessage: 'Failed',
},
],
},
@@ -1226,18 +1232,8 @@ export const mockFailedJobsQueryResponse = {
},
};
-export const mockFailedJobsSummaryData = [
- {
- id: 1848,
- failure: null,
- failure_summary:
- '<span>Pulling docker image node:latest ...<br/></span><span>Using docker image sha256:738d733448be00c72cb6618b7a06a1424806c6d239d8885e92f9b1e8727092b5 for node:latest with digest node@sha256:e5b7b349d517159246070bf14242027a9e220ffa8bd98a67ba1495d969c06c01 ...<br/></span><div class="section-start" data-timestamp="1651175313" data-section="prepare-script" role="button"></div><span class="term-fg-l-cyan term-bold section section-header js-s-prepare-script">Preparing environment</span><span class="section section-header js-s-prepare-script"><br/></span><span class="section line js-s-prepare-script">Running on runner-kvkqh24-project-20-concurrent-0 via 0706719b1b8d...<br/></span><div class="section-end" data-section="prepare-script"></div><div class="section-start" data-timestamp="1651175313" data-section="get-sources" role="button"></div><span class="term-fg-l-cyan term-bold section section-header js-s-get-sources">Getting source from Git repository</span><span class="section section-header js-s-get-sources"><br/></span><span class="term-fg-l-green term-bold section line js-s-get-sources">Fetching changes with git depth set to 50...</span><span class="section line js-s-get-sources"><br/>Reinitialized existing Git repository in /builds/root/ci-project/.git/<br/>fatal: couldn\'t find remote ref refs/heads/test<br/></span><div class="section-end" data-section="get-sources"></div><span class="term-fg-l-red term-bold">ERROR: Job failed: exit code 1<br/></span>',
- },
-];
-
export const mockFailedJobsData = [
{
- normalizedId: 1848,
__typename: 'CiJob',
status: 'FAILED',
detailedStatus: {
@@ -1260,13 +1256,25 @@ export const mockFailedJobsData = [
},
},
id: 'gid://gitlab/Ci::Build/1848',
- stage: { __typename: 'CiStage', id: 'gid://gitlab/Ci::Stage/358', name: 'build' },
+ stage: {
+ __typename: 'CiStage',
+ id: 'gid://gitlab/Ci::Stage/358',
+ name: 'build',
+ },
name: 'wait_job',
retryable: true,
- userPermissions: { __typename: 'JobPermissions', readBuild: true, updateBuild: true },
+ userPermissions: {
+ __typename: 'JobPermissions',
+ readBuild: true,
+ updateBuild: true,
+ },
+ trace: {
+ htmlSummary: '<span>Html Summary</span>',
+ },
+ failureMessage: 'Job failed',
+ _showDetails: true,
},
{
- normalizedId: 1710,
__typename: 'CiJob',
status: 'FAILED',
detailedStatus: {
@@ -1281,52 +1289,27 @@ export const mockFailedJobsData = [
action: null,
},
id: 'gid://gitlab/Ci::Build/1710',
- stage: { __typename: 'CiStage', id: 'gid://gitlab/Ci::Stage/358', name: 'build' },
+ stage: {
+ __typename: 'CiStage',
+ id: 'gid://gitlab/Ci::Stage/358',
+ name: 'build',
+ },
name: 'wait_job',
retryable: false,
- userPermissions: { __typename: 'JobPermissions', readBuild: true, updateBuild: true },
- },
-];
-
-export const mockPreparedFailedJobsData = [
- {
- __typename: 'CiJob',
- _showDetails: true,
- detailedStatus: {
- __typename: 'DetailedStatus',
- action: {
- __typename: 'StatusAction',
- buttonTitle: 'Retry this job',
- icon: 'retry',
- id: 'Ci::Build-failed-1848',
- method: 'post',
- path: '/root/ci-project/-/jobs/1848/retry',
- title: 'Retry',
- },
- detailsPath: '/root/ci-project/-/jobs/1848',
- group: 'failed',
- icon: 'status_failed',
- id: 'failed-1848-1848',
- label: 'failed',
- text: 'failed',
- tooltip: 'failed - (script failure)',
+ userPermissions: {
+ __typename: 'JobPermissions',
+ readBuild: true,
+ updateBuild: true,
},
- failure: null,
- failureSummary:
- '<span>Pulling docker image node:latest ...<br/></span><span>Using docker image sha256:738d733448be00c72cb6618b7a06a1424806c6d239d8885e92f9b1e8727092b5 for node:latest with digest node@sha256:e5b7b349d517159246070bf14242027a9e220ffa8bd98a67ba1495d969c06c01 ...<br/></span><div class="section-start" data-timestamp="1651175313" data-section="prepare-script" role="button"></div><span class="term-fg-l-cyan term-bold section section-header js-s-prepare-script">Preparing environment</span><span class="section section-header js-s-prepare-script"><br/></span><span class="section line js-s-prepare-script">Running on runner-kvkqh24-project-20-concurrent-0 via 0706719b1b8d...<br/></span><div class="section-end" data-section="prepare-script"></div><div class="section-start" data-timestamp="1651175313" data-section="get-sources" role="button"></div><span class="term-fg-l-cyan term-bold section section-header js-s-get-sources">Getting source from Git repository</span><span class="section section-header js-s-get-sources"><br/></span><span class="term-fg-l-green term-bold section line js-s-get-sources">Fetching changes with git depth set to 50...</span><span class="section line js-s-get-sources"><br/>Reinitialized existing Git repository in /builds/root/ci-project/.git/<br/>fatal: couldn\'t find remote ref refs/heads/test<br/></span><div class="section-end" data-section="get-sources"></div><span class="term-fg-l-red term-bold">ERROR: Job failed: exit code 1<br/></span>',
- id: 'gid://gitlab/Ci::Build/1848',
- name: 'wait_job',
- normalizedId: 1848,
- retryable: true,
- stage: { __typename: 'CiStage', id: 'gid://gitlab/Ci::Stage/358', name: 'build' },
- status: 'FAILED',
- userPermissions: { __typename: 'JobPermissions', readBuild: true, updateBuild: true },
+ trace: null,
+ failureMessage: 'Job failed',
+ _showDetails: true,
},
];
-export const mockPreparedFailedJobsDataNoPermission = [
+export const mockFailedJobsDataNoPermission = [
{
- ...mockPreparedFailedJobsData[0],
+ ...mockFailedJobsData[0],
userPermissions: { __typename: 'JobPermissions', readBuild: false, updateBuild: false },
},
];
diff --git a/spec/frontend/pipelines/nav_controls_spec.js b/spec/frontend/pipelines/nav_controls_spec.js
index 2c4740df174..15de7dc51f1 100644
--- a/spec/frontend/pipelines/nav_controls_spec.js
+++ b/spec/frontend/pipelines/nav_controls_spec.js
@@ -1,23 +1,20 @@
-import { shallowMount } from '@vue/test-utils';
-import { nextTick } from 'vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import NavControls from '~/pipelines/components/pipelines_list/nav_controls.vue';
describe('Pipelines Nav Controls', () => {
let wrapper;
const createComponent = (props) => {
- wrapper = shallowMount(NavControls, {
+ wrapper = shallowMountExtended(NavControls, {
propsData: {
...props,
},
});
};
- const findRunPipeline = () => wrapper.find('.js-run-pipeline');
-
- afterEach(() => {
- wrapper.destroy();
- });
+ const findRunPipelineButton = () => wrapper.findByTestId('run-pipeline-button');
+ const findCiLintButton = () => wrapper.findByTestId('ci-lint-button');
+ const findClearCacheButton = () => wrapper.findByTestId('clear-cache-button');
it('should render link to create a new pipeline', () => {
const mockData = {
@@ -28,9 +25,9 @@ describe('Pipelines Nav Controls', () => {
createComponent(mockData);
- const runPipeline = findRunPipeline();
- expect(runPipeline.text()).toContain('Run pipeline');
- expect(runPipeline.attributes('href')).toBe(mockData.newPipelinePath);
+ const runPipelineButton = findRunPipelineButton();
+ expect(runPipelineButton.text()).toContain('Run pipeline');
+ expect(runPipelineButton.attributes('href')).toBe(mockData.newPipelinePath);
});
it('should not render link to create pipeline if no path is provided', () => {
@@ -42,7 +39,7 @@ describe('Pipelines Nav Controls', () => {
createComponent(mockData);
- expect(findRunPipeline().exists()).toBe(false);
+ expect(findRunPipelineButton().exists()).toBe(false);
});
it('should render link for CI lint', () => {
@@ -54,9 +51,10 @@ describe('Pipelines Nav Controls', () => {
};
createComponent(mockData);
+ const ciLintButton = findCiLintButton();
- expect(wrapper.find('.js-ci-lint').text().trim()).toContain('CI lint');
- expect(wrapper.find('.js-ci-lint').attributes('href')).toBe(mockData.ciLintPath);
+ expect(ciLintButton.text()).toContain('CI lint');
+ expect(ciLintButton.attributes('href')).toBe(mockData.ciLintPath);
});
describe('Reset Runners Cache', () => {
@@ -70,16 +68,13 @@ describe('Pipelines Nav Controls', () => {
});
it('should render button for resetting runner caches', () => {
- expect(wrapper.find('.js-clear-cache').text().trim()).toContain('Clear runner caches');
+ expect(findClearCacheButton().text()).toContain('Clear runner caches');
});
- it('should emit postAction event when reset runner cache button is clicked', async () => {
- jest.spyOn(wrapper.vm, '$emit').mockImplementation(() => {});
-
- wrapper.find('.js-clear-cache').vm.$emit('click');
- await nextTick();
+ it('should emit postAction event when reset runner cache button is clicked', () => {
+ findClearCacheButton().vm.$emit('click');
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('resetRunnersCache', 'foo');
+ expect(wrapper.emitted('resetRunnersCache')).toEqual([['foo']]);
});
});
});
diff --git a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
index df10742fd93..123f2e011c3 100644
--- a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
+++ b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js
@@ -39,10 +39,6 @@ describe('pipeline graph component', () => {
const findLinksLayer = () => wrapper.findComponent(LinksLayer);
const findPipelineGraph = () => wrapper.find('[data-testid="graph-container"]');
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('with `VALID` status', () => {
beforeEach(() => {
wrapper = createComponent({
diff --git a/spec/frontend/pipelines/pipeline_labels_spec.js b/spec/frontend/pipelines/pipeline_labels_spec.js
index ca0229b1cbe..6a37e36352b 100644
--- a/spec/frontend/pipelines/pipeline_labels_spec.js
+++ b/spec/frontend/pipelines/pipeline_labels_spec.js
@@ -30,10 +30,6 @@ describe('Pipeline label component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should not render tags when flags are not set', () => {
createComponent();
diff --git a/spec/frontend/pipelines/pipeline_multi_actions_spec.js b/spec/frontend/pipelines/pipeline_multi_actions_spec.js
index bedde71c48d..e3c9983aa52 100644
--- a/spec/frontend/pipelines/pipeline_multi_actions_spec.js
+++ b/spec/frontend/pipelines/pipeline_multi_actions_spec.js
@@ -67,8 +67,6 @@ describe('Pipeline Multi Actions Dropdown', () => {
afterEach(() => {
mockAxios.restore();
-
- wrapper.destroy();
});
it('should render the dropdown', () => {
diff --git a/spec/frontend/pipelines/pipeline_operations_spec.js b/spec/frontend/pipelines/pipeline_operations_spec.js
new file mode 100644
index 00000000000..b2191453824
--- /dev/null
+++ b/spec/frontend/pipelines/pipeline_operations_spec.js
@@ -0,0 +1,77 @@
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import PipelinesManualActions from '~/pipelines/components/pipelines_list/pipelines_manual_actions.vue';
+import PipelineMultiActions from '~/pipelines/components/pipelines_list/pipeline_multi_actions.vue';
+import PipelineOperations from '~/pipelines/components/pipelines_list/pipeline_operations.vue';
+import eventHub from '~/pipelines/event_hub';
+
+describe('Pipeline operations', () => {
+ let wrapper;
+
+ const defaultProps = {
+ pipeline: {
+ id: 329,
+ iid: 234,
+ details: {
+ has_manual_actions: true,
+ has_scheduled_actions: false,
+ },
+ flags: {
+ retryable: true,
+ cancelable: true,
+ },
+ cancel_path: '/root/ci-project/-/pipelines/329/cancel',
+ retry_path: '/root/ci-project/-/pipelines/329/retry',
+ },
+ };
+
+ const createComponent = (props = defaultProps) => {
+ wrapper = shallowMountExtended(PipelineOperations, {
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ const findManualActions = () => wrapper.findComponent(PipelinesManualActions);
+ const findMultiActions = () => wrapper.findComponent(PipelineMultiActions);
+ const findRetryBtn = () => wrapper.findByTestId('pipelines-retry-button');
+ const findCancelBtn = () => wrapper.findByTestId('pipelines-cancel-button');
+
+ it('should display pipeline manual actions', () => {
+ createComponent();
+
+ expect(findManualActions().exists()).toBe(true);
+ });
+
+ it('should display pipeline multi actions', () => {
+ createComponent();
+
+ expect(findMultiActions().exists()).toBe(true);
+ });
+
+ describe('events', () => {
+ beforeEach(() => {
+ createComponent();
+
+ jest.spyOn(eventHub, '$emit').mockImplementation(() => {});
+ });
+
+ it('should emit retryPipeline event', () => {
+ findRetryBtn().vm.$emit('click');
+
+ expect(eventHub.$emit).toHaveBeenCalledWith(
+ 'retryPipeline',
+ defaultProps.pipeline.retry_path,
+ );
+ });
+
+ it('should emit openConfirmationModal event', () => {
+ findCancelBtn().vm.$emit('click');
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('openConfirmationModal', {
+ pipeline: defaultProps.pipeline,
+ endpoint: defaultProps.pipeline.cancel_path,
+ });
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/pipeline_tabs_spec.js b/spec/frontend/pipelines/pipeline_tabs_spec.js
index 099748a5cca..8d1cd98e981 100644
--- a/spec/frontend/pipelines/pipeline_tabs_spec.js
+++ b/spec/frontend/pipelines/pipeline_tabs_spec.js
@@ -25,7 +25,6 @@ describe('~/pipelines/pipeline_tabs.js', () => {
el.dataset.exposeSecurityDashboard = 'true';
el.dataset.exposeLicenseScanningData = 'true';
el.dataset.failedJobsCount = 1;
- el.dataset.failedJobsSummary = '[]';
el.dataset.graphqlResourceEtag = 'graphqlResourceEtag';
el.dataset.pipelineIid = '123';
el.dataset.pipelineProjectPath = 'pipelineProjectPath';
@@ -50,7 +49,6 @@ describe('~/pipelines/pipeline_tabs.js', () => {
exposeSecurityDashboard: true,
exposeLicenseScanningData: true,
failedJobsCount: '1',
- failedJobsSummary: [],
graphqlResourceEtag: 'graphqlResourceEtag',
pipelineIid: '123',
pipelineProjectPath: 'pipelineProjectPath',
diff --git a/spec/frontend/pipelines/pipeline_triggerer_spec.js b/spec/frontend/pipelines/pipeline_triggerer_spec.js
index 58bfb68e85c..856c0484075 100644
--- a/spec/frontend/pipelines/pipeline_triggerer_spec.js
+++ b/spec/frontend/pipelines/pipeline_triggerer_spec.js
@@ -22,15 +22,11 @@ describe('Pipelines Triggerer', () => {
...props,
},
directives: {
- GlTooltip: createMockDirective(),
+ GlTooltip: createMockDirective('gl-tooltip'),
},
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
const findAvatarLink = () => wrapper.findComponent(GlAvatarLink);
const findAvatar = () => wrapper.findComponent(GlAvatar);
const findTriggerer = () => wrapper.findByText('API');
diff --git a/spec/frontend/pipelines/pipeline_url_spec.js b/spec/frontend/pipelines/pipeline_url_spec.js
index c62898f0c83..f00ee4a6367 100644
--- a/spec/frontend/pipelines/pipeline_url_spec.js
+++ b/spec/frontend/pipelines/pipeline_url_spec.js
@@ -35,10 +35,6 @@ describe('Pipeline Url Component', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render pipeline url table cell', () => {
createComponent();
diff --git a/spec/frontend/pipelines/pipelines_actions_spec.js b/spec/frontend/pipelines/pipelines_actions_spec.js
deleted file mode 100644
index e034d52a33c..00000000000
--- a/spec/frontend/pipelines/pipelines_actions_spec.js
+++ /dev/null
@@ -1,171 +0,0 @@
-import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import MockAdapter from 'axios-mock-adapter';
-import { nextTick } from 'vue';
-import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
-import waitForPromises from 'helpers/wait_for_promises';
-import { TEST_HOST } from 'spec/test_constants';
-import { createAlert } from '~/flash';
-import axios from '~/lib/utils/axios_utils';
-import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
-import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
-import PipelinesManualActions from '~/pipelines/components/pipelines_list/pipelines_manual_actions.vue';
-import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
-import { TRACKING_CATEGORIES } from '~/pipelines/constants';
-
-jest.mock('~/flash');
-jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal');
-
-describe('Pipelines Actions dropdown', () => {
- let wrapper;
- let mock;
-
- const createComponent = (props, mountFn = shallowMount) => {
- wrapper = mountFn(PipelinesManualActions, {
- propsData: {
- ...props,
- },
- });
- };
-
- const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
- const findAllCountdowns = () => wrapper.findAllComponents(GlCountdown);
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
-
- mock.restore();
- confirmAction.mockReset();
- });
-
- describe('manual actions', () => {
- const mockActions = [
- {
- name: 'stop_review',
- path: `${TEST_HOST}/root/review-app/builds/1893/play`,
- },
- {
- name: 'foo',
- path: `${TEST_HOST}/disabled/pipeline/action`,
- playable: false,
- },
- ];
-
- beforeEach(() => {
- createComponent({ actions: mockActions });
- });
-
- it('renders a dropdown with the provided actions', () => {
- expect(findAllDropdownItems()).toHaveLength(mockActions.length);
- });
-
- it("renders a disabled action when it's not playable", () => {
- expect(findAllDropdownItems().at(1).attributes('disabled')).toBe('true');
- });
-
- describe('on click', () => {
- it('makes a request and toggles the loading state', async () => {
- mock.onPost(mockActions.path).reply(HTTP_STATUS_OK);
-
- findAllDropdownItems().at(0).vm.$emit('click');
-
- await nextTick();
- expect(findDropdown().props('loading')).toBe(true);
-
- await waitForPromises();
- expect(findDropdown().props('loading')).toBe(false);
- });
-
- it('makes a failed request and toggles the loading state', async () => {
- mock.onPost(mockActions.path).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
-
- findAllDropdownItems().at(0).vm.$emit('click');
-
- await nextTick();
- expect(findDropdown().props('loading')).toBe(true);
-
- await waitForPromises();
- expect(findDropdown().props('loading')).toBe(false);
- expect(createAlert).toHaveBeenCalledTimes(1);
- });
- });
-
- describe('tracking', () => {
- afterEach(() => {
- unmockTracking();
- });
-
- it('tracks manual actions click', () => {
- const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
-
- findDropdown().vm.$emit('shown');
-
- expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_manual_actions', {
- label: TRACKING_CATEGORIES.table,
- });
- });
- });
- });
-
- describe('scheduled jobs', () => {
- const scheduledJobAction = {
- name: 'scheduled action',
- path: `${TEST_HOST}/scheduled/job/action`,
- playable: true,
- scheduled_at: '2063-04-05T00:42:00Z',
- };
- const expiredJobAction = {
- name: 'expired action',
- path: `${TEST_HOST}/expired/job/action`,
- playable: true,
- scheduled_at: '2018-10-05T08:23:00Z',
- };
-
- beforeEach(() => {
- jest.spyOn(Date, 'now').mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime());
- createComponent({ actions: [scheduledJobAction, expiredJobAction] });
- });
-
- it('makes post request after confirming', async () => {
- mock.onPost(scheduledJobAction.path).reply(HTTP_STATUS_OK);
- confirmAction.mockResolvedValueOnce(true);
-
- findAllDropdownItems().at(0).vm.$emit('click');
-
- expect(confirmAction).toHaveBeenCalled();
-
- await waitForPromises();
-
- expect(mock.history.post).toHaveLength(1);
- });
-
- it('does not make post request if confirmation is cancelled', async () => {
- mock.onPost(scheduledJobAction.path).reply(HTTP_STATUS_OK);
- confirmAction.mockResolvedValueOnce(false);
-
- findAllDropdownItems().at(0).vm.$emit('click');
-
- expect(confirmAction).toHaveBeenCalled();
-
- await waitForPromises();
-
- expect(mock.history.post).toHaveLength(0);
- });
-
- it('displays the remaining time in the dropdown', () => {
- expect(findAllCountdowns().at(0).props('endDateString')).toBe(
- scheduledJobAction.scheduled_at,
- );
- });
-
- it('displays 00:00:00 for expired jobs in the dropdown', () => {
- expect(findAllCountdowns().at(1).props('endDateString')).toBe(expiredJobAction.scheduled_at);
- });
- });
-});
diff --git a/spec/frontend/pipelines/pipelines_artifacts_spec.js b/spec/frontend/pipelines/pipelines_artifacts_spec.js
index e3e54716a7b..9fedbaf9b56 100644
--- a/spec/frontend/pipelines/pipelines_artifacts_spec.js
+++ b/spec/frontend/pipelines/pipelines_artifacts_spec.js
@@ -34,11 +34,6 @@ describe('Pipelines Artifacts dropdown', () => {
const findAllGlDropdownItems = () =>
wrapper.findComponent(GlDropdown).findAllComponents(GlDropdownItem);
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('should render a dropdown with all the provided artifacts', () => {
createComponent();
diff --git a/spec/frontend/pipelines/pipelines_manual_actions_spec.js b/spec/frontend/pipelines/pipelines_manual_actions_spec.js
new file mode 100644
index 00000000000..82cab88c9eb
--- /dev/null
+++ b/spec/frontend/pipelines/pipelines_manual_actions_spec.js
@@ -0,0 +1,216 @@
+import { GlDropdown, GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
+import MockAdapter from 'axios-mock-adapter';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import mockPipelineActionsQueryResponse from 'test_fixtures/graphql/pipelines/get_pipeline_actions.query.graphql.json';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { createAlert } from '~/alert';
+import axios from '~/lib/utils/axios_utils';
+import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
+import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
+import PipelinesManualActions from '~/pipelines/components/pipelines_list/pipelines_manual_actions.vue';
+import getPipelineActionsQuery from '~/pipelines/graphql/queries/get_pipeline_actions.query.graphql';
+import { TRACKING_CATEGORIES } from '~/pipelines/constants';
+import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
+
+Vue.use(VueApollo);
+
+jest.mock('~/alert');
+jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal');
+
+describe('Pipeline manual actions', () => {
+ let wrapper;
+ let mock;
+
+ const queryHandler = jest.fn().mockResolvedValue(mockPipelineActionsQueryResponse);
+ const {
+ data: {
+ project: {
+ pipeline: {
+ jobs: { nodes },
+ },
+ },
+ },
+ } = mockPipelineActionsQueryResponse;
+
+ const mockPath = nodes[2].playPath;
+
+ const createComponent = (limit = 50) => {
+ wrapper = shallowMountExtended(PipelinesManualActions, {
+ provide: {
+ fullPath: 'root/ci-project',
+ manualActionsLimit: limit,
+ },
+ propsData: {
+ iid: 100,
+ },
+ stubs: {
+ GlDropdown,
+ },
+ apolloProvider: createMockApollo([[getPipelineActionsQuery, queryHandler]]),
+ });
+ };
+
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findAllCountdowns = () => wrapper.findAllComponents(GlCountdown);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+ const findLimitMessage = () => wrapper.findByTestId('limit-reached-msg');
+
+ it('skips calling query on mount', () => {
+ createComponent();
+
+ expect(queryHandler).not.toHaveBeenCalled();
+ });
+
+ describe('loading', () => {
+ beforeEach(() => {
+ createComponent();
+
+ findDropdown().vm.$emit('shown');
+ });
+
+ it('display loading state while actions are being fetched', () => {
+ expect(findAllDropdownItems().at(0).text()).toBe('Loading...');
+ expect(findLoadingIcon().exists()).toBe(true);
+ expect(findAllDropdownItems()).toHaveLength(1);
+ });
+ });
+
+ describe('loaded', () => {
+ beforeEach(async () => {
+ mock = new MockAdapter(axios);
+
+ createComponent();
+
+ findDropdown().vm.$emit('shown');
+
+ await waitForPromises();
+ });
+
+ afterEach(() => {
+ mock.restore();
+ confirmAction.mockReset();
+ });
+
+ it('displays dropdown with the provided actions', () => {
+ expect(findAllDropdownItems()).toHaveLength(3);
+ });
+
+ it("displays a disabled action when it's not playable", () => {
+ expect(findAllDropdownItems().at(0).attributes('disabled')).toBeDefined();
+ });
+
+ describe('on action click', () => {
+ it('makes a request and toggles the loading state', async () => {
+ mock.onPost(mockPath).reply(HTTP_STATUS_OK);
+
+ findAllDropdownItems().at(1).vm.$emit('click');
+
+ await nextTick();
+
+ expect(findDropdown().props('loading')).toBe(true);
+
+ await waitForPromises();
+
+ expect(findDropdown().props('loading')).toBe(false);
+ });
+
+ it('makes a failed request and toggles the loading state', async () => {
+ mock.onPost(mockPath).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+
+ findAllDropdownItems().at(1).vm.$emit('click');
+
+ await nextTick();
+
+ expect(findDropdown().props('loading')).toBe(true);
+
+ await waitForPromises();
+
+ expect(findDropdown().props('loading')).toBe(false);
+ expect(createAlert).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('tracking', () => {
+ afterEach(() => {
+ unmockTracking();
+ });
+
+ it('tracks manual actions click', () => {
+ const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+
+ findDropdown().vm.$emit('shown');
+
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_manual_actions', {
+ label: TRACKING_CATEGORIES.table,
+ });
+ });
+ });
+
+ describe('scheduled jobs', () => {
+ beforeEach(() => {
+ jest
+ .spyOn(Date, 'now')
+ .mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime());
+ });
+
+ it('makes post request after confirming', async () => {
+ mock.onPost(mockPath).reply(HTTP_STATUS_OK);
+
+ confirmAction.mockResolvedValueOnce(true);
+
+ findAllDropdownItems().at(2).vm.$emit('click');
+
+ expect(confirmAction).toHaveBeenCalled();
+
+ await waitForPromises();
+
+ expect(mock.history.post).toHaveLength(1);
+ });
+
+ it('does not make post request if confirmation is cancelled', async () => {
+ mock.onPost(mockPath).reply(HTTP_STATUS_OK);
+
+ confirmAction.mockResolvedValueOnce(false);
+
+ findAllDropdownItems().at(2).vm.$emit('click');
+
+ expect(confirmAction).toHaveBeenCalled();
+
+ await waitForPromises();
+
+ expect(mock.history.post).toHaveLength(0);
+ });
+
+ it('displays the remaining time in the dropdown', () => {
+ expect(findAllCountdowns().at(0).props('endDateString')).toBe(nodes[2].scheduledAt);
+ });
+ });
+ });
+
+ describe('limit message', () => {
+ it('limit message does not show', async () => {
+ createComponent();
+
+ findDropdown().vm.$emit('shown');
+
+ await waitForPromises();
+
+ expect(findLimitMessage().exists()).toBe(false);
+ });
+
+ it('limit message does show', async () => {
+ createComponent(3);
+
+ findDropdown().vm.$emit('shown');
+
+ await waitForPromises();
+
+ expect(findLimitMessage().exists()).toBe(true);
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js
index 2523b901506..f0772bce167 100644
--- a/spec/frontend/pipelines/pipelines_spec.js
+++ b/spec/frontend/pipelines/pipelines_spec.js
@@ -11,7 +11,7 @@ import { mockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
-import { createAlert, VARIANT_WARNING } from '~/flash';
+import { createAlert, VARIANT_WARNING } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue';
@@ -25,7 +25,7 @@ import TablePagination from '~/vue_shared/components/pagination/table_pagination
import { stageReply, users, mockSearch, branches } from './mock_data';
-jest.mock('~/flash');
+jest.mock('~/alert');
const mockProjectPath = 'twitter/flight';
const mockProjectId = '21';
@@ -42,7 +42,7 @@ describe('Pipelines', () => {
let trackingSpy;
const paths = {
- emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
+ emptyStateSvgPath: '/assets/illustrations/empty-state/empty-pipeline-md.svg',
errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg',
noPipelinesSvgPath: '/assets/illustrations/pipelines_pending.svg',
ciLintPath: '/ci/lint',
@@ -53,7 +53,7 @@ describe('Pipelines', () => {
};
const noPermissions = {
- emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
+ emptyStateSvgPath: '/assets/illustrations/empty-state/empty-pipeline-md.svg',
errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg',
noPipelinesSvgPath: '/assets/illustrations/pipelines_pending.svg',
};
@@ -114,7 +114,6 @@ describe('Pipelines', () => {
});
afterEach(() => {
- wrapper.destroy();
mock.reset();
window.history.pushState.mockReset();
});
@@ -246,7 +245,7 @@ describe('Pipelines', () => {
await waitForPromises();
});
- it('should filter pipelines', async () => {
+ it('should filter pipelines', () => {
expect(findPipelinesTable().exists()).toBe(true);
expect(findPipelineUrlLinks()).toHaveLength(1);
@@ -288,7 +287,7 @@ describe('Pipelines', () => {
await waitForPromises();
});
- it('should filter pipelines', async () => {
+ it('should filter pipelines', () => {
expect(findEmptyState().text()).toBe('There are currently no pipelines.');
});
@@ -331,11 +330,11 @@ describe('Pipelines', () => {
await waitForPromises();
});
- it('requests data with query params on filter submit', async () => {
+ it('requests data with query params on filter submit', () => {
expect(mock.history.get[1].params).toEqual(expectedParams);
});
- it('renders filtered pipelines', async () => {
+ it('renders filtered pipelines', () => {
expect(findPipelineUrlLinks()).toHaveLength(1);
expect(findPipelineUrlLinks().at(0).text()).toBe(`#${mockFilteredPipeline.id}`);
});
@@ -357,7 +356,7 @@ describe('Pipelines', () => {
await waitForPromises();
});
- it('requests data with query params on filter submit', async () => {
+ it('requests data with query params on filter submit', () => {
expect(mock.history.get[1].params).toEqual({ page: '1', scope: 'all' });
});
@@ -517,7 +516,7 @@ describe('Pipelines', () => {
expect(findNavigationTabs().exists()).toBe(true);
});
- it('is loading after a time', async () => {
+ it('is loading after a time', () => {
expect(findPipelineUrlLinks()).toHaveLength(mockPipelinesIds.length);
expect(findPipelineUrlLinks().at(0).text()).toBe(`#${mockPipelinesIds[0]}`);
expect(findPipelineUrlLinks().at(1).text()).toBe(`#${mockPipelinesIds[1]}`);
@@ -728,7 +727,7 @@ describe('Pipelines', () => {
});
describe('when pipelines cannot be loaded', () => {
- beforeEach(async () => {
+ beforeEach(() => {
mock.onGet(mockPipelinesEndpoint).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR, {});
});
@@ -751,8 +750,9 @@ describe('Pipelines', () => {
});
it('shows error state', () => {
- expect(findEmptyState().text()).toBe(
- 'There was an error fetching the pipelines. Try again in a few moments or contact your support team.',
+ expect(findEmptyState().props('title')).toBe('There was an error fetching the pipelines.');
+ expect(findEmptyState().props('description')).toBe(
+ 'Try again in a few moments or contact your support team.',
);
});
});
@@ -776,8 +776,9 @@ describe('Pipelines', () => {
});
it('shows error state', () => {
- expect(findEmptyState().text()).toBe(
- 'There was an error fetching the pipelines. Try again in a few moments or contact your support team.',
+ expect(findEmptyState().props('title')).toBe('There was an error fetching the pipelines.');
+ expect(findEmptyState().props('description')).toBe(
+ 'Try again in a few moments or contact your support team.',
);
});
});
diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js
index 6ec8901038b..8d2a52eb6d0 100644
--- a/spec/frontend/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_spec.js
@@ -69,12 +69,6 @@ describe('Pipelines Table', () => {
pipeline = createMockPipeline();
});
- afterEach(() => {
- wrapper.destroy();
-
- wrapper = null;
- });
-
describe('Pipelines Table', () => {
beforeEach(() => {
createComponent({ pipelines: [pipeline], viewType: 'root' });
diff --git a/spec/frontend/pipelines/test_reports/stores/actions_spec.js b/spec/frontend/pipelines/test_reports/stores/actions_spec.js
index f6287107ed0..e05d2151f0a 100644
--- a/spec/frontend/pipelines/test_reports/stores/actions_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/actions_spec.js
@@ -2,13 +2,13 @@ import MockAdapter from 'axios-mock-adapter';
import testReports from 'test_fixtures/pipelines/test_report.json';
import { TEST_HOST } from 'helpers/test_constants';
import testAction from 'helpers/vuex_action_helper';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import * as actions from '~/pipelines/stores/test_reports/actions';
import * as types from '~/pipelines/stores/test_reports/mutation_types';
-jest.mock('~/flash.js');
+jest.mock('~/alert');
describe('Actions TestReports Store', () => {
let mock;
@@ -49,7 +49,7 @@ describe('Actions TestReports Store', () => {
);
});
- it('should create flash on API error', async () => {
+ it('should create alert on API error', async () => {
await testAction(
actions.fetchSummary,
null,
diff --git a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
index ed0cc71eb97..685ac6ea3e5 100644
--- a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
@@ -1,9 +1,9 @@
import testReports from 'test_fixtures/pipelines/test_report.json';
import * as types from '~/pipelines/stores/test_reports/mutation_types';
import mutations from '~/pipelines/stores/test_reports/mutations';
-import { createAlert } from '~/flash';
+import { createAlert } from '~/alert';
-jest.mock('~/flash.js');
+jest.mock('~/alert');
describe('Mutations TestReports Store', () => {
let mockState;
@@ -58,7 +58,7 @@ describe('Mutations TestReports Store', () => {
expect(mockState.errorMessage).toBe(message);
});
- it('should show a flash message otherwise', () => {
+ it('should show an alert otherwise', () => {
mutations[types.SET_SUITE_ERROR](mockState, {});
expect(createAlert).toHaveBeenCalled();
diff --git a/spec/frontend/pipelines/test_reports/test_case_details_spec.js b/spec/frontend/pipelines/test_reports/test_case_details_spec.js
index f194864447c..f8663408817 100644
--- a/spec/frontend/pipelines/test_reports/test_case_details_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_case_details_spec.js
@@ -45,11 +45,6 @@ describe('Test case details', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
describe('required details', () => {
beforeEach(() => {
createComponent();
diff --git a/spec/frontend/pipelines/test_reports/test_reports_spec.js b/spec/frontend/pipelines/test_reports/test_reports_spec.js
index 9b9ee4172f9..c8c917a1b9e 100644
--- a/spec/frontend/pipelines/test_reports/test_reports_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_reports_spec.js
@@ -60,10 +60,6 @@ describe('Test reports app', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- });
-
describe('when component is created', () => {
it('should call fetchSummary when pipeline has test report', () => {
createComponent();
diff --git a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
index da13df833e7..8eb83f17f4d 100644
--- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
@@ -65,10 +65,6 @@ describe('Test reports suite table', () => {
});
};
- afterEach(() => {
- wrapper.destroy();
- });
-
it('should render a message when there are no test cases', () => {
createComponent({ suite: [] });
diff --git a/spec/frontend/pipelines/time_ago_spec.js b/spec/frontend/pipelines/time_ago_spec.js
index f0da0df2ba6..efb1bf09d20 100644
--- a/spec/frontend/pipelines/time_ago_spec.js
+++ b/spec/frontend/pipelines/time_ago_spec.js
@@ -30,11 +30,6 @@ describe('Timeago component', () => {
);
};
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
const duration = () => wrapper.find('.duration');
const finishedAt = () => wrapper.find('.finished-at');
const findInProgress = () => wrapper.findByTestId('pipeline-in-progress');
diff --git a/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
index caa66502e11..d518519a424 100644
--- a/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_branch_name_token_spec.js
@@ -71,11 +71,6 @@ describe('Pipeline Branch Name Token', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('passes config correctly', () => {
expect(findFilteredSearchToken().props('config')).toEqual(defaultProps.config);
});
diff --git a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
index c090fd353f7..cf4ccb5ce43 100644
--- a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js
@@ -45,11 +45,6 @@ describe('Pipeline Status Token', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('passes config correctly', () => {
expect(findFilteredSearchToken().props('config')).toEqual(defaultProps.config);
});
diff --git a/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
index 7311a5d2f5a..88c88d8f16f 100644
--- a/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_tag_name_token_spec.js
@@ -53,11 +53,6 @@ describe('Pipeline Branch Name Token', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('passes config correctly', () => {
expect(findFilteredSearchToken().props('config')).toEqual(defaultProps.config);
});
diff --git a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
index c763bfe1b27..e9ec684a350 100644
--- a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
+++ b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js
@@ -52,11 +52,6 @@ describe('Pipeline Trigger Author Token', () => {
createComponent();
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
- });
-
it('passes config correctly', () => {
expect(findFilteredSearchToken().props('config')).toEqual(defaultProps.config);
});