Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/pipelines')
-rw-r--r--spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js12
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/graphql_pipeline_mini_graph_spec.js123
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/job_item_spec.js29
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/legacy_pipeline_mini_graph_spec.js122
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/legacy_pipeline_stage_spec.js247
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_mini_graph_spec.js179
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stage_spec.js255
-rw-r--r--spec/frontend/pipelines/components/pipeline_mini_graph/pipeline_stages_spec.js10
-rw-r--r--spec/frontend/pipelines/components/pipeline_tabs_spec.js63
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/failure_widget/failed_job_details_spec.js40
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/failure_widget/failed_jobs_list_spec.js49
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/failure_widget/mock.js5
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget_spec.js29
-rw-r--r--spec/frontend/pipelines/pipeline_graph/utils_spec.js6
-rw-r--r--spec/frontend/pipelines/pipeline_multi_actions_spec.js95
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js15
-rw-r--r--spec/frontend/pipelines/pipelines_table_spec.js31
-rw-r--r--spec/frontend/pipelines/test_reports/test_reports_spec.js1
-rw-r--r--spec/frontend/pipelines/test_reports/test_suite_table_spec.js1
-rw-r--r--spec/frontend/pipelines/test_reports/test_summary_table_spec.js1
20 files changed, 808 insertions, 505 deletions
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 d5307b87a11..99a178120cc 100644
--- a/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js
+++ b/spec/frontend/pipelines/components/jobs/failed_jobs_table_spec.js
@@ -2,12 +2,14 @@ import { GlButton, GlLink, GlTableLite } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { mountExtended } from 'helpers/vue_test_utils_helper';
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 { TRACKING_CATEGORIES } from '~/pipelines/constants';
import {
successRetryMutationResponse,
failedRetryMutationResponse,
@@ -71,7 +73,9 @@ describe('Failed Jobs Table', () => {
expect(findFirstFailureMessage().text()).toBe('Job failed');
});
- it('calls the retry failed job mutation correctly', () => {
+ it('calls the retry failed job mutation and tracks the click', () => {
+ const trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+
createComponent(successRetryMutationHandler);
findRetryButton().trigger('click');
@@ -79,6 +83,12 @@ describe('Failed Jobs Table', () => {
expect(successRetryMutationHandler).toHaveBeenCalledWith({
id: mockFailedJobsData[0].id,
});
+ expect(trackingSpy).toHaveBeenCalledTimes(1);
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_retry', {
+ label: TRACKING_CATEGORIES.failed,
+ });
+
+ unmockTracking();
});
it('redirects to the new job after the mutation', async () => {
diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/graphql_pipeline_mini_graph_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/graphql_pipeline_mini_graph_spec.js
deleted file mode 100644
index 69b223461bd..00000000000
--- a/spec/frontend/pipelines/components/pipeline_mini_graph/graphql_pipeline_mini_graph_spec.js
+++ /dev/null
@@ -1,123 +0,0 @@
-import Vue from 'vue';
-import VueApollo from 'vue-apollo';
-import { GlLoadingIcon } from '@gitlab/ui';
-
-import { createAlert } from '~/alert';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import waitForPromises from 'helpers/wait_for_promises';
-import createMockApollo from 'helpers/mock_apollo_helper';
-
-import getLinkedPipelinesQuery from '~/pipelines/graphql/queries/get_linked_pipelines.query.graphql';
-import getPipelineStagesQuery from '~/pipelines/graphql/queries/get_pipeline_stages.query.graphql';
-import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
-import GraphqlPipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/graphql_pipeline_mini_graph.vue';
-import * as sharedGraphQlUtils from '~/graphql_shared/utils';
-
-import {
- linkedPipelinesFetchError,
- stagesFetchError,
- mockPipelineStagesQueryResponse,
- mockUpstreamDownstreamQueryResponse,
-} from './mock_data';
-
-Vue.use(VueApollo);
-jest.mock('~/alert');
-
-describe('GraphqlPipelineMiniGraph', () => {
- let wrapper;
- let linkedPipelinesResponse;
- let pipelineStagesResponse;
-
- const fullPath = 'gitlab-org/gitlab';
- const iid = '315';
- const pipelineEtag = '/api/graphql:pipelines/id/315';
-
- const createComponent = ({
- pipelineStagesHandler = pipelineStagesResponse,
- linkedPipelinesHandler = linkedPipelinesResponse,
- } = {}) => {
- const handlers = [
- [getLinkedPipelinesQuery, linkedPipelinesHandler],
- [getPipelineStagesQuery, pipelineStagesHandler],
- ];
- const mockApollo = createMockApollo(handlers);
-
- wrapper = shallowMountExtended(GraphqlPipelineMiniGraph, {
- propsData: {
- fullPath,
- iid,
- pipelineEtag,
- },
- apolloProvider: mockApollo,
- });
-
- return waitForPromises();
- };
-
- const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
- const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
-
- beforeEach(() => {
- linkedPipelinesResponse = jest.fn().mockResolvedValue(mockUpstreamDownstreamQueryResponse);
- pipelineStagesResponse = jest.fn().mockResolvedValue(mockPipelineStagesQueryResponse);
- });
-
- describe('when initial queries are loading', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('shows a loading icon and no mini graph', () => {
- expect(findLoadingIcon().exists()).toBe(true);
- expect(findPipelineMiniGraph().exists()).toBe(false);
- });
- });
-
- describe('when queries have loaded', () => {
- it('does not show a loading icon', async () => {
- await createComponent();
-
- expect(findLoadingIcon().exists()).toBe(false);
- });
-
- it('renders the Pipeline Mini Graph', async () => {
- await createComponent();
-
- expect(findPipelineMiniGraph().exists()).toBe(true);
- });
-
- it('fires the queries', async () => {
- await createComponent();
-
- expect(linkedPipelinesResponse).toHaveBeenCalledWith({ iid, fullPath });
- expect(pipelineStagesResponse).toHaveBeenCalledWith({ iid, fullPath });
- });
- });
-
- describe('polling', () => {
- it('toggles query polling with visibility check', async () => {
- jest.spyOn(sharedGraphQlUtils, 'toggleQueryPollingByVisibility');
-
- createComponent();
-
- await waitForPromises();
-
- expect(sharedGraphQlUtils.toggleQueryPollingByVisibility).toHaveBeenCalledTimes(2);
- });
- });
-
- describe('when pipeline queries are unsuccessful', () => {
- const failedHandler = jest.fn().mockRejectedValue(new Error('GraphQL error'));
- it.each`
- query | handlerName | errorMessage
- ${'pipeline stages'} | ${'pipelineStagesHandler'} | ${stagesFetchError}
- ${'linked pipelines'} | ${'linkedPipelinesHandler'} | ${linkedPipelinesFetchError}
- `('throws an error for the $query query', async ({ errorMessage, handlerName }) => {
- await createComponent({ [handlerName]: failedHandler });
-
- await waitForPromises();
-
- expect(createAlert).toHaveBeenCalledWith({ message: errorMessage });
- });
- });
-});
diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/job_item_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/job_item_spec.js
new file mode 100644
index 00000000000..b89f27e5c05
--- /dev/null
+++ b/spec/frontend/pipelines/components/pipeline_mini_graph/job_item_spec.js
@@ -0,0 +1,29 @@
+import { shallowMount } from '@vue/test-utils';
+import JobItem from '~/pipelines/components/pipeline_mini_graph/job_item.vue';
+
+describe('JobItem', () => {
+ let wrapper;
+
+ const defaultProps = {
+ job: { id: '3' },
+ };
+
+ const createComponent = ({ props = {} } = {}) => {
+ wrapper = shallowMount(JobItem, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ });
+ };
+
+ describe('when mounted', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders the received HTML', () => {
+ expect(wrapper.html()).toContain(defaultProps.job.id);
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/legacy_pipeline_mini_graph_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/legacy_pipeline_mini_graph_spec.js
new file mode 100644
index 00000000000..6661bb079d2
--- /dev/null
+++ b/spec/frontend/pipelines/components/pipeline_mini_graph/legacy_pipeline_mini_graph_spec.js
@@ -0,0 +1,122 @@
+import { mount } from '@vue/test-utils';
+import { pipelines } from 'test_fixtures/pipelines/pipelines.json';
+import LegacyPipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/legacy_pipeline_mini_graph.vue';
+import PipelineStages from '~/pipelines/components/pipeline_mini_graph/pipeline_stages.vue';
+import mockLinkedPipelines from './linked_pipelines_mock_data';
+
+const mockStages = pipelines[0].details.stages;
+
+describe('Legacy Pipeline Mini Graph', () => {
+ let wrapper;
+
+ const findLegacyPipelineMiniGraph = () => wrapper.findComponent(LegacyPipelineMiniGraph);
+ const findPipelineStages = () => wrapper.findComponent(PipelineStages);
+
+ const findLinkedPipelineUpstream = () =>
+ wrapper.findComponent('[data-testid="pipeline-mini-graph-upstream"]');
+ const findLinkedPipelineDownstream = () =>
+ wrapper.findComponent('[data-testid="pipeline-mini-graph-downstream"]');
+ const findDownstreamArrowIcon = () => wrapper.find('[data-testid="downstream-arrow-icon"]');
+ const findUpstreamArrowIcon = () => wrapper.find('[data-testid="upstream-arrow-icon"]');
+
+ const createComponent = (props = {}) => {
+ wrapper = mount(LegacyPipelineMiniGraph, {
+ propsData: {
+ stages: mockStages,
+ ...props,
+ },
+ });
+ };
+
+ describe('rendered state without upstream or downstream pipelines', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should render the pipeline stages', () => {
+ expect(findPipelineStages().exists()).toBe(true);
+ });
+
+ it('should have the correct props', () => {
+ expect(findLegacyPipelineMiniGraph().props()).toMatchObject({
+ downstreamPipelines: [],
+ isMergeTrain: false,
+ pipelinePath: '',
+ stages: expect.any(Array),
+ updateDropdown: false,
+ upstreamPipeline: undefined,
+ });
+ });
+
+ it('should have no linked pipelines', () => {
+ expect(findLinkedPipelineDownstream().exists()).toBe(false);
+ expect(findLinkedPipelineUpstream().exists()).toBe(false);
+ });
+
+ it('should not render arrow icons', () => {
+ expect(findUpstreamArrowIcon().exists()).toBe(false);
+ expect(findDownstreamArrowIcon().exists()).toBe(false);
+ });
+ });
+
+ describe('rendered state with upstream pipeline', () => {
+ beforeEach(() => {
+ createComponent({
+ upstreamPipeline: mockLinkedPipelines.triggered_by,
+ });
+ });
+
+ it('should have the correct props', () => {
+ expect(findLegacyPipelineMiniGraph().props()).toMatchObject({
+ downstreamPipelines: [],
+ isMergeTrain: false,
+ pipelinePath: '',
+ stages: expect.any(Array),
+ updateDropdown: false,
+ upstreamPipeline: expect.any(Object),
+ });
+ });
+
+ it('should render the upstream linked pipelines mini list only', () => {
+ expect(findLinkedPipelineUpstream().exists()).toBe(true);
+ expect(findLinkedPipelineDownstream().exists()).toBe(false);
+ });
+
+ it('should render an upstream arrow icon only', () => {
+ expect(findDownstreamArrowIcon().exists()).toBe(false);
+ expect(findUpstreamArrowIcon().exists()).toBe(true);
+ expect(findUpstreamArrowIcon().props('name')).toBe('long-arrow');
+ });
+ });
+
+ describe('rendered state with downstream pipelines', () => {
+ beforeEach(() => {
+ createComponent({
+ downstreamPipelines: mockLinkedPipelines.triggered,
+ pipelinePath: 'my/pipeline/path',
+ });
+ });
+
+ it('should have the correct props', () => {
+ expect(findLegacyPipelineMiniGraph().props()).toMatchObject({
+ downstreamPipelines: expect.any(Array),
+ isMergeTrain: false,
+ pipelinePath: 'my/pipeline/path',
+ stages: expect.any(Array),
+ updateDropdown: false,
+ upstreamPipeline: undefined,
+ });
+ });
+
+ it('should render the downstream linked pipelines mini list only', () => {
+ expect(findLinkedPipelineDownstream().exists()).toBe(true);
+ expect(findLinkedPipelineUpstream().exists()).toBe(false);
+ });
+
+ it('should render a downstream arrow icon only', () => {
+ expect(findUpstreamArrowIcon().exists()).toBe(false);
+ expect(findDownstreamArrowIcon().exists()).toBe(true);
+ expect(findDownstreamArrowIcon().props('name')).toBe('long-arrow');
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/components/pipeline_mini_graph/legacy_pipeline_stage_spec.js b/spec/frontend/pipelines/components/pipeline_mini_graph/legacy_pipeline_stage_spec.js
new file mode 100644
index 00000000000..3697eaeea1a
--- /dev/null
+++ b/spec/frontend/pipelines/components/pipeline_mini_graph/legacy_pipeline_stage_spec.js
@@ -0,0 +1,247 @@
+import { GlDropdown } from '@gitlab/ui';
+import { nextTick } from 'vue';
+import { mount } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import axios from '~/lib/utils/axios_utils';
+import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
+import LegacyPipelineStage from '~/pipelines/components/pipeline_mini_graph/legacy_pipeline_stage.vue';
+import eventHub from '~/pipelines/event_hub';
+import waitForPromises from 'helpers/wait_for_promises';
+import { stageReply } from '../../mock_data';
+
+const dropdownPath = 'path.json';
+
+describe('Pipelines stage component', () => {
+ let wrapper;
+ let mock;
+ let glTooltipDirectiveMock;
+
+ const createComponent = (props = {}) => {
+ glTooltipDirectiveMock = jest.fn();
+ wrapper = mount(LegacyPipelineStage, {
+ attachTo: document.body,
+ directives: {
+ GlTooltip: glTooltipDirectiveMock,
+ },
+ propsData: {
+ stage: {
+ status: {
+ group: 'success',
+ icon: 'status_success',
+ title: 'success',
+ },
+ dropdown_path: dropdownPath,
+ },
+ updateDropdown: false,
+ ...props,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ jest.spyOn(eventHub, '$emit');
+ });
+
+ afterEach(() => {
+ eventHub.$emit.mockRestore();
+ mock.restore();
+ // eslint-disable-next-line @gitlab/vtu-no-explicit-wrapper-destroy
+ wrapper.destroy();
+ });
+
+ const findCiActionBtn = () => wrapper.find('.js-ci-action');
+ const findCiIcon = () => wrapper.findComponent(CiIcon);
+ const findDropdown = () => wrapper.findComponent(GlDropdown);
+ const findDropdownToggle = () => wrapper.find('button.dropdown-toggle');
+ const findDropdownMenu = () =>
+ wrapper.find('[data-testid="mini-pipeline-graph-dropdown-menu-list"]');
+ const findDropdownMenuTitle = () =>
+ wrapper.find('[data-testid="pipeline-stage-dropdown-menu-title"]');
+ const findMergeTrainWarning = () => wrapper.find('[data-testid="warning-message-merge-trains"]');
+ const findLoadingState = () => wrapper.find('[data-testid="pipeline-stage-loading-state"]');
+
+ const openStageDropdown = async () => {
+ await findDropdownToggle().trigger('click');
+ await waitForPromises();
+ await nextTick();
+ };
+
+ describe('loading state', () => {
+ beforeEach(async () => {
+ createComponent({ updateDropdown: true });
+
+ mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply);
+
+ await openStageDropdown();
+ });
+
+ it('displays loading state while jobs are being fetched', async () => {
+ jest.runOnlyPendingTimers();
+ await nextTick();
+
+ expect(findLoadingState().exists()).toBe(true);
+ expect(findLoadingState().text()).toBe(LegacyPipelineStage.i18n.loadingText);
+ });
+
+ it('does not display loading state after jobs have been fetched', async () => {
+ await waitForPromises();
+
+ expect(findLoadingState().exists()).toBe(false);
+ });
+ });
+
+ describe('default appearance', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('sets up the tooltip to not have a show delay animation', () => {
+ expect(glTooltipDirectiveMock.mock.calls[0][1].modifiers.ds0).toBe(true);
+ });
+
+ it('renders a dropdown with the status icon', () => {
+ expect(findDropdown().exists()).toBe(true);
+ expect(findDropdownToggle().exists()).toBe(true);
+ expect(findCiIcon().exists()).toBe(true);
+ });
+
+ it('renders a borderless ci-icon', () => {
+ expect(findCiIcon().exists()).toBe(true);
+ expect(findCiIcon().props('isBorderless')).toBe(true);
+ expect(findCiIcon().classes('borderless')).toBe(true);
+ });
+
+ it('renders a ci-icon with a custom border class', () => {
+ expect(findCiIcon().exists()).toBe(true);
+ expect(findCiIcon().classes('gl-border')).toBe(true);
+ });
+ });
+
+ describe('when user opens dropdown and stage request is successful', () => {
+ beforeEach(async () => {
+ mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply);
+ createComponent();
+
+ await openStageDropdown();
+ await jest.runAllTimers();
+ await axios.waitForAll();
+ });
+
+ 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');
+ expect(wrapper.emitted('miniGraphStageClick')).toEqual([[]]);
+ });
+
+ it('refreshes when updateDropdown is set to true', async () => {
+ expect(mock.history.get).toHaveLength(1);
+
+ wrapper.setProps({ updateDropdown: true });
+ await axios.waitForAll();
+
+ expect(mock.history.get).toHaveLength(2);
+ });
+ });
+
+ describe('when user opens dropdown and stage request fails', () => {
+ it('should close the dropdown', async () => {
+ mock.onGet(dropdownPath).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+ createComponent();
+
+ await openStageDropdown();
+ await axios.waitForAll();
+ await waitForPromises();
+
+ expect(findDropdown().classes('show')).toBe(false);
+ });
+ });
+
+ describe('update endpoint correctly', () => {
+ beforeEach(async () => {
+ const copyStage = { ...stageReply };
+ copyStage.latest_statuses[0].name = 'this is the updated content';
+ mock.onGet('bar.json').reply(HTTP_STATUS_OK, copyStage);
+ createComponent({
+ stage: {
+ status: {
+ group: 'running',
+ icon: 'status_running',
+ title: 'running',
+ },
+ dropdown_path: 'bar.json',
+ },
+ });
+ await axios.waitForAll();
+ });
+
+ it('should update the stage to request the new endpoint provided', async () => {
+ await openStageDropdown();
+ jest.runOnlyPendingTimers();
+ await waitForPromises();
+
+ expect(findDropdownMenu().text()).toContain('this is the updated content');
+ });
+ });
+
+ describe('job update in dropdown', () => {
+ beforeEach(async () => {
+ mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply);
+ mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(HTTP_STATUS_OK);
+
+ createComponent();
+ await waitForPromises();
+ await nextTick();
+ });
+
+ const clickCiAction = async () => {
+ await openStageDropdown();
+ jest.runOnlyPendingTimers();
+ await waitForPromises();
+
+ await findCiActionBtn().trigger('click');
+ };
+
+ it('keeps dropdown open when job item action is clicked', async () => {
+ await clickCiAction();
+ await waitForPromises();
+
+ expect(findDropdown().classes('show')).toBe(true);
+ });
+ });
+
+ describe('With merge trains enabled', () => {
+ it('shows a warning on the dropdown', async () => {
+ mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply);
+ createComponent({
+ isMergeTrain: true,
+ });
+
+ await openStageDropdown();
+ jest.runOnlyPendingTimers();
+ await waitForPromises();
+
+ const warning = findMergeTrainWarning();
+
+ expect(warning.text()).toBe('Merge train pipeline jobs can not be retried');
+ });
+ });
+
+ describe('With merge trains disabled', () => {
+ beforeEach(async () => {
+ mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply);
+ createComponent();
+
+ await openStageDropdown();
+ await axios.waitForAll();
+ });
+
+ it('does not show a warning on the dropdown', () => {
+ const warning = findMergeTrainWarning();
+
+ expect(warning.exists()).toBe(false);
+ });
+ });
+});
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 e7415a6c596..b3e157f75f6 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
@@ -1,122 +1,123 @@
-import { mount } from '@vue/test-utils';
-import { pipelines } from 'test_fixtures/pipelines/pipelines.json';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { GlLoadingIcon } from '@gitlab/ui';
+
+import { createAlert } from '~/alert';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import createMockApollo from 'helpers/mock_apollo_helper';
+
+import getLinkedPipelinesQuery from '~/pipelines/graphql/queries/get_linked_pipelines.query.graphql';
+import getPipelineStagesQuery from '~/pipelines/graphql/queries/get_pipeline_stages.query.graphql';
+import LegacyPipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/legacy_pipeline_mini_graph.vue';
import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
-import PipelineStages from '~/pipelines/components/pipeline_mini_graph/pipeline_stages.vue';
-import mockLinkedPipelines from './linked_pipelines_mock_data';
+import * as sharedGraphQlUtils from '~/graphql_shared/utils';
-const mockStages = pipelines[0].details.stages;
+import {
+ linkedPipelinesFetchError,
+ stagesFetchError,
+ mockPipelineStagesQueryResponse,
+ mockUpstreamDownstreamQueryResponse,
+} from './mock_data';
-describe('Pipeline Mini Graph', () => {
- let wrapper;
-
- const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
- const findPipelineStages = () => wrapper.findComponent(PipelineStages);
-
- const findLinkedPipelineUpstream = () =>
- wrapper.findComponent('[data-testid="pipeline-mini-graph-upstream"]');
- const findLinkedPipelineDownstream = () =>
- wrapper.findComponent('[data-testid="pipeline-mini-graph-downstream"]');
- const findDownstreamArrowIcon = () => wrapper.find('[data-testid="downstream-arrow-icon"]');
- const findUpstreamArrowIcon = () => wrapper.find('[data-testid="upstream-arrow-icon"]');
+Vue.use(VueApollo);
+jest.mock('~/alert');
- const createComponent = (props = {}) => {
- wrapper = mount(PipelineMiniGraph, {
+describe('PipelineMiniGraph', () => {
+ let wrapper;
+ let linkedPipelinesResponse;
+ let pipelineStagesResponse;
+
+ const fullPath = 'gitlab-org/gitlab';
+ const iid = '315';
+ const pipelineEtag = '/api/graphql:pipelines/id/315';
+
+ const createComponent = ({
+ pipelineStagesHandler = pipelineStagesResponse,
+ linkedPipelinesHandler = linkedPipelinesResponse,
+ } = {}) => {
+ const handlers = [
+ [getLinkedPipelinesQuery, linkedPipelinesHandler],
+ [getPipelineStagesQuery, pipelineStagesHandler],
+ ];
+ const mockApollo = createMockApollo(handlers);
+
+ wrapper = shallowMountExtended(PipelineMiniGraph, {
propsData: {
- stages: mockStages,
- ...props,
+ fullPath,
+ iid,
+ pipelineEtag,
},
+ apolloProvider: mockApollo,
});
+
+ return waitForPromises();
};
- describe('rendered state without upstream or downstream pipelines', () => {
+ const findLegacyPipelineMiniGraph = () => wrapper.findComponent(LegacyPipelineMiniGraph);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+
+ beforeEach(() => {
+ linkedPipelinesResponse = jest.fn().mockResolvedValue(mockUpstreamDownstreamQueryResponse);
+ pipelineStagesResponse = jest.fn().mockResolvedValue(mockPipelineStagesQueryResponse);
+ });
+
+ describe('when initial queries are loading', () => {
beforeEach(() => {
createComponent();
});
- it('should render the pipeline stages', () => {
- expect(findPipelineStages().exists()).toBe(true);
+ it('shows a loading icon and no mini graph', () => {
+ expect(findLoadingIcon().exists()).toBe(true);
+ expect(findLegacyPipelineMiniGraph().exists()).toBe(false);
});
+ });
- it('should have the correct props', () => {
- expect(findPipelineMiniGraph().props()).toMatchObject({
- downstreamPipelines: [],
- isMergeTrain: false,
- pipelinePath: '',
- stages: expect.any(Array),
- updateDropdown: false,
- upstreamPipeline: undefined,
- });
+ describe('when queries have loaded', () => {
+ it('does not show a loading icon', async () => {
+ await createComponent();
+
+ expect(findLoadingIcon().exists()).toBe(false);
});
- it('should have no linked pipelines', () => {
- expect(findLinkedPipelineDownstream().exists()).toBe(false);
- expect(findLinkedPipelineUpstream().exists()).toBe(false);
+ it('renders the Pipeline Mini Graph', async () => {
+ await createComponent();
+
+ expect(findLegacyPipelineMiniGraph().exists()).toBe(true);
});
- it('should not render arrow icons', () => {
- expect(findUpstreamArrowIcon().exists()).toBe(false);
- expect(findDownstreamArrowIcon().exists()).toBe(false);
+ it('fires the queries', async () => {
+ await createComponent();
+
+ expect(linkedPipelinesResponse).toHaveBeenCalledWith({ iid, fullPath });
+ expect(pipelineStagesResponse).toHaveBeenCalledWith({ iid, fullPath });
});
});
- describe('rendered state with upstream pipeline', () => {
- beforeEach(() => {
- createComponent({
- upstreamPipeline: mockLinkedPipelines.triggered_by,
- });
- });
+ describe('polling', () => {
+ it('toggles query polling with visibility check', async () => {
+ jest.spyOn(sharedGraphQlUtils, 'toggleQueryPollingByVisibility');
- it('should have the correct props', () => {
- expect(findPipelineMiniGraph().props()).toMatchObject({
- downstreamPipelines: [],
- isMergeTrain: false,
- pipelinePath: '',
- stages: expect.any(Array),
- updateDropdown: false,
- upstreamPipeline: expect.any(Object),
- });
- });
+ createComponent();
- it('should render the upstream linked pipelines mini list only', () => {
- expect(findLinkedPipelineUpstream().exists()).toBe(true);
- expect(findLinkedPipelineDownstream().exists()).toBe(false);
- });
+ await waitForPromises();
- it('should render an upstream arrow icon only', () => {
- expect(findDownstreamArrowIcon().exists()).toBe(false);
- expect(findUpstreamArrowIcon().exists()).toBe(true);
- expect(findUpstreamArrowIcon().props('name')).toBe('long-arrow');
+ expect(sharedGraphQlUtils.toggleQueryPollingByVisibility).toHaveBeenCalledTimes(2);
});
});
- describe('rendered state with downstream pipelines', () => {
- beforeEach(() => {
- createComponent({
- downstreamPipelines: mockLinkedPipelines.triggered,
- pipelinePath: 'my/pipeline/path',
- });
- });
+ describe('when pipeline queries are unsuccessful', () => {
+ const failedHandler = jest.fn().mockRejectedValue(new Error('GraphQL error'));
+ it.each`
+ query | handlerName | errorMessage
+ ${'pipeline stages'} | ${'pipelineStagesHandler'} | ${stagesFetchError}
+ ${'linked pipelines'} | ${'linkedPipelinesHandler'} | ${linkedPipelinesFetchError}
+ `('throws an error for the $query query', async ({ errorMessage, handlerName }) => {
+ await createComponent({ [handlerName]: failedHandler });
- it('should have the correct props', () => {
- expect(findPipelineMiniGraph().props()).toMatchObject({
- downstreamPipelines: expect.any(Array),
- isMergeTrain: false,
- pipelinePath: 'my/pipeline/path',
- stages: expect.any(Array),
- updateDropdown: false,
- upstreamPipeline: undefined,
- });
- });
-
- it('should render the downstream linked pipelines mini list only', () => {
- expect(findLinkedPipelineDownstream().exists()).toBe(true);
- expect(findLinkedPipelineUpstream().exists()).toBe(false);
- });
+ await waitForPromises();
- it('should render a downstream arrow icon only', () => {
- expect(findUpstreamArrowIcon().exists()).toBe(false);
- expect(findDownstreamArrowIcon().exists()).toBe(true);
- expect(findDownstreamArrowIcon().props('name')).toBe('long-arrow');
+ expect(createAlert).toHaveBeenCalledWith({ message: errorMessage });
});
});
});
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 21d92fec9bf..1989aad12b0 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
@@ -1,247 +1,46 @@
-import { GlDropdown } from '@gitlab/ui';
-import { nextTick } from 'vue';
-import { mount } from '@vue/test-utils';
-import MockAdapter from 'axios-mock-adapter';
-import CiIcon from '~/vue_shared/components/ci_icon.vue';
-import axios from '~/lib/utils/axios_utils';
-import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
-import PipelineStage from '~/pipelines/components/pipeline_mini_graph/pipeline_stage.vue';
-import eventHub from '~/pipelines/event_hub';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import { stageReply } from '../../mock_data';
-
-const dropdownPath = 'path.json';
-
-describe('Pipelines stage component', () => {
- let wrapper;
- let mock;
- let glTooltipDirectiveMock;
-
- const createComponent = (props = {}) => {
- glTooltipDirectiveMock = jest.fn();
- wrapper = mount(PipelineStage, {
- attachTo: document.body,
- directives: {
- GlTooltip: glTooltipDirectiveMock,
- },
- propsData: {
- stage: {
- status: {
- group: 'success',
- icon: 'status_success',
- title: 'success',
- },
- dropdown_path: dropdownPath,
- },
- updateDropdown: false,
- ...props,
- },
- });
- };
+import createMockApollo from 'helpers/mock_apollo_helper';
- beforeEach(() => {
- mock = new MockAdapter(axios);
- jest.spyOn(eventHub, '$emit');
- });
+import getPipelineStageQuery from '~/pipelines/graphql/queries/get_pipeline_stage.query.graphql';
+import PipelineStage from '~/pipelines/components/pipeline_mini_graph/pipeline_stage.vue';
- afterEach(() => {
- eventHub.$emit.mockRestore();
- mock.restore();
- // eslint-disable-next-line @gitlab/vtu-no-explicit-wrapper-destroy
- wrapper.destroy();
- });
+Vue.use(VueApollo);
- const findCiActionBtn = () => wrapper.find('.js-ci-action');
- const findCiIcon = () => wrapper.findComponent(CiIcon);
- const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findDropdownToggle = () => wrapper.find('button.dropdown-toggle');
- const findDropdownMenu = () =>
- wrapper.find('[data-testid="mini-pipeline-graph-dropdown-menu-list"]');
- const findDropdownMenuTitle = () =>
- wrapper.find('[data-testid="pipeline-stage-dropdown-menu-title"]');
- const findMergeTrainWarning = () => wrapper.find('[data-testid="warning-message-merge-trains"]');
- const findLoadingState = () => wrapper.find('[data-testid="pipeline-stage-loading-state"]');
+describe('PipelineStage', () => {
+ let wrapper;
+ let pipelineStageResponse;
- const openStageDropdown = async () => {
- await findDropdownToggle().trigger('click');
- await waitForPromises();
- await nextTick();
+ const defaultProps = {
+ pipelineEtag: '/etag',
+ stageId: '1',
};
- describe('loading state', () => {
- beforeEach(async () => {
- createComponent({ updateDropdown: true });
-
- mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply);
+ const createComponent = ({ pipelineStageHandler = pipelineStageResponse } = {}) => {
+ const handlers = [[getPipelineStageQuery, pipelineStageHandler]];
+ const mockApollo = createMockApollo(handlers);
- await openStageDropdown();
- });
-
- it('displays loading state while jobs are being fetched', async () => {
- jest.runOnlyPendingTimers();
- await nextTick();
-
- expect(findLoadingState().exists()).toBe(true);
- expect(findLoadingState().text()).toBe(PipelineStage.i18n.loadingText);
+ wrapper = shallowMountExtended(PipelineStage, {
+ propsData: {
+ ...defaultProps,
+ },
+ apolloProvider: mockApollo,
});
- it('does not display loading state after jobs have been fetched', async () => {
- await waitForPromises();
+ return waitForPromises();
+ };
- expect(findLoadingState().exists()).toBe(false);
- });
- });
+ const findPipelineStage = () => wrapper.findComponent(PipelineStage);
- describe('default appearance', () => {
+ describe('when mounted', () => {
beforeEach(() => {
createComponent();
});
- it('sets up the tooltip to not have a show delay animation', () => {
- expect(glTooltipDirectiveMock.mock.calls[0][1].modifiers.ds0).toBe(true);
- });
-
- it('renders a dropdown with the status icon', () => {
- expect(findDropdown().exists()).toBe(true);
- expect(findDropdownToggle().exists()).toBe(true);
- expect(findCiIcon().exists()).toBe(true);
- });
-
- it('renders a borderless ci-icon', () => {
- expect(findCiIcon().exists()).toBe(true);
- expect(findCiIcon().props('isBorderless')).toBe(true);
- expect(findCiIcon().classes('borderless')).toBe(true);
- });
-
- it('renders a ci-icon with a custom border class', () => {
- expect(findCiIcon().exists()).toBe(true);
- expect(findCiIcon().classes('gl-border')).toBe(true);
- });
- });
-
- describe('when user opens dropdown and stage request is successful', () => {
- beforeEach(async () => {
- mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply);
- createComponent();
-
- await openStageDropdown();
- await jest.runAllTimers();
- await axios.waitForAll();
- });
-
- 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');
- expect(wrapper.emitted('miniGraphStageClick')).toEqual([[]]);
- });
-
- it('refreshes when updateDropdown is set to true', async () => {
- expect(mock.history.get).toHaveLength(1);
-
- wrapper.setProps({ updateDropdown: true });
- await axios.waitForAll();
-
- expect(mock.history.get).toHaveLength(2);
- });
- });
-
- describe('when user opens dropdown and stage request fails', () => {
- it('should close the dropdown', async () => {
- mock.onGet(dropdownPath).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
- createComponent();
-
- await openStageDropdown();
- await axios.waitForAll();
- await waitForPromises();
-
- expect(findDropdown().classes('show')).toBe(false);
- });
- });
-
- describe('update endpoint correctly', () => {
- beforeEach(async () => {
- const copyStage = { ...stageReply };
- copyStage.latest_statuses[0].name = 'this is the updated content';
- mock.onGet('bar.json').reply(HTTP_STATUS_OK, copyStage);
- createComponent({
- stage: {
- status: {
- group: 'running',
- icon: 'status_running',
- title: 'running',
- },
- dropdown_path: 'bar.json',
- },
- });
- await axios.waitForAll();
- });
-
- it('should update the stage to request the new endpoint provided', async () => {
- await openStageDropdown();
- jest.runOnlyPendingTimers();
- await waitForPromises();
-
- expect(findDropdownMenu().text()).toContain('this is the updated content');
- });
- });
-
- describe('job update in dropdown', () => {
- beforeEach(async () => {
- mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply);
- mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(HTTP_STATUS_OK);
-
- createComponent();
- await waitForPromises();
- await nextTick();
- });
-
- const clickCiAction = async () => {
- await openStageDropdown();
- jest.runOnlyPendingTimers();
- await waitForPromises();
-
- await findCiActionBtn().trigger('click');
- };
-
- it('keeps dropdown open when job item action is clicked', async () => {
- await clickCiAction();
- await waitForPromises();
-
- expect(findDropdown().classes('show')).toBe(true);
- });
- });
-
- describe('With merge trains enabled', () => {
- it('shows a warning on the dropdown', async () => {
- mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply);
- createComponent({
- isMergeTrain: true,
- });
-
- await openStageDropdown();
- jest.runOnlyPendingTimers();
- await waitForPromises();
-
- const warning = findMergeTrainWarning();
-
- expect(warning.text()).toBe('Merge train pipeline jobs can not be retried');
- });
- });
-
- describe('With merge trains disabled', () => {
- beforeEach(async () => {
- mock.onGet(dropdownPath).reply(HTTP_STATUS_OK, stageReply);
- createComponent();
-
- await openStageDropdown();
- await axios.waitForAll();
- });
-
- it('does not show a warning on the dropdown', () => {
- const warning = findMergeTrainWarning();
-
- expect(warning.exists()).toBe(false);
+ it('renders job item', () => {
+ expect(findPipelineStage().exists()).toBe(true);
});
});
});
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 73e810bde99..c212087b7e3 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
@@ -1,6 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { pipelines } from 'test_fixtures/pipelines/pipelines.json';
-import PipelineStage from '~/pipelines/components/pipeline_mini_graph/pipeline_stage.vue';
+import LegacyPipelineStage from '~/pipelines/components/pipeline_mini_graph/legacy_pipeline_stage.vue';
import PipelineStages from '~/pipelines/components/pipeline_mini_graph/pipeline_stages.vue';
const mockStages = pipelines[0].details.stages;
@@ -8,8 +8,8 @@ const mockStages = pipelines[0].details.stages;
describe('Pipeline Stages', () => {
let wrapper;
- const findPipelineStages = () => wrapper.findAllComponents(PipelineStage);
- const findPipelineStagesAt = (i) => findPipelineStages().at(i);
+ const findLegacyPipelineStages = () => wrapper.findAllComponents(LegacyPipelineStage);
+ const findPipelineStagesAt = (i) => findLegacyPipelineStages().at(i);
const createComponent = (props = {}) => {
wrapper = shallowMount(PipelineStages, {
@@ -23,14 +23,14 @@ describe('Pipeline Stages', () => {
it('renders stages', () => {
createComponent();
- expect(findPipelineStages()).toHaveLength(mockStages.length);
+ expect(findLegacyPipelineStages()).toHaveLength(mockStages.length);
});
it('does not fail when stages are empty', () => {
createComponent({ stages: [] });
expect(wrapper.exists()).toBe(true);
- expect(findPipelineStages()).toHaveLength(0);
+ expect(findLegacyPipelineStages()).toHaveLength(0);
});
it('update dropdown is false by default', () => {
diff --git a/spec/frontend/pipelines/components/pipeline_tabs_spec.js b/spec/frontend/pipelines/components/pipeline_tabs_spec.js
index fde13128662..0951e1ffb46 100644
--- a/spec/frontend/pipelines/components/pipeline_tabs_spec.js
+++ b/spec/frontend/pipelines/components/pipeline_tabs_spec.js
@@ -1,10 +1,14 @@
-import { shallowMount } from '@vue/test-utils';
import { GlTab } from '@gitlab/ui';
-import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import PipelineTabs from '~/pipelines/components/pipeline_tabs.vue';
+import { TRACKING_CATEGORIES } from '~/pipelines/constants';
describe('The Pipeline Tabs', () => {
let wrapper;
+ let trackingSpy;
+
+ const $router = { push: jest.fn() };
const findDagTab = () => wrapper.findByTestId('dag-tab');
const findFailedJobsTab = () => wrapper.findByTestId('failed-jobs-tab');
@@ -24,18 +28,19 @@ describe('The Pipeline Tabs', () => {
};
const createComponent = (provide = {}) => {
- wrapper = extendedWrapper(
- shallowMount(PipelineTabs, {
- provide: {
- ...defaultProvide,
- ...provide,
- },
- stubs: {
- GlTab,
- RouterView: true,
- },
- }),
- );
+ wrapper = shallowMountExtended(PipelineTabs, {
+ provide: {
+ ...defaultProvide,
+ ...provide,
+ },
+ stubs: {
+ GlTab,
+ RouterView: true,
+ },
+ mocks: {
+ $router,
+ },
+ });
};
describe('Tabs', () => {
@@ -76,4 +81,34 @@ describe('The Pipeline Tabs', () => {
expect(badgeComponent().text()).toBe(badgeText);
});
});
+
+ describe('Tab tracking', () => {
+ beforeEach(() => {
+ createComponent();
+
+ trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
+ });
+
+ afterEach(() => {
+ unmockTracking();
+ });
+
+ it('tracks failed jobs tab click', () => {
+ findFailedJobsTab().vm.$emit('click');
+
+ expect(trackingSpy).toHaveBeenCalledTimes(1);
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_tab', {
+ label: TRACKING_CATEGORIES.failed,
+ });
+ });
+
+ it('tracks tests tab click', () => {
+ findTestsTab().vm.$emit('click');
+
+ expect(trackingSpy).toHaveBeenCalledTimes(1);
+ expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_tab', {
+ label: TRACKING_CATEGORIES.tests,
+ });
+ });
+ });
});
diff --git a/spec/frontend/pipelines/components/pipelines_list/failure_widget/failed_job_details_spec.js b/spec/frontend/pipelines/components/pipelines_list/failure_widget/failed_job_details_spec.js
index 4ba1b82e971..479ee854ecf 100644
--- a/spec/frontend/pipelines/components/pipelines_list/failure_widget/failed_job_details_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_list/failure_widget/failed_job_details_spec.js
@@ -8,6 +8,7 @@ import { createAlert } from '~/alert';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import FailedJobDetails from '~/pipelines/components/pipelines_list/failure_widget/failed_job_details.vue';
import RetryMrFailedJobMutation from '~/pipelines/graphql/mutations/retry_mr_failed_job.mutation.graphql';
+import { BRIDGE_KIND } from '~/pipelines/components/graph/constants';
import { job } from './mock';
Vue.use(VueApollo);
@@ -45,8 +46,7 @@ describe('FailedJobDetails component', () => {
const findArrowIcon = () => wrapper.findComponent(GlIcon);
const findJobId = () => wrapper.findComponent(GlLink);
- const findHiddenJobLog = () => wrapper.findByTestId('log-is-hidden');
- const findVisibleJobLog = () => wrapper.findByTestId('log-is-visible');
+ const findJobLog = () => wrapper.findByTestId('job-log');
const findJobName = () => wrapper.findByText(defaultProps.job.name);
const findRetryButton = () => wrapper.findByLabelText('Retry');
const findRow = () => wrapper.findByTestId('widget-row');
@@ -78,8 +78,7 @@ describe('FailedJobDetails component', () => {
});
it('does not renders the job lob', () => {
- expect(findHiddenJobLog().exists()).toBe(true);
- expect(findVisibleJobLog().exists()).toBe(false);
+ expect(findJobLog().exists()).toBe(false);
});
});
@@ -94,6 +93,16 @@ describe('FailedJobDetails component', () => {
});
});
+ describe('when the job is a bridge', () => {
+ beforeEach(() => {
+ createComponent({ props: { job: { ...job, kind: BRIDGE_KIND } } });
+ });
+
+ it('disables the retry button', () => {
+ expect(findRetryButton().props().disabled).toBe(true);
+ });
+ });
+
describe('when the job is retryable', () => {
describe('and user has permission to update the build', () => {
beforeEach(() => {
@@ -178,13 +187,11 @@ describe('FailedJobDetails component', () => {
});
it('does not renders the received html of the job log', () => {
- expect(findVisibleJobLog().html()).not.toContain(defaultProps.job.trace.htmlSummary);
+ expect(findJobLog().html()).not.toContain(defaultProps.job.trace.htmlSummary);
});
it('shows a permission error message', () => {
- expect(findVisibleJobLog().text()).toBe(
- "You do not have permission to read this job's log",
- );
+ expect(findJobLog().text()).toBe("You do not have permission to read this job's log.");
});
});
@@ -200,8 +207,7 @@ describe('FailedJobDetails component', () => {
describe('while collapsed', () => {
it('expands the job log', () => {
- expect(findHiddenJobLog().exists()).toBe(false);
- expect(findVisibleJobLog().exists()).toBe(true);
+ expect(findJobLog().exists()).toBe(true);
});
it('renders the down arrow', () => {
@@ -209,19 +215,17 @@ describe('FailedJobDetails component', () => {
});
it('renders the received html of the job log', () => {
- expect(findVisibleJobLog().html()).toContain(defaultProps.job.trace.htmlSummary);
+ expect(findJobLog().html()).toContain(defaultProps.job.trace.htmlSummary);
});
});
describe('while expanded', () => {
it('collapes the job log', async () => {
- expect(findHiddenJobLog().exists()).toBe(false);
- expect(findVisibleJobLog().exists()).toBe(true);
+ expect(findJobLog().exists()).toBe(true);
await findRow().trigger('click');
- expect(findHiddenJobLog().exists()).toBe(true);
- expect(findVisibleJobLog().exists()).toBe(false);
+ expect(findJobLog().exists()).toBe(false);
});
it('renders the right arrow', async () => {
@@ -236,14 +240,12 @@ describe('FailedJobDetails component', () => {
describe('when clicking on a link element within the row', () => {
it('does not expands/collapse the job log', async () => {
- expect(findHiddenJobLog().exists()).toBe(true);
- expect(findVisibleJobLog().exists()).toBe(false);
+ expect(findJobLog().exists()).toBe(false);
expect(findArrowIcon().props().name).toBe('chevron-right');
await findJobId().vm.$emit('click');
- expect(findHiddenJobLog().exists()).toBe(true);
- expect(findVisibleJobLog().exists()).toBe(false);
+ expect(findJobLog().exists()).toBe(false);
expect(findArrowIcon().props().name).toBe('chevron-right');
});
});
diff --git a/spec/frontend/pipelines/components/pipelines_list/failure_widget/failed_jobs_list_spec.js b/spec/frontend/pipelines/components/pipelines_list/failure_widget/failed_jobs_list_spec.js
index fc8263c6c4d..967812cc627 100644
--- a/spec/frontend/pipelines/components/pipelines_list/failure_widget/failed_jobs_list_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_list/failure_widget/failed_jobs_list_spec.js
@@ -23,14 +23,14 @@ describe('FailedJobsList component', () => {
const showToast = jest.fn();
const defaultProps = {
+ failedJobsCount: 0,
graphqlResourceEtag: 'api/graphql',
isPipelineActive: false,
pipelineIid: 1,
- pipelinePath: '/pipelines/1',
+ projectPath: 'namespace/project/',
};
const defaultProvide = {
- fullPath: 'namespace/project/',
graphqlPath: 'api/graphql',
};
@@ -65,6 +65,21 @@ describe('FailedJobsList component', () => {
mockFailedJobsResponse = jest.fn();
});
+ describe('on mount', () => {
+ beforeEach(() => {
+ mockFailedJobsResponse.mockResolvedValue(failedJobsMock);
+ createComponent();
+ });
+
+ it('fires the graphql query', () => {
+ expect(mockFailedJobsResponse).toHaveBeenCalledTimes(1);
+ expect(mockFailedJobsResponse).toHaveBeenCalledWith({
+ fullPath: defaultProps.projectPath,
+ pipelineIid: defaultProps.pipelineIid,
+ });
+ });
+ });
+
describe('when loading failed jobs', () => {
beforeEach(() => {
mockFailedJobsResponse.mockResolvedValue(failedJobsMock);
@@ -91,7 +106,7 @@ describe('FailedJobsList component', () => {
});
it('renders table column', () => {
- expect(findAllHeaders()).toHaveLength(4);
+ expect(findAllHeaders()).toHaveLength(3);
});
it('shows the list of failed jobs', () => {
@@ -184,6 +199,34 @@ describe('FailedJobsList component', () => {
});
});
+ describe('When the job count changes from REST', () => {
+ beforeEach(() => {
+ mockFailedJobsResponse.mockResolvedValue(failedJobsMockEmpty);
+
+ createComponent();
+ });
+
+ describe('and the count is the same', () => {
+ it('does not re-fetch the query', async () => {
+ expect(mockFailedJobsResponse).toHaveBeenCalledTimes(1);
+
+ await wrapper.setProps({ failedJobsCount: 0 });
+
+ expect(mockFailedJobsResponse).toHaveBeenCalledTimes(1);
+ });
+ });
+
+ describe('and the count is different', () => {
+ it('re-fetches the query', async () => {
+ expect(mockFailedJobsResponse).toHaveBeenCalledTimes(1);
+
+ await wrapper.setProps({ failedJobsCount: 10 });
+
+ expect(mockFailedJobsResponse).toHaveBeenCalledTimes(2);
+ });
+ });
+ });
+
describe('when an error occurs loading jobs', () => {
const errorMessage = "We couldn't fetch jobs for you because you are not qualified";
diff --git a/spec/frontend/pipelines/components/pipelines_list/failure_widget/mock.js b/spec/frontend/pipelines/components/pipelines_list/failure_widget/mock.js
index b047b57fc34..318d787a984 100644
--- a/spec/frontend/pipelines/components/pipelines_list/failure_widget/mock.js
+++ b/spec/frontend/pipelines/components/pipelines_list/failure_widget/mock.js
@@ -3,17 +3,19 @@ export const job = {
allowFailure: false,
detailedStatus: {
id: 'status',
+ detailsPath: '/jobs/5241',
action: {
id: 'action',
path: '/retry',
icon: 'retry',
},
group: 'running',
- icon: 'running-icon',
+ icon: 'status_running_icon',
},
name: 'job-name',
retried: false,
retryable: true,
+ kind: 'BUILD',
stage: {
id: '1',
name: 'build',
@@ -25,7 +27,6 @@ export const job = {
readBuild: true,
updateBuild: true,
},
- webPath: '/',
};
export const allowedToFailJob = {
diff --git a/spec/frontend/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget_spec.js b/spec/frontend/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget_spec.js
index c1a885391e9..5bbb874edb0 100644
--- a/spec/frontend/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget_spec.js
@@ -1,4 +1,4 @@
-import { GlButton, GlIcon, GlPopover } from '@gitlab/ui';
+import { GlButton, GlCard, GlIcon, GlPopover } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import PipelineFailedJobsWidget from '~/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue';
import FailedJobsList from '~/pipelines/components/pipelines_list/failure_widget/failed_jobs_list.vue';
@@ -13,6 +13,7 @@ describe('PipelineFailedJobsWidget component', () => {
isPipelineActive: false,
pipelineIid: 1,
pipelinePath: '/pipelines/1',
+ projectPath: 'namespace/project/',
};
const defaultProvide = {
@@ -29,9 +30,11 @@ describe('PipelineFailedJobsWidget component', () => {
...defaultProvide,
...provide,
},
+ stubs: { GlCard },
});
};
+ const findFailedJobsCard = () => wrapper.findByTestId('failed-jobs-card');
const findFailedJobsButton = () => wrapper.findComponent(GlButton);
const findFailedJobsList = () => wrapper.findAllComponents(FailedJobsList);
const findInfoIcon = () => wrapper.findComponent(GlIcon);
@@ -44,7 +47,7 @@ describe('PipelineFailedJobsWidget component', () => {
it('renders the show failed jobs button with a count of 0', () => {
expect(findFailedJobsButton().exists()).toBe(true);
- expect(findFailedJobsButton().text()).toBe('Show failed jobs (0)');
+ expect(findFailedJobsButton().text()).toBe('Failed jobs (0)');
});
});
@@ -55,9 +58,7 @@ describe('PipelineFailedJobsWidget component', () => {
it('renders the show failed jobs button with correct count', () => {
expect(findFailedJobsButton().exists()).toBe(true);
- expect(findFailedJobsButton().text()).toBe(
- `Show failed jobs (${defaultProps.failedJobsCount})`,
- );
+ expect(findFailedJobsButton().text()).toBe(`Failed jobs (${defaultProps.failedJobsCount})`);
});
it('renders the info icon', () => {
@@ -82,6 +83,24 @@ describe('PipelineFailedJobsWidget component', () => {
it('renders the failed jobs widget', () => {
expect(findFailedJobsList().exists()).toBe(true);
});
+
+ it('removes the CSS border classes', () => {
+ expect(findFailedJobsCard().attributes('class')).not.toContain(
+ 'gl-border-white gl-hover-border-gray-100',
+ );
+ });
+ });
+
+ describe('when the job details are not expanded', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('has the CSS border classes', () => {
+ expect(findFailedJobsCard().attributes('class')).toContain(
+ 'gl-border-white gl-hover-border-gray-100',
+ );
+ });
});
describe('when the job count changes', () => {
diff --git a/spec/frontend/pipelines/pipeline_graph/utils_spec.js b/spec/frontend/pipelines/pipeline_graph/utils_spec.js
index 41b020189d0..96b18fcf96f 100644
--- a/spec/frontend/pipelines/pipeline_graph/utils_spec.js
+++ b/spec/frontend/pipelines/pipeline_graph/utils_spec.js
@@ -1,5 +1,5 @@
import { createJobsHash, generateJobNeedsDict, getPipelineDefaultTab } from '~/pipelines/utils';
-import { validPipelineTabNames } from '~/pipelines/constants';
+import { validPipelineTabNames, pipelineTabName } from '~/pipelines/constants';
describe('utils functions', () => {
const jobName1 = 'build_1';
@@ -173,8 +173,8 @@ describe('utils functions', () => {
describe('getPipelineDefaultTab', () => {
const baseUrl = 'http://gitlab.com/user/multi-projects-small/-/pipelines/332/';
- it('returns null if there is only the base url', () => {
- expect(getPipelineDefaultTab(baseUrl)).toBe(null);
+ it('returns pipeline tab name if there is only the base url', () => {
+ expect(getPipelineDefaultTab(baseUrl)).toBe(pipelineTabName);
});
it('returns null if there was no valid last url part', () => {
diff --git a/spec/frontend/pipelines/pipeline_multi_actions_spec.js b/spec/frontend/pipelines/pipeline_multi_actions_spec.js
index 43336bbc748..0fdc45a5931 100644
--- a/spec/frontend/pipelines/pipeline_multi_actions_spec.js
+++ b/spec/frontend/pipelines/pipeline_multi_actions_spec.js
@@ -28,6 +28,20 @@ describe('Pipeline Multi Actions Dropdown', () => {
path: '/download/path-two',
},
];
+ const newArtifacts = [
+ {
+ name: 'job-3 my-new-artifact',
+ path: '/new/download/path',
+ },
+ {
+ name: 'job-4 my-new-artifact-2',
+ path: '/new/download/path-two',
+ },
+ {
+ name: 'job-5 my-new-artifact-3',
+ path: '/new/download/path-three',
+ },
+ ];
const artifactItemTestId = 'artifact-item';
const artifactsEndpointPlaceholder = ':pipeline_artifacts_id';
const artifactsEndpoint = `endpoint/${artifactsEndpointPlaceholder}/artifacts.json`;
@@ -59,8 +73,15 @@ describe('Pipeline Multi Actions Dropdown', () => {
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findAllArtifactItems = () => wrapper.findAllByTestId(artifactItemTestId);
const findFirstArtifactItem = () => wrapper.findByTestId(artifactItemTestId);
+ const findAllArtifactItemsData = () =>
+ wrapper.findAllByTestId(artifactItemTestId).wrappers.map((x) => ({
+ path: x.attributes('href'),
+ name: x.text(),
+ }));
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
const findEmptyMessage = () => wrapper.findByTestId('artifacts-empty-message');
+ const findWarning = () => wrapper.findByTestId('artifacts-fetch-warning');
+ const changePipelineId = (newId) => wrapper.setProps({ pipelineId: newId });
beforeEach(() => {
mockAxios = new MockAdapter(axios);
@@ -136,6 +157,80 @@ describe('Pipeline Multi Actions Dropdown', () => {
expect(findFirstArtifactItem().attributes('href')).toBe(artifacts[0].path);
expect(findFirstArtifactItem().text()).toBe(artifacts[0].name);
});
+
+ describe('when opened again with new artifacts', () => {
+ describe('with a successful refetch', () => {
+ beforeEach(async () => {
+ mockAxios.resetHistory();
+ mockAxios.onGet(endpoint).replyOnce(HTTP_STATUS_OK, { artifacts: newArtifacts });
+
+ findDropdown().vm.$emit('show');
+ await nextTick();
+ });
+
+ it('should hide list and render a loading spinner on dropdown click', () => {
+ expect(findAllArtifactItems()).toHaveLength(0);
+ expect(findLoadingIcon().exists()).toBe(true);
+ });
+
+ it('should not render warning or empty message while loading', () => {
+ expect(findEmptyMessage().exists()).toBe(false);
+ expect(findWarning().exists()).toBe(false);
+ });
+
+ it('should render the correct new list', async () => {
+ await waitForPromises();
+
+ expect(findAllArtifactItemsData()).toEqual(newArtifacts);
+ });
+ });
+
+ describe('with a failing refetch', () => {
+ beforeEach(async () => {
+ mockAxios.onGet(endpoint).replyOnce(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+
+ findDropdown().vm.$emit('show');
+ await waitForPromises();
+ });
+
+ it('should render warning', () => {
+ expect(findWarning().text()).toBe(i18n.artifactsFetchWarningMessage);
+ });
+
+ it('should render old list', () => {
+ expect(findAllArtifactItemsData()).toEqual(artifacts);
+ });
+ });
+ });
+
+ describe('pipeline id has changed', () => {
+ const newEndpoint = artifactsEndpoint.replace(
+ artifactsEndpointPlaceholder,
+ pipelineId + 1,
+ );
+
+ beforeEach(() => {
+ changePipelineId(pipelineId + 1);
+ });
+
+ describe('followed by a failing request', () => {
+ beforeEach(async () => {
+ mockAxios.onGet(newEndpoint).replyOnce(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+
+ findDropdown().vm.$emit('show');
+ await waitForPromises();
+ });
+
+ it('should render error message and no warning', () => {
+ expect(findWarning().exists()).toBe(false);
+ expect(findAlert().text()).toBe(i18n.artifactsFetchErrorMessage);
+ });
+
+ it('should clear list', () => {
+ expect(findAllArtifactItems()).toHaveLength(0);
+ });
+ });
+ });
});
describe('artifacts list is empty', () => {
diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js
index 5b77d44c5bd..cc85d6d99e0 100644
--- a/spec/frontend/pipelines/pipelines_spec.js
+++ b/spec/frontend/pipelines/pipelines_spec.js
@@ -744,9 +744,8 @@ describe('Pipelines', () => {
createComponent();
- stopMock = jest.spyOn(wrapper.vm.poll, 'stop');
- restartMock = jest.spyOn(wrapper.vm.poll, 'restart');
- cancelMock = jest.spyOn(wrapper.vm.service.cancelationSource, 'cancel');
+ stopMock = jest.spyOn(window, 'clearTimeout');
+ restartMock = jest.spyOn(axios, 'get');
});
describe('when a request is being made', () => {
@@ -765,13 +764,15 @@ describe('Pipelines', () => {
// cancelMock is getting overwritten in pipelines_service.js#L29
// so we have to spy on it again here
- cancelMock = jest.spyOn(wrapper.vm.service.cancelationSource, 'cancel');
+ cancelMock = jest.spyOn(axios.CancelToken, 'source');
await waitForPromises();
expect(cancelMock).toHaveBeenCalled();
expect(stopMock).toHaveBeenCalled();
- expect(restartMock).toHaveBeenCalled();
+ expect(restartMock).toHaveBeenCalledWith(
+ `${mockPipelinesResponse.pipelines[0].path}/stage.json?stage=build`,
+ );
});
it('stops polling & restarts polling', async () => {
@@ -781,7 +782,9 @@ describe('Pipelines', () => {
expect(cancelMock).not.toHaveBeenCalled();
expect(stopMock).toHaveBeenCalled();
- expect(restartMock).toHaveBeenCalled();
+ expect(restartMock).toHaveBeenCalledWith(
+ `${mockPipelinesResponse.pipelines[0].path}/stage.json?stage=build`,
+ );
});
});
});
diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js
index 251d823cc37..950a6b21e16 100644
--- a/spec/frontend/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_spec.js
@@ -4,7 +4,8 @@ import { mount } from '@vue/test-utils';
import fixture from 'test_fixtures/pipelines/pipelines.json';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import PipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/pipeline_mini_graph.vue';
+import LegacyPipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/legacy_pipeline_mini_graph.vue';
+import PipelineFailedJobsWidget from '~/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue';
import PipelineOperations from '~/pipelines/components/pipelines_list/pipeline_operations.vue';
import PipelineTriggerer from '~/pipelines/components/pipelines_list/pipeline_triggerer.vue';
import PipelineUrl from '~/pipelines/components/pipelines_list/pipeline_url.vue';
@@ -70,10 +71,11 @@ describe('Pipelines Table', () => {
const findCiBadgeLink = () => wrapper.findComponent(CiBadgeLink);
const findPipelineInfo = () => wrapper.findComponent(PipelineUrl);
const findTriggerer = () => wrapper.findComponent(PipelineTriggerer);
- const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
+ const findLegacyPipelineMiniGraph = () => wrapper.findComponent(LegacyPipelineMiniGraph);
const findTimeAgo = () => wrapper.findComponent(PipelinesTimeago);
const findActions = () => wrapper.findComponent(PipelineOperations);
+ const findPipelineFailureWidget = () => wrapper.findComponent(PipelineFailedJobsWidget);
const findTableRows = () => wrapper.findAllByTestId('pipeline-table-row');
const findStatusTh = () => wrapper.findByTestId('status-th');
const findPipelineTh = () => wrapper.findByTestId('pipeline-th');
@@ -124,12 +126,12 @@ describe('Pipelines Table', () => {
describe('stages cell', () => {
it('should render pipeline mini graph', () => {
- expect(findPipelineMiniGraph().exists()).toBe(true);
+ expect(findLegacyPipelineMiniGraph().exists()).toBe(true);
});
it('should render the right number of stages', () => {
const stagesLength = pipeline.details.stages.length;
- expect(findPipelineMiniGraph().props('stages').length).toBe(stagesLength);
+ expect(findLegacyPipelineMiniGraph().props('stages').length).toBe(stagesLength);
});
it('should render the latest downstream pipelines only', () => {
@@ -137,7 +139,7 @@ describe('Pipelines Table', () => {
// because we retried the trigger job, so the mini pipeline graph will only
// render the newly created downstream pipeline instead
expect(pipeline.triggered).toHaveLength(2);
- expect(findPipelineMiniGraph().props('downstreamPipelines')).toHaveLength(1);
+ expect(findLegacyPipelineMiniGraph().props('downstreamPipelines')).toHaveLength(1);
});
describe('when pipeline does not have stages', () => {
@@ -149,7 +151,7 @@ describe('Pipelines Table', () => {
});
it('stages are not rendered', () => {
- expect(findPipelineMiniGraph().props('stages')).toHaveLength(0);
+ expect(findLegacyPipelineMiniGraph().props('stages')).toHaveLength(0);
});
});
});
@@ -189,6 +191,7 @@ describe('Pipelines Table', () => {
it('does not render', () => {
expect(findTableRows()).toHaveLength(1);
+ expect(findPipelineFailureWidget().exists()).toBe(false);
});
});
@@ -197,8 +200,21 @@ describe('Pipelines Table', () => {
beforeEach(() => {
createComponent({ pipelines: [pipeline] }, provideWithDetails);
});
+
it('renders', () => {
expect(findTableRows()).toHaveLength(2);
+ expect(findPipelineFailureWidget().exists()).toBe(true);
+ });
+
+ it('passes the expected props', () => {
+ expect(findPipelineFailureWidget().props()).toStrictEqual({
+ failedJobsCount: pipeline.failed_builds.length,
+ isPipelineActive: pipeline.active,
+ pipelineIid: pipeline.iid,
+ pipelinePath: pipeline.path,
+ // Make sure the forward slash was removed
+ projectPath: 'frontend-fixtures/pipelines-project',
+ });
});
});
@@ -212,6 +228,7 @@ describe('Pipelines Table', () => {
it('does not render', () => {
expect(findTableRows()).toHaveLength(1);
+ expect(findPipelineFailureWidget().exists()).toBe(false);
});
});
});
@@ -252,7 +269,7 @@ describe('Pipelines Table', () => {
});
it('tracks pipeline mini graph stage click', () => {
- findPipelineMiniGraph().vm.$emit('miniGraphStageClick');
+ findLegacyPipelineMiniGraph().vm.$emit('miniGraphStageClick');
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_minigraph', {
label: TRACKING_CATEGORIES.table,
diff --git a/spec/frontend/pipelines/test_reports/test_reports_spec.js b/spec/frontend/pipelines/test_reports/test_reports_spec.js
index c8c917a1b9e..de16f496eff 100644
--- a/spec/frontend/pipelines/test_reports/test_reports_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_reports_spec.js
@@ -1,6 +1,7 @@
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
+// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import testReports from 'test_fixtures/pipelines/test_report.json';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
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 8eb83f17f4d..08b430fa703 100644
--- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
@@ -1,5 +1,6 @@
import { GlButton, GlFriendlyWrap, GlLink, GlPagination, GlEmptyState } from '@gitlab/ui';
import Vue from 'vue';
+// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import testReports from 'test_fixtures/pipelines/test_report.json';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
diff --git a/spec/frontend/pipelines/test_reports/test_summary_table_spec.js b/spec/frontend/pipelines/test_reports/test_summary_table_spec.js
index cfe9ff564dc..a45946d5a03 100644
--- a/spec/frontend/pipelines/test_reports/test_summary_table_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_summary_table_spec.js
@@ -1,5 +1,6 @@
import { mount } from '@vue/test-utils';
import Vue from 'vue';
+// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import testReports from 'test_fixtures/pipelines/test_report.json';
import SummaryTable from '~/pipelines/components/test_reports/test_summary_table.vue';