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/pipeline_tabs_spec.js46
-rw-r--r--spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js37
-rw-r--r--spec/frontend/pipelines/graph/linked_pipeline_spec.js323
-rw-r--r--spec/frontend/pipelines/notification/deprecated_type_keyword_notification_spec.js146
-rw-r--r--spec/frontend/pipelines/pipeline_tabs_spec.js95
-rw-r--r--spec/frontend/pipelines/test_reports/test_case_details_spec.js21
-rw-r--r--spec/frontend/pipelines/test_reports/test_suite_table_spec.js13
7 files changed, 364 insertions, 317 deletions
diff --git a/spec/frontend/pipelines/components/pipeline_tabs_spec.js b/spec/frontend/pipelines/components/pipeline_tabs_spec.js
index 89002ee47a8..e0210307823 100644
--- a/spec/frontend/pipelines/components/pipeline_tabs_spec.js
+++ b/spec/frontend/pipelines/components/pipeline_tabs_spec.js
@@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
+import { GlTab } from '@gitlab/ui';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import PipelineTabs from '~/pipelines/components/pipeline_tabs.vue';
import PipelineGraphWrapper from '~/pipelines/components/graph/graph_component_wrapper.vue';
@@ -21,35 +22,35 @@ describe('The Pipeline Tabs', () => {
const findPipelineApp = () => wrapper.findComponent(PipelineGraphWrapper);
const findTestsApp = () => wrapper.findComponent(TestReports);
+ const findFailedJobsBadge = () => wrapper.findByTestId('failed-builds-counter');
+ const findJobsBadge = () => wrapper.findByTestId('builds-counter');
+
const defaultProvide = {
defaultTabValue: '',
+ failedJobsCount: 1,
+ failedJobsSummary: [],
+ totalJobCount: 10,
};
- const createComponent = (propsData = {}) => {
+ const createComponent = (provide = {}) => {
wrapper = extendedWrapper(
shallowMount(PipelineTabs, {
- propsData,
provide: {
...defaultProvide,
+ ...provide,
},
stubs: {
- JobsApp: { template: '<div class="jobs" />' },
+ GlTab,
TestReports: { template: '<div id="tests" />' },
},
}),
);
};
- beforeEach(() => {
- createComponent();
- });
-
afterEach(() => {
wrapper.destroy();
});
- // The failed jobs MUST be removed from here and tested individually once
- // the logic for the tab is implemented.
describe('Tabs', () => {
it.each`
tabName | tabComponent | appComponent
@@ -58,9 +59,34 @@ describe('The Pipeline Tabs', () => {
${'Jobs'} | ${findJobsTab} | ${findJobsApp}
${'Failed Jobs'} | ${findFailedJobsTab} | ${findFailedJobsApp}
${'Tests'} | ${findTestsTab} | ${findTestsApp}
- `('shows $tabName tab and its associated component', ({ appComponent, tabComponent }) => {
+ `('shows $tabName tab with its associated component', ({ appComponent, tabComponent }) => {
+ createComponent();
+
expect(tabComponent().exists()).toBe(true);
expect(appComponent().exists()).toBe(true);
});
+
+ describe('with no failed jobs', () => {
+ beforeEach(() => {
+ createComponent({ failedJobsCount: 0 });
+ });
+
+ it('hides the failed jobs tab', () => {
+ expect(findFailedJobsTab().exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('Tabs badges', () => {
+ it.each`
+ tabName | badgeComponent | badgeText
+ ${'Jobs'} | ${findJobsBadge} | ${String(defaultProvide.totalJobCount)}
+ ${'Failed Jobs'} | ${findFailedJobsBadge} | ${String(defaultProvide.failedJobsCount)}
+ `('shows badge for $tabName with the correct text', ({ badgeComponent, badgeText }) => {
+ createComponent();
+
+ expect(badgeComponent().exists()).toBe(true);
+ expect(badgeComponent().text()).toBe(badgeText);
+ });
});
});
diff --git a/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js b/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js
index 6d0e99ff63e..1ff32b03344 100644
--- a/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js
+++ b/spec/frontend/pipelines/components/pipelines_list/pipeline_stage_spec.js
@@ -5,6 +5,7 @@ import CiIcon from '~/vue_shared/components/ci_icon.vue';
import axios from '~/lib/utils/axios_utils';
import PipelineStage from '~/pipelines/components/pipelines_list/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';
@@ -55,7 +56,10 @@ describe('Pipelines stage component', () => {
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 = () => {
findDropdownToggle().trigger('click');
@@ -64,6 +68,27 @@ describe('Pipelines stage component', () => {
});
};
+ describe('loading state', () => {
+ beforeEach(async () => {
+ createComponent({ updateDropdown: true });
+
+ mock.onGet(dropdownPath).reply(200, stageReply);
+
+ await openStageDropdown();
+ });
+
+ it('displays loading state while jobs are being fetched', () => {
+ expect(findLoadingState().exists()).toBe(true);
+ expect(findLoadingState().text()).toBe(PipelineStage.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();
@@ -78,6 +103,17 @@ describe('Pipelines stage component', () => {
expect(findDropdownToggle().exists()).toBe(true);
expect(findCiIcon().exists()).toBe(true);
});
+
+ it('should render a borderless ci-icon', () => {
+ expect(findCiIcon().exists()).toBe(true);
+ expect(findCiIcon().props('isBorderless')).toBe(true);
+ expect(findCiIcon().classes('borderless')).toBe(true);
+ });
+
+ it('should render a ci-icon with a custom border class', () => {
+ expect(findCiIcon().exists()).toBe(true);
+ expect(findCiIcon().classes('gl-border')).toBe(true);
+ });
});
describe('when update dropdown is changed', () => {
@@ -97,6 +133,7 @@ describe('Pipelines stage component', () => {
it('should render the received data and emit `clickedDropdown` event', async () => {
expect(findDropdownMenu().text()).toContain(stageReply.latest_statuses[0].name);
+ expect(findDropdownMenuTitle().text()).toContain(stageReply.name);
expect(eventHub.$emit).toHaveBeenCalledWith('clickedDropdown');
});
diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
index 06fd970778c..fd97c2dbe77 100644
--- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js
@@ -47,17 +47,12 @@ describe('Linked pipeline', () => {
const findPipelineLink = () => wrapper.findByTestId('pipelineLink');
const findRetryButton = () => wrapper.findByLabelText('Retry downstream pipeline');
- const createWrapper = ({ propsData, downstreamRetryAction = false }) => {
+ const createWrapper = ({ propsData }) => {
const mockApollo = createMockApollo();
wrapper = extendedWrapper(
mount(LinkedPipelineComponent, {
propsData,
- provide: {
- glFeatures: {
- downstreamRetryAction,
- },
- },
apolloProvider: mockApollo,
}),
);
@@ -164,197 +159,188 @@ describe('Linked pipeline', () => {
});
describe('action button', () => {
- describe('with the `downstream_retry_action` flag on', () => {
- describe('with permissions', () => {
- describe('on an upstream', () => {
- describe('when retryable', () => {
- beforeEach(() => {
- const retryablePipeline = {
- ...upstreamProps,
- pipeline: { ...mockPipeline, retryable: true },
- };
-
- createWrapper({ propsData: retryablePipeline, downstreamRetryAction: true });
- });
+ describe('with permissions', () => {
+ describe('on an upstream', () => {
+ describe('when retryable', () => {
+ beforeEach(() => {
+ const retryablePipeline = {
+ ...upstreamProps,
+ pipeline: { ...mockPipeline, retryable: true },
+ };
+
+ createWrapper({ propsData: retryablePipeline });
+ });
- it('does not show the retry or cancel button', () => {
- expect(findCancelButton().exists()).toBe(false);
- expect(findRetryButton().exists()).toBe(false);
- });
+ it('does not show the retry or cancel button', () => {
+ expect(findCancelButton().exists()).toBe(false);
+ expect(findRetryButton().exists()).toBe(false);
});
});
+ });
- describe('on a downstream', () => {
- describe('when retryable', () => {
- beforeEach(() => {
- const retryablePipeline = {
- ...downstreamProps,
- pipeline: { ...mockPipeline, retryable: true },
- };
+ describe('on a downstream', () => {
+ describe('when retryable', () => {
+ beforeEach(() => {
+ const retryablePipeline = {
+ ...downstreamProps,
+ pipeline: { ...mockPipeline, retryable: true },
+ };
- createWrapper({ propsData: retryablePipeline, downstreamRetryAction: true });
- });
+ createWrapper({ propsData: retryablePipeline });
+ });
- it('shows only the retry button', () => {
- expect(findCancelButton().exists()).toBe(false);
- expect(findRetryButton().exists()).toBe(true);
- });
+ it('shows only the retry button', () => {
+ expect(findCancelButton().exists()).toBe(false);
+ expect(findRetryButton().exists()).toBe(true);
+ });
- it('hides the card tooltip when the action button tooltip is hovered', async () => {
- expect(findCardTooltip().exists()).toBe(true);
+ it.each`
+ findElement | name
+ ${findRetryButton} | ${'retry button'}
+ ${findExpandButton} | ${'expand button'}
+ `('hides the card tooltip when $name is hovered', async ({ findElement }) => {
+ expect(findCardTooltip().exists()).toBe(true);
- await findRetryButton().trigger('mouseover');
+ await findElement().trigger('mouseover');
- expect(findCardTooltip().exists()).toBe(false);
- });
+ expect(findCardTooltip().exists()).toBe(false);
+ });
- describe('and the retry button is clicked', () => {
- describe('on success', () => {
- beforeEach(async () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
- jest.spyOn(wrapper.vm, '$emit');
- await findRetryButton().trigger('click');
- });
+ describe('and the retry button is clicked', () => {
+ describe('on success', () => {
+ beforeEach(async () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
+ jest.spyOn(wrapper.vm, '$emit');
+ await findRetryButton().trigger('click');
+ });
- it('calls the retry mutation ', () => {
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: RetryPipelineMutation,
- variables: {
- id: convertToGraphQLId(PIPELINE_GRAPHQL_TYPE, mockPipeline.id),
- },
- });
+ it('calls the retry mutation ', () => {
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: RetryPipelineMutation,
+ variables: {
+ id: convertToGraphQLId(PIPELINE_GRAPHQL_TYPE, mockPipeline.id),
+ },
});
+ });
- it('emits the refreshPipelineGraph event', () => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('refreshPipelineGraph');
- });
+ it('emits the refreshPipelineGraph event', () => {
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('refreshPipelineGraph');
});
+ });
- describe('on failure', () => {
- beforeEach(async () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue({ errors: [] });
- jest.spyOn(wrapper.vm, '$emit');
- await findRetryButton().trigger('click');
- });
+ describe('on failure', () => {
+ beforeEach(async () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue({ errors: [] });
+ jest.spyOn(wrapper.vm, '$emit');
+ await findRetryButton().trigger('click');
+ });
- it('emits an error event', () => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('error', {
- type: ACTION_FAILURE,
- });
+ it('emits an error event', () => {
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('error', {
+ type: ACTION_FAILURE,
});
});
});
});
+ });
- describe('when cancelable', () => {
- beforeEach(() => {
- const cancelablePipeline = {
- ...downstreamProps,
- pipeline: { ...mockPipeline, cancelable: true },
- };
+ describe('when cancelable', () => {
+ beforeEach(() => {
+ const cancelablePipeline = {
+ ...downstreamProps,
+ pipeline: { ...mockPipeline, cancelable: true },
+ };
- createWrapper({ propsData: cancelablePipeline, downstreamRetryAction: true });
- });
+ createWrapper({ propsData: cancelablePipeline });
+ });
- it('shows only the cancel button ', () => {
- expect(findCancelButton().exists()).toBe(true);
- expect(findRetryButton().exists()).toBe(false);
- });
+ it('shows only the cancel button ', () => {
+ expect(findCancelButton().exists()).toBe(true);
+ expect(findRetryButton().exists()).toBe(false);
+ });
- it('hides the card tooltip when the action button tooltip is hovered', async () => {
- expect(findCardTooltip().exists()).toBe(true);
+ it.each`
+ findElement | name
+ ${findCancelButton} | ${'cancel button'}
+ ${findExpandButton} | ${'expand button'}
+ `('hides the card tooltip when $name is hovered', async ({ findElement }) => {
+ expect(findCardTooltip().exists()).toBe(true);
- await findCancelButton().trigger('mouseover');
+ await findElement().trigger('mouseover');
- expect(findCardTooltip().exists()).toBe(false);
- });
+ expect(findCardTooltip().exists()).toBe(false);
+ });
- describe('and the cancel button is clicked', () => {
- describe('on success', () => {
- beforeEach(async () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
- jest.spyOn(wrapper.vm, '$emit');
- await findCancelButton().trigger('click');
- });
+ describe('and the cancel button is clicked', () => {
+ describe('on success', () => {
+ beforeEach(async () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
+ jest.spyOn(wrapper.vm, '$emit');
+ await findCancelButton().trigger('click');
+ });
- it('calls the cancel mutation', () => {
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
- expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
- mutation: CancelPipelineMutation,
- variables: {
- id: convertToGraphQLId(PIPELINE_GRAPHQL_TYPE, mockPipeline.id),
- },
- });
- });
- it('emits the refreshPipelineGraph event', () => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('refreshPipelineGraph');
+ it('calls the cancel mutation', () => {
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
+ expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
+ mutation: CancelPipelineMutation,
+ variables: {
+ id: convertToGraphQLId(PIPELINE_GRAPHQL_TYPE, mockPipeline.id),
+ },
});
});
- describe('on failure', () => {
- beforeEach(async () => {
- jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue({ errors: [] });
- jest.spyOn(wrapper.vm, '$emit');
- await findCancelButton().trigger('click');
- });
- it('emits an error event', () => {
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('error', {
- type: ACTION_FAILURE,
- });
+ it('emits the refreshPipelineGraph event', () => {
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('refreshPipelineGraph');
+ });
+ });
+ describe('on failure', () => {
+ beforeEach(async () => {
+ jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue({ errors: [] });
+ jest.spyOn(wrapper.vm, '$emit');
+ await findCancelButton().trigger('click');
+ });
+ it('emits an error event', () => {
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('error', {
+ type: ACTION_FAILURE,
});
});
});
});
+ });
- describe('when both cancellable and retryable', () => {
- beforeEach(() => {
- const pipelineWithTwoActions = {
- ...downstreamProps,
- pipeline: { ...mockPipeline, cancelable: true, retryable: true },
- };
-
- createWrapper({ propsData: pipelineWithTwoActions, downstreamRetryAction: true });
- });
+ describe('when both cancellable and retryable', () => {
+ beforeEach(() => {
+ const pipelineWithTwoActions = {
+ ...downstreamProps,
+ pipeline: { ...mockPipeline, cancelable: true, retryable: true },
+ };
- it('only shows the cancel button', () => {
- expect(findRetryButton().exists()).toBe(false);
- expect(findCancelButton().exists()).toBe(true);
- });
+ createWrapper({ propsData: pipelineWithTwoActions });
});
- });
- });
-
- describe('without permissions', () => {
- beforeEach(() => {
- const pipelineWithTwoActions = {
- ...downstreamProps,
- pipeline: {
- ...mockPipeline,
- cancelable: true,
- retryable: true,
- userPermissions: { updatePipeline: false },
- },
- };
-
- createWrapper({ propsData: pipelineWithTwoActions });
- });
- it('does not show any action button', () => {
- expect(findRetryButton().exists()).toBe(false);
- expect(findCancelButton().exists()).toBe(false);
+ it('only shows the cancel button', () => {
+ expect(findRetryButton().exists()).toBe(false);
+ expect(findCancelButton().exists()).toBe(true);
+ });
});
});
});
- describe('with the `downstream_retry_action` flag off', () => {
+ describe('without permissions', () => {
beforeEach(() => {
const pipelineWithTwoActions = {
...downstreamProps,
- pipeline: { ...mockPipeline, cancelable: true, retryable: true },
+ pipeline: {
+ ...mockPipeline,
+ cancelable: true,
+ retryable: true,
+ userPermissions: { updatePipeline: false },
+ },
};
createWrapper({ propsData: pipelineWithTwoActions });
});
+
it('does not show any action button', () => {
expect(findRetryButton().exists()).toBe(false);
expect(findCancelButton().exists()).toBe(false);
@@ -365,19 +351,44 @@ describe('Linked pipeline', () => {
describe('expand button', () => {
it.each`
- pipelineType | anglePosition | buttonBorderClasses | expanded
- ${downstreamProps} | ${'angle-right'} | ${'gl-border-l-0!'} | ${false}
- ${downstreamProps} | ${'angle-left'} | ${'gl-border-l-0!'} | ${true}
- ${upstreamProps} | ${'angle-left'} | ${'gl-border-r-0!'} | ${false}
- ${upstreamProps} | ${'angle-right'} | ${'gl-border-r-0!'} | ${true}
+ pipelineType | chevronPosition | buttonBorderClasses | expanded
+ ${downstreamProps} | ${'chevron-lg-right'} | ${'gl-border-l-0!'} | ${false}
+ ${downstreamProps} | ${'chevron-lg-left'} | ${'gl-border-l-0!'} | ${true}
+ ${upstreamProps} | ${'chevron-lg-left'} | ${'gl-border-r-0!'} | ${false}
+ ${upstreamProps} | ${'chevron-lg-right'} | ${'gl-border-r-0!'} | ${true}
`(
- '$pipelineType.columnTitle pipeline button icon should be $anglePosition with $buttonBorderClasses if expanded state is $expanded',
- ({ pipelineType, anglePosition, buttonBorderClasses, expanded }) => {
+ '$pipelineType.columnTitle pipeline button icon should be $chevronPosition with $buttonBorderClasses if expanded state is $expanded',
+ ({ pipelineType, chevronPosition, buttonBorderClasses, expanded }) => {
createWrapper({ propsData: { ...pipelineType, expanded } });
- expect(findExpandButton().props('icon')).toBe(anglePosition);
+ expect(findExpandButton().props('icon')).toBe(chevronPosition);
expect(findExpandButton().classes()).toContain(buttonBorderClasses);
},
);
+
+ describe('shadow border', () => {
+ beforeEach(() => {
+ createWrapper({ propsData: downstreamProps });
+ });
+
+ it.each`
+ activateEventName | deactivateEventName
+ ${'mouseover'} | ${'mouseout'}
+ ${'focus'} | ${'blur'}
+ `(
+ 'applies the class on $activateEventName and removes it on $deactivateEventName ',
+ async ({ activateEventName, deactivateEventName }) => {
+ const shadowClass = 'gl-shadow-none!';
+
+ expect(findExpandButton().classes()).toContain(shadowClass);
+
+ await findExpandButton().vm.$emit(activateEventName);
+ expect(findExpandButton().classes()).not.toContain(shadowClass);
+
+ await findExpandButton().vm.$emit(deactivateEventName);
+ expect(findExpandButton().classes()).toContain(shadowClass);
+ },
+ );
+ });
});
describe('when isLoading is true', () => {
diff --git a/spec/frontend/pipelines/notification/deprecated_type_keyword_notification_spec.js b/spec/frontend/pipelines/notification/deprecated_type_keyword_notification_spec.js
deleted file mode 100644
index f626652a944..00000000000
--- a/spec/frontend/pipelines/notification/deprecated_type_keyword_notification_spec.js
+++ /dev/null
@@ -1,146 +0,0 @@
-import VueApollo from 'vue-apollo';
-import { createLocalVue, shallowMount } from '@vue/test-utils';
-import { GlAlert, GlSprintf } from '@gitlab/ui';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import waitForPromises from 'helpers/wait_for_promises';
-import DeprecatedTypeKeywordNotification from '~/pipelines/components/notification/deprecated_type_keyword_notification.vue';
-import getPipelineWarnings from '~/pipelines/graphql/queries/get_pipeline_warnings.query.graphql';
-import {
- mockWarningsWithoutDeprecation,
- mockWarningsRootType,
- mockWarningsType,
- mockWarningsTypesAll,
-} from './mock_data';
-
-const defaultProvide = {
- deprecatedKeywordsDocPath: '/help/ci/yaml/index.md#deprecated-keywords',
- fullPath: '/namespace/my-project',
- pipelineIid: 4,
-};
-
-let wrapper;
-
-const mockWarnings = jest.fn();
-
-const createComponent = ({ isLoading = false, options = {} } = {}) => {
- return shallowMount(DeprecatedTypeKeywordNotification, {
- stubs: {
- GlSprintf,
- },
- provide: {
- ...defaultProvide,
- },
- mocks: {
- $apollo: {
- queries: {
- warnings: {
- loading: isLoading,
- },
- },
- },
- },
- ...options,
- });
-};
-
-const createComponentWithApollo = () => {
- const localVue = createLocalVue();
- localVue.use(VueApollo);
-
- const handlers = [[getPipelineWarnings, mockWarnings]];
- const mockApollo = createMockApollo(handlers);
-
- return createComponent({
- options: {
- localVue,
- apolloProvider: mockApollo,
- mocks: {},
- },
- });
-};
-
-const findAlert = () => wrapper.findComponent(GlAlert);
-const findAlertItems = () => findAlert().findAll('li');
-
-afterEach(() => {
- wrapper.destroy();
-});
-
-describe('Deprecated keyword notification', () => {
- describe('while loading the pipeline warnings', () => {
- beforeEach(() => {
- wrapper = createComponent({ isLoading: true });
- });
-
- it('does not display the notification', () => {
- expect(findAlert().exists()).toBe(false);
- });
- });
-
- describe('if there is an error in the query', () => {
- beforeEach(async () => {
- mockWarnings.mockResolvedValue({ errors: ['It didnt work'] });
- wrapper = createComponentWithApollo();
- await waitForPromises();
- });
-
- it('does not display the notification', () => {
- expect(findAlert().exists()).toBe(false);
- });
- });
-
- describe('with a valid query result', () => {
- describe('if there are no deprecation warnings', () => {
- beforeEach(async () => {
- mockWarnings.mockResolvedValue(mockWarningsWithoutDeprecation);
- wrapper = createComponentWithApollo();
- await waitForPromises();
- });
- it('does not show the notification', () => {
- expect(findAlert().exists()).toBe(false);
- });
- });
-
- describe('with a root type deprecation message', () => {
- beforeEach(async () => {
- mockWarnings.mockResolvedValue(mockWarningsRootType);
- wrapper = createComponentWithApollo();
- await waitForPromises();
- });
- it('shows the notification with one item', () => {
- expect(findAlert().exists()).toBe(true);
- expect(findAlertItems()).toHaveLength(1);
- expect(findAlertItems().at(0).text()).toContain('types');
- });
- });
-
- describe('with a job type deprecation message', () => {
- beforeEach(async () => {
- mockWarnings.mockResolvedValue(mockWarningsType);
- wrapper = createComponentWithApollo();
- await waitForPromises();
- });
- it('shows the notification with one item', () => {
- expect(findAlert().exists()).toBe(true);
- expect(findAlertItems()).toHaveLength(1);
- expect(findAlertItems().at(0).text()).toContain('type');
- expect(findAlertItems().at(0).text()).not.toContain('types');
- });
- });
-
- describe('with both the root types and job type deprecation message', () => {
- beforeEach(async () => {
- mockWarnings.mockResolvedValue(mockWarningsTypesAll);
- wrapper = createComponentWithApollo();
- await waitForPromises();
- });
- it('shows the notification with two items', () => {
- expect(findAlert().exists()).toBe(true);
- expect(findAlertItems()).toHaveLength(2);
- expect(findAlertItems().at(0).text()).toContain('types');
- expect(findAlertItems().at(1).text()).toContain('type');
- expect(findAlertItems().at(1).text()).not.toContain('types');
- });
- });
- });
-});
diff --git a/spec/frontend/pipelines/pipeline_tabs_spec.js b/spec/frontend/pipelines/pipeline_tabs_spec.js
new file mode 100644
index 00000000000..b184ce31d20
--- /dev/null
+++ b/spec/frontend/pipelines/pipeline_tabs_spec.js
@@ -0,0 +1,95 @@
+import { createAppOptions, createPipelineTabs } from '~/pipelines/pipeline_tabs';
+import { updateHistory } from '~/lib/utils/url_utility';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ removeParams: () => 'gitlab.com',
+ updateHistory: jest.fn(),
+ joinPaths: () => {},
+ setUrlFragment: () => {},
+}));
+
+jest.mock('~/pipelines/utils', () => ({
+ getPipelineDefaultTab: () => '',
+}));
+
+describe('~/pipelines/pipeline_tabs.js', () => {
+ describe('createAppOptions', () => {
+ const SELECTOR = 'SELECTOR';
+
+ let el;
+
+ const createElement = () => {
+ el = document.createElement('div');
+ el.id = SELECTOR;
+ el.dataset.canGenerateCodequalityReports = 'true';
+ el.dataset.codequalityReportDownloadPath = 'codequalityReportDownloadPath';
+ el.dataset.downloadablePathForReportType = 'downloadablePathForReportType';
+ el.dataset.exposeSecurityDashboard = 'true';
+ el.dataset.exposeLicenseScanningData = 'true';
+ el.dataset.failedJobsCount = 1;
+ el.dataset.failedJobsSummary = '[]';
+ el.dataset.graphqlResourceEtag = 'graphqlResourceEtag';
+ el.dataset.pipelineIid = '123';
+ el.dataset.pipelineProjectPath = 'pipelineProjectPath';
+
+ document.body.appendChild(el);
+ };
+
+ afterEach(() => {
+ el = null;
+ });
+
+ it("extracts the properties from the element's dataset", () => {
+ createElement();
+ const options = createAppOptions(`#${SELECTOR}`, null);
+
+ expect(options).toMatchObject({
+ el,
+ provide: {
+ canGenerateCodequalityReports: true,
+ codequalityReportDownloadPath: 'codequalityReportDownloadPath',
+ downloadablePathForReportType: 'downloadablePathForReportType',
+ exposeSecurityDashboard: true,
+ exposeLicenseScanningData: true,
+ failedJobsCount: '1',
+ failedJobsSummary: [],
+ graphqlResourceEtag: 'graphqlResourceEtag',
+ pipelineIid: '123',
+ pipelineProjectPath: 'pipelineProjectPath',
+ },
+ });
+ });
+
+ it('returns `null` if el does not exist', () => {
+ expect(createAppOptions('foo', null)).toBe(null);
+ });
+ });
+
+ describe('createPipelineTabs', () => {
+ const title = 'Pipeline Tabs';
+
+ beforeAll(() => {
+ document.title = title;
+ });
+
+ afterAll(() => {
+ document.title = '';
+ });
+
+ it('calls `updateHistory` with correct params', () => {
+ createPipelineTabs({});
+
+ expect(updateHistory).toHaveBeenCalledWith({
+ title,
+ url: 'gitlab.com',
+ replace: true,
+ });
+ });
+
+ it("returns early if options aren't provided", () => {
+ createPipelineTabs();
+
+ expect(updateHistory).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/pipelines/test_reports/test_case_details_spec.js b/spec/frontend/pipelines/test_reports/test_case_details_spec.js
index 4b33c1522a5..29c07e5e9f8 100644
--- a/spec/frontend/pipelines/test_reports/test_case_details_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_case_details_spec.js
@@ -1,4 +1,4 @@
-import { GlModal } from '@gitlab/ui';
+import { GlModal, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import TestCaseDetails from '~/pipelines/components/test_reports/test_case_details.vue';
@@ -9,6 +9,8 @@ describe('Test case details', () => {
const defaultTestCase = {
classname: 'spec.test_spec',
name: 'Test#something cool',
+ file: '~/index.js',
+ filePath: '/src/javascripts/index.js',
formattedTime: '10.04ms',
recent_failures: {
count: 2,
@@ -19,6 +21,8 @@ describe('Test case details', () => {
const findModal = () => wrapper.findComponent(GlModal);
const findName = () => wrapper.findByTestId('test-case-name');
+ const findFile = () => wrapper.findByTestId('test-case-file');
+ const findFileLink = () => wrapper.findComponent(GlLink);
const findDuration = () => wrapper.findByTestId('test-case-duration');
const findRecentFailures = () => wrapper.findByTestId('test-case-recent-failures');
const findAttachmentUrl = () => wrapper.findByTestId('test-case-attachment-url');
@@ -57,11 +61,26 @@ describe('Test case details', () => {
expect(findName().text()).toBe(defaultTestCase.name);
});
+ it('renders the test case file', () => {
+ expect(findFile().text()).toBe(defaultTestCase.file);
+ expect(findFileLink().attributes('href')).toBe(defaultTestCase.filePath);
+ });
+
it('renders the test case duration', () => {
expect(findDuration().text()).toBe(defaultTestCase.formattedTime);
});
});
+ describe('when test case has execution time instead of formatted time', () => {
+ beforeEach(() => {
+ createComponent({ ...defaultTestCase, formattedTime: null, execution_time: 17 });
+ });
+
+ it('renders the test case duration', () => {
+ expect(findDuration().text()).toBe('17 s');
+ });
+ });
+
describe('when test case has recent failures', () => {
describe('has only 1 recent failure', () => {
it('renders the recent failure', () => {
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 dc72fa31ace..25650b24705 100644
--- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
@@ -1,9 +1,9 @@
-import { GlButton, GlFriendlyWrap, GlLink, GlPagination } from '@gitlab/ui';
+import { GlButton, GlFriendlyWrap, GlLink, GlPagination, GlEmptyState } from '@gitlab/ui';
import Vue from 'vue';
import Vuex from 'vuex';
import testReports from 'test_fixtures/pipelines/test_report.json';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import SuiteTable from '~/pipelines/components/test_reports/test_suite_table.vue';
+import SuiteTable, { i18n } from '~/pipelines/components/test_reports/test_suite_table.vue';
import { TestStatus } from '~/pipelines/constants';
import * as getters from '~/pipelines/stores/test_reports/getters';
import { formatFilePath } from '~/pipelines/stores/test_reports/utils';
@@ -26,6 +26,7 @@ describe('Test reports suite table', () => {
const noCasesMessage = () => wrapper.findByTestId('no-test-cases');
const artifactsExpiredMessage = () => wrapper.findByTestId('artifacts-expired');
+ const artifactsExpiredEmptyState = () => wrapper.find(GlEmptyState);
const allCaseRows = () => wrapper.findAllByTestId('test-case-row');
const findCaseRowAtIndex = (index) => wrapper.findAllByTestId('test-case-row').at(index);
const findLinkForRow = (row) => row.find(GlLink);
@@ -65,11 +66,15 @@ describe('Test reports suite table', () => {
expect(artifactsExpiredMessage().exists()).toBe(false);
});
- it('should render a message when artifacts have expired', () => {
+ it('should render an empty state when artifacts have expired', () => {
createComponent({ suite: [], errorMessage: ARTIFACTS_EXPIRED_ERROR_MESSAGE });
+ const emptyState = artifactsExpiredEmptyState();
- expect(noCasesMessage().exists()).toBe(true);
+ expect(noCasesMessage().exists()).toBe(false);
expect(artifactsExpiredMessage().exists()).toBe(true);
+
+ expect(emptyState.exists()).toBe(true);
+ expect(emptyState.props('title')).toBe(i18n.expiredArtifactsTitle);
});
describe('when a test suite is supplied', () => {