summaryrefslogtreecommitdiff
path: root/spec/frontend/pipelines
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/pipelines')
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/pipeline_mini_graph_spec.js83
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js210
-rw-r--r--spec/frontend/pipelines/empty_state_spec.js58
-rw-r--r--spec/frontend/pipelines/graph/graph_component_spec.js4
-rw-r--r--spec/frontend/pipelines/graph/graph_component_wrapper_spec.js53
-rw-r--r--spec/frontend/pipelines/graph/linked_pipelines_column_spec.js8
-rw-r--r--spec/frontend/pipelines/graph_shared/links_inner_spec.js149
-rw-r--r--spec/frontend/pipelines/graph_shared/links_layer_spec.js18
-rw-r--r--spec/frontend/pipelines/mock_data.js322
-rw-r--r--spec/frontend/pipelines/pipeline_triggerer_spec.js4
-rw-r--r--spec/frontend/pipelines/pipeline_url_spec.js34
-rw-r--r--spec/frontend/pipelines/pipelines_actions_spec.js10
-rw-r--r--spec/frontend/pipelines/pipelines_artifacts_spec.js17
-rw-r--r--spec/frontend/pipelines/pipelines_spec.js36
-rw-r--r--spec/frontend/pipelines/pipelines_table_row_spec.js35
-rw-r--r--spec/frontend/pipelines/pipelines_table_spec.js223
-rw-r--r--spec/frontend/pipelines/stage_spec.js297
-rw-r--r--spec/frontend/pipelines/time_ago_spec.js32
18 files changed, 872 insertions, 721 deletions
diff --git a/spec/frontend/pipelines/components/pipelines_list/pipeline_mini_graph_spec.js b/spec/frontend/pipelines/components/pipelines_list/pipeline_mini_graph_spec.js
new file mode 100644
index 00000000000..154828aff4b
--- /dev/null
+++ b/spec/frontend/pipelines/components/pipelines_list/pipeline_mini_graph_spec.js
@@ -0,0 +1,83 @@
+import { shallowMount } from '@vue/test-utils';
+import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue';
+import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue';
+
+const { pipelines } = getJSONFixture('pipelines/pipelines.json');
+const mockStages = pipelines[0].details.stages;
+
+describe('Pipeline Mini Graph', () => {
+ let wrapper;
+
+ const findPipelineStages = () => wrapper.findAll(PipelineStage);
+ const findPipelineStagesAt = (i) => findPipelineStages().at(i);
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(PipelineMiniGraph, {
+ propsData: {
+ stages: mockStages,
+ ...props,
+ },
+ });
+ };
+
+ it('renders stages', () => {
+ createComponent();
+
+ expect(findPipelineStages()).toHaveLength(mockStages.length);
+ });
+
+ it('renders stages with a custom class', () => {
+ createComponent({ stagesClass: 'my-class' });
+
+ expect(wrapper.findAll('.my-class')).toHaveLength(mockStages.length);
+ });
+
+ it('does not fail when stages are empty', () => {
+ createComponent({ stages: [] });
+
+ expect(wrapper.exists()).toBe(true);
+ expect(findPipelineStages()).toHaveLength(0);
+ });
+
+ it('triggers events in "action request complete" in stages', () => {
+ createComponent();
+
+ findPipelineStagesAt(0).vm.$emit('pipelineActionRequestComplete');
+ findPipelineStagesAt(1).vm.$emit('pipelineActionRequestComplete');
+
+ expect(wrapper.emitted('pipelineActionRequestComplete')).toHaveLength(2);
+ });
+
+ it('update dropdown is false by default', () => {
+ createComponent();
+
+ expect(findPipelineStagesAt(0).props('updateDropdown')).toBe(false);
+ expect(findPipelineStagesAt(1).props('updateDropdown')).toBe(false);
+ });
+
+ it('update dropdown is set to true', () => {
+ createComponent({ updateDropdown: true });
+
+ expect(findPipelineStagesAt(0).props('updateDropdown')).toBe(true);
+ expect(findPipelineStagesAt(1).props('updateDropdown')).toBe(true);
+ });
+
+ it('is merge train is false by default', () => {
+ createComponent();
+
+ expect(findPipelineStagesAt(0).props('isMergeTrain')).toBe(false);
+ expect(findPipelineStagesAt(1).props('isMergeTrain')).toBe(false);
+ });
+
+ it('is merge train is set to true', () => {
+ createComponent({ isMergeTrain: true });
+
+ 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/pipelines_list/pipeline_stage_spec.js b/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js
new file mode 100644
index 00000000000..60026f69b84
--- /dev/null
+++ b/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js
@@ -0,0 +1,210 @@
+import { GlDropdown } from '@gitlab/ui';
+import { mount } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue';
+import eventHub from '~/pipelines/event_hub';
+import { stageReply } from '../../mock_data';
+
+const dropdownPath = 'path.json';
+
+describe('Pipelines stage component', () => {
+ let wrapper;
+ let mock;
+
+ const createComponent = (props = {}) => {
+ wrapper = mount(PipelineStage, {
+ attachTo: document.body,
+ 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(() => {
+ wrapper.destroy();
+ wrapper = null;
+
+ eventHub.$emit.mockRestore();
+ mock.restore();
+ });
+
+ 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 findCiActionBtn = () => wrapper.find('.js-ci-action');
+ const findMergeTrainWarning = () => wrapper.find('[data-testid="warning-message-merge-trains"]');
+
+ const openStageDropdown = () => {
+ findDropdownToggle().trigger('click');
+ return new Promise((resolve) => {
+ wrapper.vm.$root.$on('bv::dropdown::show', resolve);
+ });
+ };
+
+ describe('default appearance', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should render a dropdown with the status icon', () => {
+ expect(findDropdown().exists()).toBe(true);
+ expect(findDropdownToggle().exists()).toBe(true);
+ expect(wrapper.find('[data-testid="status_success_borderless-icon"]').exists()).toBe(true);
+ });
+ });
+
+ describe('when update dropdown is changed', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+ });
+
+ describe('when user opens dropdown and stage request is successful', () => {
+ beforeEach(async () => {
+ mock.onGet(dropdownPath).reply(200, stageReply);
+ createComponent();
+
+ await openStageDropdown();
+ await axios.waitForAll();
+ });
+
+ it('should render the received data and emit `clickedDropdown` event', async () => {
+ expect(findDropdownMenu().text()).toContain(stageReply.latest_statuses[0].name);
+ expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
+ });
+
+ it('should refresh 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', () => {
+ beforeEach(async () => {
+ mock.onGet(dropdownPath).reply(500);
+ createComponent();
+
+ await openStageDropdown();
+ await axios.waitForAll();
+ });
+
+ it('should close the dropdown', () => {
+ 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(200, 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();
+ await axios.waitForAll();
+
+ expect(findDropdownMenu().text()).toContain('this is the updated content');
+ });
+ });
+
+ describe('pipelineActionRequestComplete', () => {
+ beforeEach(() => {
+ mock.onGet(dropdownPath).reply(200, stageReply);
+ mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200);
+
+ createComponent();
+ });
+
+ const clickCiAction = async () => {
+ await openStageDropdown();
+ await axios.waitForAll();
+
+ findCiActionBtn().trigger('click');
+ await axios.waitForAll();
+ };
+
+ it('closes dropdown when job item action is clicked', async () => {
+ const hidden = jest.fn();
+
+ wrapper.vm.$root.$on('bv::dropdown::hide', hidden);
+
+ expect(hidden).toHaveBeenCalledTimes(0);
+
+ await clickCiAction();
+
+ expect(hidden).toHaveBeenCalledTimes(1);
+ });
+
+ it('emits `pipelineActionRequestComplete` when job item action is clicked', async () => {
+ await clickCiAction();
+
+ expect(wrapper.emitted('pipelineActionRequestComplete')).toHaveLength(1);
+ });
+ });
+
+ describe('With merge trains enabled', () => {
+ beforeEach(async () => {
+ mock.onGet(dropdownPath).reply(200, stageReply);
+ createComponent({
+ isMergeTrain: true,
+ });
+
+ await openStageDropdown();
+ await axios.waitForAll();
+ });
+
+ it('shows a warning on the dropdown', () => {
+ 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(200, 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/empty_state_spec.js b/spec/frontend/pipelines/empty_state_spec.js
index 3ebedc9ac87..912bc7a104a 100644
--- a/spec/frontend/pipelines/empty_state_spec.js
+++ b/spec/frontend/pipelines/empty_state_spec.js
@@ -1,24 +1,25 @@
-import { shallowMount } from '@vue/test-utils';
+import { mount } from '@vue/test-utils';
import EmptyState from '~/pipelines/components/pipelines_list/empty_state.vue';
describe('Pipelines Empty State', () => {
let wrapper;
- const findGetStartedButton = () => wrapper.find('[data-testid="get-started-pipelines"]');
- const findInfoText = () => wrapper.find('[data-testid="info-text"]').text();
- const createWrapper = () => {
- wrapper = shallowMount(EmptyState, {
+ const findIllustration = () => wrapper.find('img');
+ const findButton = () => wrapper.find('a');
+
+ const createWrapper = (props = {}) => {
+ wrapper = mount(EmptyState, {
propsData: {
- helpPagePath: 'foo',
- emptyStateSvgPath: 'foo',
+ emptyStateSvgPath: 'foo.svg',
canSetCi: true,
+ ...props,
},
});
};
- describe('renders', () => {
+ describe('when user can configure CI', () => {
beforeEach(() => {
- createWrapper();
+ createWrapper({}, mount);
});
afterEach(() => {
@@ -27,26 +28,49 @@ describe('Pipelines Empty State', () => {
});
it('should render empty state SVG', () => {
- expect(wrapper.find('img').attributes('src')).toBe('foo');
+ expect(findIllustration().attributes('src')).toBe('foo.svg');
});
it('should render empty state header', () => {
- expect(wrapper.find('[data-testid="header-text"]').text()).toBe('Build with confidence');
- });
-
- it('should render a link with provided help path', () => {
- expect(findGetStartedButton().attributes('href')).toBe('foo');
+ expect(wrapper.text()).toContain('Build with confidence');
});
it('should render empty state information', () => {
- expect(findInfoText()).toContain(
+ expect(wrapper.text()).toContain(
'GitLab CI/CD can automatically build, test, and deploy your code. Let GitLab take care of time',
'consuming tasks, so you can spend more time creating',
);
});
+ it('should render button with help path', () => {
+ expect(findButton().attributes('href')).toBe('/help/ci/quick_start/index.md');
+ });
+
it('should render button text', () => {
- expect(findGetStartedButton().text()).toBe('Get started with CI/CD');
+ expect(findButton().text()).toBe('Get started with CI/CD');
+ });
+ });
+
+ describe('when user cannot configure CI', () => {
+ beforeEach(() => {
+ createWrapper({ canSetCi: false }, mount);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('should render empty state SVG', () => {
+ expect(findIllustration().attributes('src')).toBe('foo.svg');
+ });
+
+ it('should render empty state header', () => {
+ expect(wrapper.text()).toBe('This project is not currently set up to run pipelines.');
+ });
+
+ it('should not render a link', () => {
+ expect(findButton().exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/pipelines/graph/graph_component_spec.js b/spec/frontend/pipelines/graph/graph_component_spec.js
index 3e8d4ba314c..6c3f848333c 100644
--- a/spec/frontend/pipelines/graph/graph_component_spec.js
+++ b/spec/frontend/pipelines/graph/graph_component_spec.js
@@ -20,6 +20,10 @@ describe('graph component', () => {
const defaultProps = {
pipeline: generateResponse(mockPipelineResponse, 'root/fungi-xoxo'),
+ configPaths: {
+ metricsPath: '',
+ graphqlResourceEtag: 'this/is/a/path',
+ },
};
const defaultData = {
diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
index 202365ecd35..44d8e467f51 100644
--- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
+++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js
@@ -9,6 +9,8 @@ import PipelineGraphWrapper from '~/pipelines/components/graph/graph_component_w
import { mockPipelineResponse } from './mock_data';
const defaultProvide = {
+ graphqlResourceEtag: 'frog/amphibirama/etag/',
+ metricsPath: '',
pipelineProjectPath: 'frog/amphibirama',
pipelineIid: '22',
};
@@ -87,6 +89,13 @@ describe('Pipeline graph wrapper', () => {
it('displays the graph', () => {
expect(getGraph().exists()).toBe(true);
});
+
+ it('passes the etag resource and metrics path to the graph', () => {
+ expect(getGraph().props('configPaths')).toMatchObject({
+ graphqlResourceEtag: defaultProvide.graphqlResourceEtag,
+ metricsPath: defaultProvide.metricsPath,
+ });
+ });
});
describe('when there is an error', () => {
@@ -121,4 +130,48 @@ describe('Pipeline graph wrapper', () => {
expect(wrapper.vm.$apollo.queries.pipeline.refetch).toHaveBeenCalled();
});
});
+
+ describe('when query times out', () => {
+ const advanceApolloTimers = async () => {
+ jest.runOnlyPendingTimers();
+ await wrapper.vm.$nextTick();
+ await wrapper.vm.$nextTick();
+ };
+
+ beforeEach(async () => {
+ const errorData = {
+ data: {
+ project: {
+ pipelines: null,
+ },
+ },
+ errors: [{ message: 'timeout' }],
+ };
+
+ const failSucceedFail = jest
+ .fn()
+ .mockResolvedValueOnce(errorData)
+ .mockResolvedValueOnce(mockPipelineResponse)
+ .mockResolvedValueOnce(errorData);
+
+ createComponentWithApollo(failSucceedFail);
+ await wrapper.vm.$nextTick();
+ });
+
+ 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);
+
+ /* succeeds, clears error, shows graph */
+ await advanceApolloTimers();
+ expect(getAlert().exists()).toBe(false);
+ expect(getGraph().exists()).toBe(true);
+
+ /* fails again, alert returns but data persists */
+ await advanceApolloTimers();
+ expect(getAlert().exists()).toBe(true);
+ expect(getGraph().exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
index 8f01accccc1..4c72dad735e 100644
--- a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
@@ -20,6 +20,10 @@ describe('Linked Pipelines Column', () => {
columnTitle: 'Downstream',
linkedPipelines: processedPipeline.downstream,
type: DOWNSTREAM,
+ configPaths: {
+ metricsPath: '',
+ graphqlResourceEtag: 'this/is/a/path',
+ },
};
let wrapper;
@@ -112,7 +116,7 @@ describe('Linked Pipelines Column', () => {
it('emits the error', async () => {
await clickExpandButton();
- expect(wrapper.emitted().error).toEqual([[LOAD_FAILURE]]);
+ expect(wrapper.emitted().error).toEqual([[{ type: LOAD_FAILURE, skipSentry: true }]]);
});
it('does not show the pipeline', async () => {
@@ -163,7 +167,7 @@ describe('Linked Pipelines Column', () => {
it('emits the error', async () => {
await clickExpandButton();
- expect(wrapper.emitted().error).toEqual([[LOAD_FAILURE]]);
+ expect(wrapper.emitted().error).toEqual([[{ type: LOAD_FAILURE, skipSentry: true }]]);
});
it('does not show the pipeline', async () => {
diff --git a/spec/frontend/pipelines/graph_shared/links_inner_spec.js b/spec/frontend/pipelines/graph_shared/links_inner_spec.js
index 6cabe2bc8a7..6fef1c9b62e 100644
--- a/spec/frontend/pipelines/graph_shared/links_inner_spec.js
+++ b/spec/frontend/pipelines/graph_shared/links_inner_spec.js
@@ -1,5 +1,15 @@
import { shallowMount } from '@vue/test-utils';
+import MockAdapter from 'axios-mock-adapter';
import { setHTMLFixture } from 'helpers/fixtures';
+import axios from '~/lib/utils/axios_utils';
+import {
+ PIPELINES_DETAIL_LINK_DURATION,
+ PIPELINES_DETAIL_LINKS_TOTAL,
+ PIPELINES_DETAIL_LINKS_JOB_RATIO,
+} from '~/performance/constants';
+import * as perfUtils from '~/performance/utils';
+import * as sentryUtils from '~/pipelines/components/graph/utils';
+import * as Api from '~/pipelines/components/graph_shared/api';
import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue';
import { createJobsHash } from '~/pipelines/utils';
import {
@@ -18,7 +28,9 @@ describe('Links Inner component', () => {
containerMeasurements: { width: 1019, height: 445 },
pipelineId: 1,
pipelineData: [],
+ totalGroups: 10,
};
+
let wrapper;
const createComponent = (props) => {
@@ -194,4 +206,141 @@ describe('Links Inner component', () => {
expect(firstLink.classes(hoverColorClass)).toBe(true);
});
});
+
+ describe('performance metrics', () => {
+ let markAndMeasure;
+ let reportToSentry;
+ let reportPerformance;
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => cb());
+ markAndMeasure = jest.spyOn(perfUtils, 'performanceMarkAndMeasure');
+ reportToSentry = jest.spyOn(sentryUtils, 'reportToSentry');
+ reportPerformance = jest.spyOn(Api, 'reportPerformance');
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('with no metrics config object', () => {
+ beforeEach(() => {
+ setFixtures(pipelineData);
+ createComponent({
+ pipelineData: pipelineData.stages,
+ });
+ });
+
+ it('is not called', () => {
+ expect(markAndMeasure).not.toHaveBeenCalled();
+ expect(reportToSentry).not.toHaveBeenCalled();
+ expect(reportPerformance).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('with metrics config set to false', () => {
+ beforeEach(() => {
+ setFixtures(pipelineData);
+ createComponent({
+ pipelineData: pipelineData.stages,
+ metricsConfig: {
+ collectMetrics: false,
+ metricsPath: '/path/to/metrics',
+ },
+ });
+ });
+
+ it('is not called', () => {
+ expect(markAndMeasure).not.toHaveBeenCalled();
+ expect(reportToSentry).not.toHaveBeenCalled();
+ expect(reportPerformance).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('with no metrics path', () => {
+ beforeEach(() => {
+ setFixtures(pipelineData);
+ createComponent({
+ pipelineData: pipelineData.stages,
+ metricsConfig: {
+ collectMetrics: true,
+ metricsPath: '',
+ },
+ });
+ });
+
+ it('is not called', () => {
+ expect(markAndMeasure).not.toHaveBeenCalled();
+ expect(reportToSentry).not.toHaveBeenCalled();
+ expect(reportPerformance).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('with metrics path and collect set to true', () => {
+ const metricsPath = '/root/project/-/ci/prometheus_metrics/histograms.json';
+ const duration = 0.0478;
+ const numLinks = 1;
+ const metricsData = {
+ histograms: [
+ { name: PIPELINES_DETAIL_LINK_DURATION, value: duration / 1000 },
+ { name: PIPELINES_DETAIL_LINKS_TOTAL, value: numLinks },
+ {
+ name: PIPELINES_DETAIL_LINKS_JOB_RATIO,
+ value: numLinks / defaultProps.totalGroups,
+ },
+ ],
+ };
+
+ describe('when no duration is obtained', () => {
+ beforeEach(() => {
+ jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => {
+ return [];
+ });
+
+ setFixtures(pipelineData);
+
+ createComponent({
+ pipelineData: pipelineData.stages,
+ metricsConfig: {
+ collectMetrics: true,
+ path: metricsPath,
+ },
+ });
+ });
+
+ it('attempts to collect metrics', () => {
+ expect(markAndMeasure).toHaveBeenCalled();
+ expect(reportPerformance).not.toHaveBeenCalled();
+ expect(reportToSentry).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('with duration and no error', () => {
+ beforeEach(() => {
+ jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => {
+ return [{ duration }];
+ });
+
+ setFixtures(pipelineData);
+
+ createComponent({
+ pipelineData: pipelineData.stages,
+ metricsConfig: {
+ collectMetrics: true,
+ path: metricsPath,
+ },
+ });
+ });
+
+ it('it calls reportPerformance with expected arguments', () => {
+ expect(markAndMeasure).toHaveBeenCalled();
+ expect(reportPerformance).toHaveBeenCalled();
+ expect(reportPerformance).toHaveBeenCalledWith(metricsPath, metricsData);
+ expect(reportToSentry).not.toHaveBeenCalled();
+ });
+ });
+ });
+ });
});
diff --git a/spec/frontend/pipelines/graph_shared/links_layer_spec.js b/spec/frontend/pipelines/graph_shared/links_layer_spec.js
index 0ff8583fbff..43d8fe28893 100644
--- a/spec/frontend/pipelines/graph_shared/links_layer_spec.js
+++ b/spec/frontend/pipelines/graph_shared/links_layer_spec.js
@@ -79,6 +79,24 @@ describe('links layer component', () => {
});
});
+ describe('with width or height measurement at 0', () => {
+ beforeEach(() => {
+ createComponent({ props: { containerMeasurements: { width: 0, height: 100 } } });
+ });
+
+ it('renders the default slot', () => {
+ expect(wrapper.html()).toContain(slotContent);
+ });
+
+ it('does not render the alert component', () => {
+ expect(findAlert().exists()).toBe(false);
+ });
+
+ it('does not render the inner links component', () => {
+ expect(findLinksInner().exists()).toBe(false);
+ });
+ });
+
describe('interactions', () => {
beforeEach(() => {
createComponent({ mountFn: mount, props: { pipelineData: tooManyStages } });
diff --git a/spec/frontend/pipelines/mock_data.js b/spec/frontend/pipelines/mock_data.js
index 2afdbb05107..337838c41b3 100644
--- a/spec/frontend/pipelines/mock_data.js
+++ b/spec/frontend/pipelines/mock_data.js
@@ -2,328 +2,6 @@ const PIPELINE_RUNNING = 'RUNNING';
const PIPELINE_CANCELED = 'CANCELED';
const PIPELINE_FAILED = 'FAILED';
-export const pipelineWithStages = {
- id: 20333396,
- user: {
- id: 128633,
- name: 'Rémy Coutable',
- username: 'rymai',
- state: 'active',
- avatar_url:
- 'https://secure.gravatar.com/avatar/263da227929cc0035cb0eba512bcf81a?s=80\u0026d=identicon',
- web_url: 'https://gitlab.com/rymai',
- path: '/rymai',
- },
- active: true,
- coverage: '58.24',
- source: 'push',
- created_at: '2018-04-11T14:04:53.881Z',
- updated_at: '2018-04-11T14:05:00.792Z',
- path: '/gitlab-org/gitlab/pipelines/20333396',
- flags: {
- latest: true,
- stuck: false,
- auto_devops: false,
- yaml_errors: false,
- retryable: false,
- cancelable: true,
- failure_reason: false,
- },
- details: {
- status: {
- icon: 'status_running',
- text: 'running',
- label: 'running',
- group: 'running',
- has_details: true,
- details_path: '/gitlab-org/gitlab/pipelines/20333396',
- favicon:
- 'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_running-2eb56be2871937954b2ba6d6f4ee9fdf7e5e1c146ac45f7be98119ccaca1aca9.ico',
- },
- duration: null,
- finished_at: null,
- stages: [
- {
- name: 'build',
- title: 'build: skipped',
- status: {
- icon: 'status_skipped',
- text: 'skipped',
- label: 'skipped',
- group: 'skipped',
- has_details: true,
- details_path: '/gitlab-org/gitlab/pipelines/20333396#build',
- favicon:
- 'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_skipped-a2eee568a5bffdb494050c7b62dde241de9189280836288ac8923d369f16222d.ico',
- },
- path: '/gitlab-org/gitlab/pipelines/20333396#build',
- dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=build',
- },
- {
- name: 'prepare',
- title: 'prepare: passed',
- status: {
- icon: 'status_success',
- text: 'passed',
- label: 'passed',
- group: 'success',
- has_details: true,
- details_path: '/gitlab-org/gitlab/pipelines/20333396#prepare',
- favicon:
- 'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_success-26f59841becbef8c6fe414e9e74471d8bfd6a91b5855c19fe7f5923a40a7da47.ico',
- },
- path: '/gitlab-org/gitlab/pipelines/20333396#prepare',
- dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=prepare',
- },
- {
- name: 'test',
- title: 'test: running',
- status: {
- icon: 'status_running',
- text: 'running',
- label: 'running',
- group: 'running',
- has_details: true,
- details_path: '/gitlab-org/gitlab/pipelines/20333396#test',
- favicon:
- 'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_running-2eb56be2871937954b2ba6d6f4ee9fdf7e5e1c146ac45f7be98119ccaca1aca9.ico',
- },
- path: '/gitlab-org/gitlab/pipelines/20333396#test',
- dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=test',
- },
- {
- name: 'post-test',
- title: 'post-test: created',
- status: {
- icon: 'status_created',
- text: 'created',
- label: 'created',
- group: 'created',
- has_details: true,
- details_path: '/gitlab-org/gitlab/pipelines/20333396#post-test',
- favicon:
- 'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_created-e997aa0b7db73165df8a9d6803932b18d7b7cc37d604d2d96e378fea2dba9c5f.ico',
- },
- path: '/gitlab-org/gitlab/pipelines/20333396#post-test',
- dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=post-test',
- },
- {
- name: 'pages',
- title: 'pages: created',
- status: {
- icon: 'status_created',
- text: 'created',
- label: 'created',
- group: 'created',
- has_details: true,
- details_path: '/gitlab-org/gitlab/pipelines/20333396#pages',
- favicon:
- 'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_created-e997aa0b7db73165df8a9d6803932b18d7b7cc37d604d2d96e378fea2dba9c5f.ico',
- },
- path: '/gitlab-org/gitlab/pipelines/20333396#pages',
- dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=pages',
- },
- {
- name: 'post-cleanup',
- title: 'post-cleanup: created',
- status: {
- icon: 'status_created',
- text: 'created',
- label: 'created',
- group: 'created',
- has_details: true,
- details_path: '/gitlab-org/gitlab/pipelines/20333396#post-cleanup',
- favicon:
- 'https://assets.gitlab-static.net/assets/ci_favicons/favicon_status_created-e997aa0b7db73165df8a9d6803932b18d7b7cc37d604d2d96e378fea2dba9c5f.ico',
- },
- path: '/gitlab-org/gitlab/pipelines/20333396#post-cleanup',
- dropdown_path: '/gitlab-org/gitlab/pipelines/20333396/stage.json?stage=post-cleanup',
- },
- ],
- artifacts: [
- {
- name: 'gitlab:assets:compile',
- expired: false,
- expire_at: '2018-05-12T14:22:54.730Z',
- path: '/gitlab-org/gitlab/-/jobs/62411438/artifacts/download',
- keep_path: '/gitlab-org/gitlab/-/jobs/62411438/artifacts/keep',
- browse_path: '/gitlab-org/gitlab/-/jobs/62411438/artifacts/browse',
- },
- {
- name: 'rspec-mysql 12 28',
- expired: false,
- expire_at: '2018-05-12T14:22:45.136Z',
- path: '/gitlab-org/gitlab/-/jobs/62411397/artifacts/download',
- keep_path: '/gitlab-org/gitlab/-/jobs/62411397/artifacts/keep',
- browse_path: '/gitlab-org/gitlab/-/jobs/62411397/artifacts/browse',
- },
- {
- name: 'rspec-mysql 6 28',
- expired: false,
- expire_at: '2018-05-12T14:22:41.523Z',
- path: '/gitlab-org/gitlab/-/jobs/62411391/artifacts/download',
- keep_path: '/gitlab-org/gitlab/-/jobs/62411391/artifacts/keep',
- browse_path: '/gitlab-org/gitlab/-/jobs/62411391/artifacts/browse',
- },
- {
- name: 'rspec-pg geo 0 1',
- expired: false,
- expire_at: '2018-05-12T14:22:13.287Z',
- path: '/gitlab-org/gitlab/-/jobs/62411353/artifacts/download',
- keep_path: '/gitlab-org/gitlab/-/jobs/62411353/artifacts/keep',
- browse_path: '/gitlab-org/gitlab/-/jobs/62411353/artifacts/browse',
- },
- {
- name: 'rspec-mysql 0 28',
- expired: false,
- expire_at: '2018-05-12T14:22:06.834Z',
- path: '/gitlab-org/gitlab/-/jobs/62411385/artifacts/download',
- keep_path: '/gitlab-org/gitlab/-/jobs/62411385/artifacts/keep',
- browse_path: '/gitlab-org/gitlab/-/jobs/62411385/artifacts/browse',
- },
- {
- name: 'spinach-mysql 0 2',
- expired: false,
- expire_at: '2018-05-12T14:21:51.409Z',
- path: '/gitlab-org/gitlab/-/jobs/62411423/artifacts/download',
- keep_path: '/gitlab-org/gitlab/-/jobs/62411423/artifacts/keep',
- browse_path: '/gitlab-org/gitlab/-/jobs/62411423/artifacts/browse',
- },
- {
- name: 'karma',
- expired: false,
- expire_at: '2018-05-12T14:21:20.934Z',
- path: '/gitlab-org/gitlab/-/jobs/62411440/artifacts/download',
- keep_path: '/gitlab-org/gitlab/-/jobs/62411440/artifacts/keep',
- browse_path: '/gitlab-org/gitlab/-/jobs/62411440/artifacts/browse',
- },
- {
- name: 'spinach-pg 0 2',
- expired: false,
- expire_at: '2018-05-12T14:20:01.028Z',
- path: '/gitlab-org/gitlab/-/jobs/62411419/artifacts/download',
- keep_path: '/gitlab-org/gitlab/-/jobs/62411419/artifacts/keep',
- browse_path: '/gitlab-org/gitlab/-/jobs/62411419/artifacts/browse',
- },
- {
- name: 'spinach-pg 1 2',
- expired: false,
- expire_at: '2018-05-12T14:19:04.336Z',
- path: '/gitlab-org/gitlab/-/jobs/62411421/artifacts/download',
- keep_path: '/gitlab-org/gitlab/-/jobs/62411421/artifacts/keep',
- browse_path: '/gitlab-org/gitlab/-/jobs/62411421/artifacts/browse',
- },
- {
- name: 'sast',
- expired: null,
- expire_at: null,
- path: '/gitlab-org/gitlab/-/jobs/62411442/artifacts/download',
- browse_path: '/gitlab-org/gitlab/-/jobs/62411442/artifacts/browse',
- },
- {
- name: 'code_quality',
- expired: false,
- expire_at: '2018-04-18T14:16:24.484Z',
- path: '/gitlab-org/gitlab/-/jobs/62411441/artifacts/download',
- keep_path: '/gitlab-org/gitlab/-/jobs/62411441/artifacts/keep',
- browse_path: '/gitlab-org/gitlab/-/jobs/62411441/artifacts/browse',
- },
- {
- name: 'cache gems',
- expired: null,
- expire_at: null,
- path: '/gitlab-org/gitlab/-/jobs/62411447/artifacts/download',
- browse_path: '/gitlab-org/gitlab/-/jobs/62411447/artifacts/browse',
- },
- {
- name: 'dependency_scanning',
- expired: null,
- expire_at: null,
- path: '/gitlab-org/gitlab/-/jobs/62411443/artifacts/download',
- browse_path: '/gitlab-org/gitlab/-/jobs/62411443/artifacts/browse',
- },
- {
- name: 'compile-assets',
- expired: false,
- expire_at: '2018-04-18T14:12:07.638Z',
- path: '/gitlab-org/gitlab/-/jobs/62411334/artifacts/download',
- keep_path: '/gitlab-org/gitlab/-/jobs/62411334/artifacts/keep',
- browse_path: '/gitlab-org/gitlab/-/jobs/62411334/artifacts/browse',
- },
- {
- name: 'setup-test-env',
- expired: false,
- expire_at: '2018-04-18T14:10:27.024Z',
- path: '/gitlab-org/gitlab/-/jobs/62411336/artifacts/download',
- keep_path: '/gitlab-org/gitlab/-/jobs/62411336/artifacts/keep',
- browse_path: '/gitlab-org/gitlab/-/jobs/62411336/artifacts/browse',
- },
- {
- name: 'retrieve-tests-metadata',
- expired: false,
- expire_at: '2018-05-12T14:06:35.926Z',
- path: '/gitlab-org/gitlab/-/jobs/62411333/artifacts/download',
- keep_path: '/gitlab-org/gitlab/-/jobs/62411333/artifacts/keep',
- browse_path: '/gitlab-org/gitlab/-/jobs/62411333/artifacts/browse',
- },
- ],
- manual_actions: [
- {
- name: 'package-and-qa',
- path: '/gitlab-org/gitlab/-/jobs/62411330/play',
- playable: true,
- },
- {
- name: 'review-docs-deploy',
- path: '/gitlab-org/gitlab/-/jobs/62411332/play',
- playable: true,
- },
- ],
- },
- ref: {
- name: 'master',
- path: '/gitlab-org/gitlab/commits/master',
- tag: false,
- branch: true,
- },
- commit: {
- id: 'e6a2885c503825792cb8a84a8731295e361bd059',
- short_id: 'e6a2885c',
- title: "Merge branch 'ce-to-ee-2018-04-11' into 'master'",
- created_at: '2018-04-11T14:04:39.000Z',
- parent_ids: [
- '5d9b5118f6055f72cff1a82b88133609912f2c1d',
- '6fdc6ee76a8062fe41b1a33f7c503334a6ebdc02',
- ],
- message:
- "Merge branch 'ce-to-ee-2018-04-11' into 'master'\n\nCE upstream - 2018-04-11 12:26 UTC\n\nSee merge request gitlab-org/gitlab-ee!5326",
- author_name: 'Rémy Coutable',
- author_email: 'remy@rymai.me',
- authored_date: '2018-04-11T14:04:39.000Z',
- committer_name: 'Rémy Coutable',
- committer_email: 'remy@rymai.me',
- committed_date: '2018-04-11T14:04:39.000Z',
- author: {
- id: 128633,
- name: 'Rémy Coutable',
- username: 'rymai',
- state: 'active',
- avatar_url:
- 'https://secure.gravatar.com/avatar/263da227929cc0035cb0eba512bcf81a?s=80\u0026d=identicon',
- web_url: 'https://gitlab.com/rymai',
- path: '/rymai',
- },
- author_gravatar_url:
- 'https://secure.gravatar.com/avatar/263da227929cc0035cb0eba512bcf81a?s=80\u0026d=identicon',
- commit_url:
- 'https://gitlab.com/gitlab-org/gitlab/commit/e6a2885c503825792cb8a84a8731295e361bd059',
- commit_path: '/gitlab-org/gitlab/commit/e6a2885c503825792cb8a84a8731295e361bd059',
- },
- cancel_path: '/gitlab-org/gitlab/pipelines/20333396/cancel',
- triggered_by: null,
- triggered: [],
-};
-
const threeWeeksAgo = new Date();
threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);
diff --git a/spec/frontend/pipelines/pipeline_triggerer_spec.js b/spec/frontend/pipelines/pipeline_triggerer_spec.js
index 467a97d95c7..ffb2721f159 100644
--- a/spec/frontend/pipelines/pipeline_triggerer_spec.js
+++ b/spec/frontend/pipelines/pipeline_triggerer_spec.js
@@ -35,8 +35,8 @@ describe('Pipelines Triggerer', () => {
wrapper.destroy();
});
- it('should render a table cell', () => {
- expect(wrapper.find('.table-section').exists()).toBe(true);
+ it('should render pipeline triggerer table cell', () => {
+ expect(wrapper.find('[data-testid="pipeline-triggerer"]').exists()).toBe(true);
});
it('should pass triggerer information when triggerer is provided', () => {
diff --git a/spec/frontend/pipelines/pipeline_url_spec.js b/spec/frontend/pipelines/pipeline_url_spec.js
index 44c9def99cc..367c7f2b2f6 100644
--- a/spec/frontend/pipelines/pipeline_url_spec.js
+++ b/spec/frontend/pipelines/pipeline_url_spec.js
@@ -1,19 +1,20 @@
import { shallowMount } from '@vue/test-utils';
-import $ from 'jquery';
import { trimText } from 'helpers/text_helper';
import PipelineUrlComponent from '~/pipelines/components/pipelines_list/pipeline_url.vue';
-$.fn.popover = () => {};
+const projectPath = 'test/test';
describe('Pipeline Url Component', () => {
let wrapper;
+ const findTableCell = () => wrapper.find('[data-testid="pipeline-url-table-cell"]');
const findPipelineUrlLink = () => wrapper.find('[data-testid="pipeline-url-link"]');
const findScheduledTag = () => wrapper.find('[data-testid="pipeline-url-scheduled"]');
const findLatestTag = () => wrapper.find('[data-testid="pipeline-url-latest"]');
const findYamlTag = () => wrapper.find('[data-testid="pipeline-url-yaml"]');
const findFailureTag = () => wrapper.find('[data-testid="pipeline-url-failure"]');
const findAutoDevopsTag = () => wrapper.find('[data-testid="pipeline-url-autodevops"]');
+ const findAutoDevopsTagLink = () => wrapper.find('[data-testid="pipeline-url-autodevops-link"]');
const findStuckTag = () => wrapper.find('[data-testid="pipeline-url-stuck"]');
const findDetachedTag = () => wrapper.find('[data-testid="pipeline-url-detached"]');
const findForkTag = () => wrapper.find('[data-testid="pipeline-url-fork"]');
@@ -23,9 +24,9 @@ describe('Pipeline Url Component', () => {
pipeline: {
id: 1,
path: 'foo',
+ project: { full_path: `/${projectPath}` },
flags: {},
},
- autoDevopsHelpPath: 'foo',
pipelineScheduleUrl: 'foo',
};
@@ -33,7 +34,7 @@ describe('Pipeline Url Component', () => {
wrapper = shallowMount(PipelineUrlComponent, {
propsData: { ...defaultProps, ...props },
provide: {
- targetProjectFullPath: 'test/test',
+ targetProjectFullPath: projectPath,
},
});
};
@@ -43,10 +44,10 @@ describe('Pipeline Url Component', () => {
wrapper = null;
});
- it('should render a table cell', () => {
+ it('should render pipeline url table cell', () => {
createComponent();
- expect(wrapper.attributes('class')).toContain('table-section');
+ expect(findTableCell().exists()).toBe(true);
});
it('should render a link the provided path and id', () => {
@@ -57,6 +58,19 @@ describe('Pipeline Url Component', () => {
expect(findPipelineUrlLink().text()).toBe('#1');
});
+ it('should not render tags when flags are not set', () => {
+ createComponent();
+
+ expect(findStuckTag().exists()).toBe(false);
+ expect(findLatestTag().exists()).toBe(false);
+ expect(findYamlTag().exists()).toBe(false);
+ expect(findAutoDevopsTag().exists()).toBe(false);
+ expect(findFailureTag().exists()).toBe(false);
+ expect(findScheduledTag().exists()).toBe(false);
+ expect(findForkTag().exists()).toBe(false);
+ expect(findTrainTag().exists()).toBe(false);
+ });
+
it('should render the stuck tag when flag is provided', () => {
createComponent({
pipeline: {
@@ -96,6 +110,7 @@ describe('Pipeline Url Component', () => {
it('should render an autodevops badge when flag is provided', () => {
createComponent({
pipeline: {
+ ...defaultProps.pipeline,
flags: {
auto_devops: true,
},
@@ -103,6 +118,11 @@ describe('Pipeline Url Component', () => {
});
expect(trimText(findAutoDevopsTag().text())).toBe('Auto DevOps');
+
+ expect(findAutoDevopsTagLink().attributes()).toMatchObject({
+ href: '/help/topics/autodevops/index.md',
+ target: '_blank',
+ });
});
it('should render a detached badge when flag is provided', () => {
@@ -147,7 +167,7 @@ describe('Pipeline Url Component', () => {
createComponent({
pipeline: {
flags: {},
- project: { fullPath: 'test/forked' },
+ project: { fullPath: '/test/forked' },
},
});
diff --git a/spec/frontend/pipelines/pipelines_actions_spec.js b/spec/frontend/pipelines/pipelines_actions_spec.js
index 1e6c9e50a7e..c4bfec8ae14 100644
--- a/spec/frontend/pipelines/pipelines_actions_spec.js
+++ b/spec/frontend/pipelines/pipelines_actions_spec.js
@@ -1,11 +1,11 @@
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import { shallowMount, mount } from '@vue/test-utils';
+import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import waitForPromises from 'helpers/wait_for_promises';
import { TEST_HOST } from 'spec/test_constants';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
-import PipelinesActions from '~/pipelines/components/pipelines_list/pipelines_actions.vue';
+import PipelinesManualActions from '~/pipelines/components/pipelines_list/pipelines_manual_actions.vue';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
jest.mock('~/flash');
@@ -15,7 +15,7 @@ describe('Pipelines Actions dropdown', () => {
let mock;
const createComponent = (props, mountFn = shallowMount) => {
- wrapper = mountFn(PipelinesActions, {
+ wrapper = mountFn(PipelinesManualActions, {
propsData: {
...props,
},
@@ -63,10 +63,6 @@ describe('Pipelines Actions dropdown', () => {
});
describe('on click', () => {
- beforeEach(() => {
- createComponent({ actions: mockActions }, mount);
- });
-
it('makes a request and toggles the loading state', async () => {
mock.onPost(mockActions.path).reply(200);
diff --git a/spec/frontend/pipelines/pipelines_artifacts_spec.js b/spec/frontend/pipelines/pipelines_artifacts_spec.js
index f077833ae16..d4a2db08d97 100644
--- a/spec/frontend/pipelines/pipelines_artifacts_spec.js
+++ b/spec/frontend/pipelines/pipelines_artifacts_spec.js
@@ -1,24 +1,27 @@
-import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
+import { GlDropdown, GlDropdownItem, GlSprintf } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
import PipelineArtifacts from '~/pipelines/components/pipelines_list/pipelines_artifacts.vue';
describe('Pipelines Artifacts dropdown', () => {
let wrapper;
const createComponent = () => {
- wrapper = mount(PipelineArtifacts, {
+ wrapper = shallowMount(PipelineArtifacts, {
propsData: {
artifacts: [
{
- name: 'artifact',
+ name: 'job my-artifact',
path: '/download/path',
},
{
- name: 'artifact two',
+ name: 'job-2 my-artifact-2',
path: '/download/path-two',
},
],
},
+ stubs: {
+ GlSprintf,
+ },
});
};
@@ -39,8 +42,8 @@ describe('Pipelines Artifacts dropdown', () => {
});
it('should render a link with the provided path', () => {
- expect(findFirstGlDropdownItem().find('a').attributes('href')).toEqual('/download/path');
+ expect(findFirstGlDropdownItem().attributes('href')).toBe('/download/path');
- expect(findFirstGlDropdownItem().text()).toContain('artifact');
+ expect(findFirstGlDropdownItem().text()).toBe('Download job my-artifact artifact');
});
});
diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js
index 811303a5624..b04880b43ae 100644
--- a/spec/frontend/pipelines/pipelines_spec.js
+++ b/spec/frontend/pipelines/pipelines_spec.js
@@ -1,4 +1,4 @@
-import { GlFilteredSearch, GlButton, GlLoadingIcon, GlPagination } from '@gitlab/ui';
+import { GlButton, GlFilteredSearch, GlLoadingIcon, GlPagination } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { chunk } from 'lodash';
@@ -18,7 +18,7 @@ import Store from '~/pipelines/stores/pipelines_store';
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
-import { pipelineWithStages, stageReply, users, mockSearch, branches } from './mock_data';
+import { stageReply, users, mockSearch, branches } from './mock_data';
jest.mock('~/flash');
@@ -27,6 +27,9 @@ const mockProjectId = '21';
const mockPipelinesEndpoint = `/${mockProjectPath}/pipelines.json`;
const mockPipelinesResponse = getJSONFixture('pipelines/pipelines.json');
const mockPipelinesIds = mockPipelinesResponse.pipelines.map(({ id }) => id);
+const mockPipelineWithStages = mockPipelinesResponse.pipelines.find(
+ (p) => p.details.stages && p.details.stages.length,
+);
describe('Pipelines', () => {
let wrapper;
@@ -34,8 +37,6 @@ describe('Pipelines', () => {
let origWindowLocation;
const paths = {
- autoDevopsHelpPath: '/help/topics/autodevops/index.md',
- helpPagePath: '/help/ci/quick_start/README',
emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg',
noPipelinesSvgPath: '/assets/illustrations/pipelines_pending.svg',
@@ -45,8 +46,6 @@ describe('Pipelines', () => {
};
const noPermissions = {
- autoDevopsHelpPath: '/help/topics/autodevops/index.md',
- helpPagePath: '/help/ci/quick_start/README',
emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg',
noPipelinesSvgPath: '/assets/illustrations/pipelines_pending.svg',
@@ -70,7 +69,8 @@ describe('Pipelines', () => {
const findRunPipelineButton = () => wrapper.findByTestId('run-pipeline-button');
const findCiLintButton = () => wrapper.findByTestId('ci-lint-button');
const findCleanCacheButton = () => wrapper.findByTestId('clear-cache-button');
- const findStagesDropdown = () => wrapper.findByTestId('mini-pipeline-graph-dropdown-toggle');
+ const findStagesDropdownToggle = () =>
+ wrapper.find('[data-testid="mini-pipeline-graph-dropdown"] .dropdown-toggle');
const findPipelineUrlLinks = () => wrapper.findAll('[data-testid="pipeline-url-link"]');
const createComponent = (props = defaultProps) => {
@@ -539,14 +539,15 @@ describe('Pipelines', () => {
});
it('renders empty state', () => {
- expect(findEmptyState().find('[data-testid="header-text"]').text()).toBe(
- 'Build with confidence',
- );
- expect(findEmptyState().find('[data-testid="info-text"]').text()).toContain(
+ expect(findEmptyState().text()).toContain('Build with confidence');
+ expect(findEmptyState().text()).toContain(
'GitLab CI/CD can automatically build, test, and deploy your code.',
);
+
expect(findEmptyState().find(GlButton).text()).toBe('Get started with CI/CD');
- expect(findEmptyState().find(GlButton).attributes('href')).toBe(paths.helpPagePath);
+ expect(findEmptyState().find(GlButton).attributes('href')).toBe(
+ '/help/ci/quick_start/index.md',
+ );
});
it('does not render tabs nor buttons', () => {
@@ -613,14 +614,15 @@ describe('Pipelines', () => {
mock.onGet(mockPipelinesEndpoint, { scope: 'all', page: '1' }).reply(
200,
{
- pipelines: [pipelineWithStages],
+ pipelines: [mockPipelineWithStages],
count: { all: '1' },
},
{
'POLL-INTERVAL': 100,
},
);
- mock.onGet(pipelineWithStages.details.stages[0].dropdown_path).reply(200, stageReply);
+
+ mock.onGet(mockPipelineWithStages.details.stages[0].dropdown_path).reply(200, stageReply);
createComponent();
@@ -640,7 +642,7 @@ describe('Pipelines', () => {
// Mock init a polling cycle
wrapper.vm.poll.options.notificationCallback(true);
- findStagesDropdown().trigger('click');
+ findStagesDropdownToggle().trigger('click');
await waitForPromises();
@@ -650,7 +652,9 @@ describe('Pipelines', () => {
});
it('stops polling & restarts polling', async () => {
- findStagesDropdown().trigger('click');
+ findStagesDropdownToggle().trigger('click');
+
+ await waitForPromises();
expect(cancelMock).not.toHaveBeenCalled();
expect(stopMock).toHaveBeenCalled();
diff --git a/spec/frontend/pipelines/pipelines_table_row_spec.js b/spec/frontend/pipelines/pipelines_table_row_spec.js
index 660651547fc..68d46575081 100644
--- a/spec/frontend/pipelines/pipelines_table_row_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_row_spec.js
@@ -1,4 +1,5 @@
import { mount } from '@vue/test-utils';
+import waitForPromises from 'helpers/wait_for_promises';
import PipelinesTableRowComponent from '~/pipelines/components/pipelines_list/pipelines_table_row.vue';
import eventHub from '~/pipelines/event_hub';
@@ -9,7 +10,6 @@ describe('Pipelines Table Row', () => {
mount(PipelinesTableRowComponent, {
propsData: {
pipeline,
- autoDevopsHelpPath: 'foo',
viewType: 'root',
},
});
@@ -19,8 +19,6 @@ describe('Pipelines Table Row', () => {
let pipelineWithoutAuthor;
let pipelineWithoutCommit;
- preloadFixtures(jsonFixtureName);
-
beforeEach(() => {
const { pipelines } = getJSONFixture(jsonFixtureName);
@@ -149,16 +147,22 @@ describe('Pipelines Table Row', () => {
});
describe('stages column', () => {
- beforeEach(() => {
+ const findAllMiniPipelineStages = () =>
+ wrapper.findAll('.table-section:nth-child(5) [data-testid="mini-pipeline-graph-dropdown"]');
+
+ it('should render an icon for each stage', () => {
wrapper = createWrapper(pipeline);
+
+ expect(findAllMiniPipelineStages()).toHaveLength(pipeline.details.stages.length);
});
- it('should render an icon for each stage', () => {
- expect(
- wrapper.findAll(
- '.table-section:nth-child(4) [data-testid="mini-pipeline-graph-dropdown-toggle"]',
- ).length,
- ).toEqual(pipeline.details.stages.length);
+ it('should not render stages when stages are empty', () => {
+ const withoutStages = { ...pipeline };
+ withoutStages.details = { ...withoutStages.details, stages: null };
+
+ wrapper = createWrapper(withoutStages);
+
+ expect(findAllMiniPipelineStages()).toHaveLength(0);
});
});
@@ -183,9 +187,16 @@ describe('Pipelines Table Row', () => {
expect(wrapper.find('.js-pipelines-retry-button').attributes('title')).toMatch('Retry');
expect(wrapper.find('.js-pipelines-cancel-button').exists()).toBe(true);
expect(wrapper.find('.js-pipelines-cancel-button').attributes('title')).toMatch('Cancel');
- const dropdownMenu = wrapper.find('.dropdown-menu');
+ });
+
+ it('should render the manual actions', async () => {
+ const manualActions = wrapper.find('[data-testid="pipelines-manual-actions-dropdown"]');
+
+ // Click on the dropdown and wait for `lazy` dropdown items
+ manualActions.find('.dropdown-toggle').trigger('click');
+ await waitForPromises();
- expect(dropdownMenu.text()).toContain(scheduledJobAction.name);
+ expect(manualActions.text()).toContain(scheduledJobAction.name);
});
it('emits `retryPipeline` event when retry button is clicked and toggles loading', () => {
diff --git a/spec/frontend/pipelines/pipelines_table_spec.js b/spec/frontend/pipelines/pipelines_table_spec.js
index fd73d507919..952bea81052 100644
--- a/spec/frontend/pipelines/pipelines_table_spec.js
+++ b/spec/frontend/pipelines/pipelines_table_spec.js
@@ -1,5 +1,18 @@
+import { GlTable } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import PipelineMiniGraph from '~/pipelines/components/pipelines_list/pipeline_mini_graph.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';
+import PipelinesStatusBadge from '~/pipelines/components/pipelines_list/pipelines_status_badge.vue';
import PipelinesTable from '~/pipelines/components/pipelines_list/pipelines_table.vue';
+import PipelinesTimeago from '~/pipelines/components/pipelines_list/time_ago.vue';
+
+import eventHub from '~/pipelines/event_hub';
+import CommitComponent from '~/vue_shared/components/commit.vue';
+
+jest.mock('~/pipelines/event_hub');
describe('Pipelines Table', () => {
let pipeline;
@@ -9,24 +22,52 @@ describe('Pipelines Table', () => {
const defaultProps = {
pipelines: [],
- autoDevopsHelpPath: 'foo',
viewType: 'root',
};
- const createComponent = (props = defaultProps) => {
- wrapper = mount(PipelinesTable, {
- propsData: props,
- });
+ const createMockPipeline = () => {
+ const { pipelines } = getJSONFixture(jsonFixtureName);
+ return pipelines.find((p) => p.user !== null && p.commit !== null);
};
- const findRows = () => wrapper.findAll('.commit.gl-responsive-table-row');
- preloadFixtures(jsonFixtureName);
+ const createComponent = (props = {}, flagState = false) => {
+ wrapper = extendedWrapper(
+ mount(PipelinesTable, {
+ propsData: {
+ ...defaultProps,
+ ...props,
+ },
+ provide: {
+ glFeatures: {
+ newPipelinesTable: flagState,
+ },
+ },
+ }),
+ );
+ };
- beforeEach(() => {
- const { pipelines } = getJSONFixture(jsonFixtureName);
- pipeline = pipelines.find((p) => p.user !== null && p.commit !== null);
+ const findRows = () => wrapper.findAll('.commit.gl-responsive-table-row');
+ const findGlTable = () => wrapper.findComponent(GlTable);
+ const findStatusBadge = () => wrapper.findComponent(PipelinesStatusBadge);
+ const findPipelineInfo = () => wrapper.findComponent(PipelineUrl);
+ const findTriggerer = () => wrapper.findComponent(PipelineTriggerer);
+ const findCommit = () => wrapper.findComponent(CommitComponent);
+ const findPipelineMiniGraph = () => wrapper.findComponent(PipelineMiniGraph);
+ const findTimeAgo = () => wrapper.findComponent(PipelinesTimeago);
+ const findActions = () => wrapper.findComponent(PipelineOperations);
+
+ const findLegacyTable = () => wrapper.findByTestId('legacy-ci-table');
+ const findTableRows = () => wrapper.findAll('[data-testid="pipeline-table-row"]');
+ const findStatusTh = () => wrapper.findByTestId('status-th');
+ const findPipelineTh = () => wrapper.findByTestId('pipeline-th');
+ const findTriggererTh = () => wrapper.findByTestId('triggerer-th');
+ const findCommitTh = () => wrapper.findByTestId('commit-th');
+ const findStagesTh = () => wrapper.findByTestId('stages-th');
+ const findTimeAgoTh = () => wrapper.findByTestId('timeago-th');
+ const findActionsTh = () => wrapper.findByTestId('actions-th');
- createComponent();
+ beforeEach(() => {
+ pipeline = createMockPipeline();
});
afterEach(() => {
@@ -34,33 +75,161 @@ describe('Pipelines Table', () => {
wrapper = null;
});
- describe('table', () => {
- it('should render a table', () => {
- expect(wrapper.classes()).toContain('ci-table');
+ describe('table with feature flag off', () => {
+ describe('renders the table correctly', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('should render a table', () => {
+ expect(wrapper.classes()).toContain('ci-table');
+ });
+
+ it('should render table head with correct columns', () => {
+ expect(wrapper.find('.table-section.js-pipeline-status').text()).toEqual('Status');
+
+ expect(wrapper.find('.table-section.js-pipeline-info').text()).toEqual('Pipeline');
+
+ expect(wrapper.find('.table-section.js-pipeline-commit').text()).toEqual('Commit');
+
+ expect(wrapper.find('.table-section.js-pipeline-stages').text()).toEqual('Stages');
+ });
});
- it('should render table head with correct columns', () => {
- expect(wrapper.find('.table-section.js-pipeline-status').text()).toEqual('Status');
+ describe('without data', () => {
+ it('should render an empty table', () => {
+ createComponent();
- expect(wrapper.find('.table-section.js-pipeline-info').text()).toEqual('Pipeline');
+ expect(findRows()).toHaveLength(0);
+ });
+ });
- expect(wrapper.find('.table-section.js-pipeline-commit').text()).toEqual('Commit');
+ describe('with data', () => {
+ it('should render rows', () => {
+ createComponent({ pipelines: [pipeline], viewType: 'root' });
- expect(wrapper.find('.table-section.js-pipeline-stages').text()).toEqual('Stages');
+ expect(findRows()).toHaveLength(1);
+ });
});
});
- describe('without data', () => {
- it('should render an empty table', () => {
- expect(findRows()).toHaveLength(0);
+ describe('table with feature flag on', () => {
+ beforeEach(() => {
+ createComponent({ pipelines: [pipeline], viewType: 'root' }, true);
+ });
+
+ it('displays new table', () => {
+ expect(findGlTable().exists()).toBe(true);
+ expect(findLegacyTable().exists()).toBe(false);
+ });
+
+ it('should render table head with correct columns', () => {
+ expect(findStatusTh().text()).toBe('Status');
+ expect(findPipelineTh().text()).toBe('Pipeline');
+ expect(findTriggererTh().text()).toBe('Triggerer');
+ expect(findCommitTh().text()).toBe('Commit');
+ expect(findStagesTh().text()).toBe('Stages');
+ expect(findTimeAgoTh().text()).toBe('Duration');
+ expect(findActionsTh().text()).toBe('Actions');
+ });
+
+ it('should display a table row', () => {
+ expect(findTableRows()).toHaveLength(1);
});
- });
- describe('with data', () => {
- it('should render rows', () => {
- createComponent({ pipelines: [pipeline], autoDevopsHelpPath: 'foo', viewType: 'root' });
+ describe('status cell', () => {
+ it('should render a status badge', () => {
+ expect(findStatusBadge().exists()).toBe(true);
+ });
+
+ it('should render status badge with correct path', () => {
+ expect(findStatusBadge().attributes('href')).toBe(pipeline.path);
+ });
+ });
+
+ describe('pipeline cell', () => {
+ it('should render pipeline information', () => {
+ expect(findPipelineInfo().exists()).toBe(true);
+ });
+
+ it('should display the pipeline id', () => {
+ expect(findPipelineInfo().text()).toContain(`#${pipeline.id}`);
+ });
+ });
+
+ describe('triggerer cell', () => {
+ it('should render the pipeline triggerer', () => {
+ expect(findTriggerer().exists()).toBe(true);
+ });
+ });
+
+ describe('commit cell', () => {
+ it('should render commit information', () => {
+ expect(findCommit().exists()).toBe(true);
+ });
+
+ it('should display and link to commit', () => {
+ expect(findCommit().text()).toContain(pipeline.commit.short_id);
+ expect(findCommit().props('commitUrl')).toBe(pipeline.commit.commit_path);
+ });
+
+ it('should display the commit author', () => {
+ expect(findCommit().props('author')).toEqual(pipeline.commit.author);
+ });
+ });
+
+ describe('stages cell', () => {
+ it('should render a pipeline mini graph', () => {
+ expect(findPipelineMiniGraph().exists()).toBe(true);
+ });
+
+ it('should render the right number of stages', () => {
+ const stagesLength = pipeline.details.stages.length;
+ expect(
+ findPipelineMiniGraph().findAll('[data-testid="mini-pipeline-graph-dropdown"]'),
+ ).toHaveLength(stagesLength);
+ });
+
+ describe('when pipeline does not have stages', () => {
+ beforeEach(() => {
+ pipeline = createMockPipeline();
+ pipeline.details.stages = null;
+
+ createComponent({ pipelines: [pipeline] }, true);
+ });
+
+ it('stages are not rendered', () => {
+ expect(findPipelineMiniGraph().exists()).toBe(false);
+ });
+ });
+
+ it('should not update dropdown', () => {
+ expect(findPipelineMiniGraph().props('updateDropdown')).toBe(false);
+ });
+
+ it('when update graph dropdown is set, should update graph dropdown', () => {
+ createComponent({ pipelines: [pipeline], updateGraphDropdown: true }, true);
+
+ expect(findPipelineMiniGraph().props('updateDropdown')).toBe(true);
+ });
+
+ it('when action request is complete, should refresh table', () => {
+ findPipelineMiniGraph().vm.$emit('pipelineActionRequestComplete');
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
+ });
+ });
+
+ describe('duration cell', () => {
+ it('should render duration information', () => {
+ expect(findTimeAgo().exists()).toBe(true);
+ });
+ });
- expect(findRows()).toHaveLength(1);
+ describe('operations cell', () => {
+ it('should render pipeline operations', () => {
+ expect(findActions().exists()).toBe(true);
+ });
});
});
});
diff --git a/spec/frontend/pipelines/stage_spec.js b/spec/frontend/pipelines/stage_spec.js
deleted file mode 100644
index 87b43558252..00000000000
--- a/spec/frontend/pipelines/stage_spec.js
+++ /dev/null
@@ -1,297 +0,0 @@
-import 'bootstrap/js/dist/dropdown';
-import { GlDropdown } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
-import MockAdapter from 'axios-mock-adapter';
-import $ from 'jquery';
-import axios from '~/lib/utils/axios_utils';
-import StageComponent from '~/pipelines/components/pipelines_list/stage.vue';
-import eventHub from '~/pipelines/event_hub';
-import { stageReply } from './mock_data';
-
-describe('Pipelines stage component', () => {
- let wrapper;
- let mock;
- let glFeatures;
-
- const defaultProps = {
- stage: {
- status: {
- group: 'success',
- icon: 'status_success',
- title: 'success',
- },
- dropdown_path: 'path.json',
- },
- updateDropdown: false,
- };
-
- const createComponent = (props = {}) => {
- wrapper = mount(StageComponent, {
- attachTo: document.body,
- propsData: {
- ...defaultProps,
- ...props,
- },
- provide: {
- glFeatures,
- },
- });
- };
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- jest.spyOn(eventHub, '$emit');
- glFeatures = {};
- });
-
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
-
- eventHub.$emit.mockRestore();
- mock.restore();
- });
-
- describe('when ci_mini_pipeline_gl_dropdown feature flag is disabled', () => {
- const isDropdownOpen = () => wrapper.classes('show');
-
- describe('default', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('should render a dropdown with the status icon', () => {
- expect(wrapper.attributes('class')).toEqual('dropdown');
- expect(wrapper.find('svg').exists()).toBe(true);
- expect(wrapper.find('button').attributes('data-toggle')).toEqual('dropdown');
- });
- });
-
- describe('with successful request', () => {
- beforeEach(() => {
- mock.onGet('path.json').reply(200, stageReply);
- createComponent();
- });
-
- it('should render the received data and emit `clickedDropdown` event', async () => {
- wrapper.find('button').trigger('click');
-
- await axios.waitForAll();
- expect(wrapper.find('.js-builds-dropdown-container ul').text()).toContain(
- stageReply.latest_statuses[0].name,
- );
-
- expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
- });
- });
-
- it('when request fails should close the dropdown', async () => {
- mock.onGet('path.json').reply(500);
- createComponent();
- wrapper.find({ ref: 'dropdown' }).trigger('click');
-
- expect(isDropdownOpen()).toBe(true);
-
- wrapper.find('button').trigger('click');
- await axios.waitForAll();
-
- expect(isDropdownOpen()).toBe(false);
- });
-
- describe('update endpoint correctly', () => {
- beforeEach(() => {
- const copyStage = { ...stageReply };
- copyStage.latest_statuses[0].name = 'this is the updated content';
- mock.onGet('bar.json').reply(200, copyStage);
- createComponent({
- stage: {
- status: {
- group: 'running',
- icon: 'status_running',
- title: 'running',
- },
- dropdown_path: 'bar.json',
- },
- });
- return axios.waitForAll();
- });
-
- it('should update the stage to request the new endpoint provided', async () => {
- wrapper.find('button').trigger('click');
- await axios.waitForAll();
-
- expect(wrapper.find('.js-builds-dropdown-container ul').text()).toContain(
- 'this is the updated content',
- );
- });
- });
-
- describe('pipelineActionRequestComplete', () => {
- beforeEach(() => {
- mock.onGet('path.json').reply(200, stageReply);
- mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200);
- });
-
- const clickCiAction = async () => {
- wrapper.find('button').trigger('click');
- await axios.waitForAll();
-
- wrapper.find('.js-ci-action').trigger('click');
- await axios.waitForAll();
- };
-
- describe('within pipeline table', () => {
- it('emits `refreshPipelinesTable` event when `pipelineActionRequestComplete` is triggered', async () => {
- createComponent({ type: 'PIPELINES_TABLE' });
-
- await clickCiAction();
-
- expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
- });
- });
-
- describe('in MR widget', () => {
- beforeEach(() => {
- jest.spyOn($.fn, 'dropdown');
- });
-
- it('closes the dropdown when `pipelineActionRequestComplete` is triggered', async () => {
- createComponent();
-
- await clickCiAction();
-
- expect($.fn.dropdown).toHaveBeenCalledWith('toggle');
- });
- });
- });
- });
-
- describe('when ci_mini_pipeline_gl_dropdown feature flag is enabled', () => {
- const findDropdown = () => wrapper.find(GlDropdown);
- const findDropdownToggle = () => wrapper.find('button.gl-dropdown-toggle');
- const findDropdownMenu = () =>
- wrapper.find('[data-testid="mini-pipeline-graph-dropdown-menu-list"]');
- const findCiActionBtn = () => wrapper.find('.js-ci-action');
-
- const openGlDropdown = () => {
- findDropdownToggle().trigger('click');
- return new Promise((resolve) => {
- wrapper.vm.$root.$on('bv::dropdown::show', resolve);
- });
- };
-
- beforeEach(() => {
- glFeatures = { ciMiniPipelineGlDropdown: true };
- });
-
- describe('default', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('should render a dropdown with the status icon', () => {
- expect(findDropdown().exists()).toBe(true);
- expect(findDropdownToggle().classes('gl-dropdown-toggle')).toEqual(true);
- expect(wrapper.find('[data-testid="status_success_borderless-icon"]').exists()).toBe(true);
- });
- });
-
- describe('with successful request', () => {
- beforeEach(() => {
- mock.onGet('path.json').reply(200, stageReply);
- createComponent();
- });
-
- it('should render the received data and emit `clickedDropdown` event', async () => {
- await openGlDropdown();
- await axios.waitForAll();
-
- expect(findDropdownMenu().text()).toContain(stageReply.latest_statuses[0].name);
- expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
- });
- });
-
- it('when request fails should close the dropdown', async () => {
- mock.onGet('path.json').reply(500);
-
- createComponent();
-
- await openGlDropdown();
- await axios.waitForAll();
-
- 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(200, 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 openGlDropdown();
- await axios.waitForAll();
-
- expect(findDropdownMenu().text()).toContain('this is the updated content');
- });
- });
-
- describe('pipelineActionRequestComplete', () => {
- beforeEach(() => {
- mock.onGet('path.json').reply(200, stageReply);
- mock.onPost(`${stageReply.latest_statuses[0].status.action.path}.json`).reply(200);
- });
-
- const clickCiAction = async () => {
- await openGlDropdown();
- await axios.waitForAll();
-
- findCiActionBtn().trigger('click');
- await axios.waitForAll();
- };
-
- describe('within pipeline table', () => {
- beforeEach(() => {
- createComponent({ type: 'PIPELINES_TABLE' });
- });
-
- it('emits `refreshPipelinesTable` event when `pipelineActionRequestComplete` is triggered', async () => {
- await clickCiAction();
-
- expect(eventHub.$emit).toHaveBeenCalledWith('refreshPipelinesTable');
- });
- });
-
- describe('in MR widget', () => {
- beforeEach(() => {
- jest.spyOn($.fn, 'dropdown');
- createComponent();
- });
-
- it('closes the dropdown when `pipelineActionRequestComplete` is triggered', async () => {
- const hidden = jest.fn();
-
- wrapper.vm.$root.$on('bv::dropdown::hide', hidden);
-
- expect(hidden).toHaveBeenCalledTimes(0);
-
- await clickCiAction();
-
- expect(hidden).toHaveBeenCalledTimes(1);
- });
- });
- });
- });
-});
diff --git a/spec/frontend/pipelines/time_ago_spec.js b/spec/frontend/pipelines/time_ago_spec.js
index 55a19ef5165..93aeb049434 100644
--- a/spec/frontend/pipelines/time_ago_spec.js
+++ b/spec/frontend/pipelines/time_ago_spec.js
@@ -8,7 +8,11 @@ describe('Timeago component', () => {
const createComponent = (props = {}) => {
wrapper = shallowMount(TimeAgo, {
propsData: {
- ...props,
+ pipeline: {
+ details: {
+ ...props,
+ },
+ },
},
data() {
return {
@@ -25,10 +29,11 @@ describe('Timeago component', () => {
const duration = () => wrapper.find('.duration');
const finishedAt = () => wrapper.find('.finished-at');
+ const findInProgress = () => wrapper.find('[data-testid="pipeline-in-progress"]');
describe('with duration', () => {
beforeEach(() => {
- createComponent({ duration: 10, finishedTime: '' });
+ createComponent({ duration: 10, finished_at: '' });
});
it('should render duration and timer svg', () => {
@@ -41,7 +46,7 @@ describe('Timeago component', () => {
describe('without duration', () => {
beforeEach(() => {
- createComponent({ duration: 0, finishedTime: '' });
+ createComponent({ duration: 0, finished_at: '' });
});
it('should not render duration and timer svg', () => {
@@ -51,7 +56,7 @@ describe('Timeago component', () => {
describe('with finishedTime', () => {
beforeEach(() => {
- createComponent({ duration: 0, finishedTime: '2017-04-26T12:40:23.277Z' });
+ createComponent({ duration: 0, finished_at: '2017-04-26T12:40:23.277Z' });
});
it('should render time and calendar icon', () => {
@@ -66,11 +71,28 @@ describe('Timeago component', () => {
describe('without finishedTime', () => {
beforeEach(() => {
- createComponent({ duration: 0, finishedTime: '' });
+ createComponent({ duration: 0, finished_at: '' });
});
it('should not render time and calendar icon', () => {
expect(finishedAt().exists()).toBe(false);
});
});
+
+ describe('in progress', () => {
+ it.each`
+ durationTime | finishedAtTime | shouldShow
+ ${10} | ${'2017-04-26T12:40:23.277Z'} | ${false}
+ ${10} | ${''} | ${false}
+ ${0} | ${'2017-04-26T12:40:23.277Z'} | ${false}
+ ${0} | ${''} | ${true}
+ `(
+ 'progress state shown: $shouldShow when pipeline duration is $durationTime and finished_at is $finishedAtTime',
+ ({ durationTime, finishedAtTime, shouldShow }) => {
+ createComponent({ duration: durationTime, finished_at: finishedAtTime });
+
+ expect(findInProgress().exists()).toBe(shouldShow);
+ },
+ );
+ });
});