diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-20 14:10:13 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-20 14:10:13 +0300 |
commit | 0ea3fcec397b69815975647f5e2aa5fe944a8486 (patch) | |
tree | 7979381b89d26011bcf9bdc989a40fcc2f1ed4ff /spec/frontend/pipelines | |
parent | 72123183a20411a36d607d70b12d57c484394c8e (diff) |
Add latest changes from gitlab-org/gitlab@15-1-stable-eev15.1.0-rc42
Diffstat (limited to 'spec/frontend/pipelines')
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', () => { |