diff options
Diffstat (limited to 'spec/frontend/pipelines')
18 files changed, 539 insertions, 88 deletions
diff --git a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js index c5b7318d3af..8a6586a7d7d 100644 --- a/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js +++ b/spec/frontend/pipelines/components/pipelines_filtered_search_spec.js @@ -47,9 +47,6 @@ describe('Pipelines filtered search', () => { }); it('displays UI elements', () => { - expect(wrapper.isVueInstance()).toBe(true); - expect(wrapper.isEmpty()).toBe(false); - expect(findFilteredSearch().exists()).toBe(true); }); diff --git a/spec/frontend/pipelines/graph/graph_component_spec.js b/spec/frontend/pipelines/graph/graph_component_spec.js index 1389649abea..d977db58a0e 100644 --- a/spec/frontend/pipelines/graph/graph_component_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_spec.js @@ -16,6 +16,9 @@ describe('graph component', () => { let wrapper; + const findExpandPipelineBtn = () => wrapper.find('[data-testid="expandPipelineButton"]'); + const findAllExpandPipelineBtns = () => wrapper.findAll('[data-testid="expandPipelineButton"]'); + beforeEach(() => { setHTMLFixture('<div class="layout-page"></div>'); }); @@ -167,7 +170,7 @@ describe('graph component', () => { describe('triggered by', () => { describe('on click', () => { it('should emit `onClickTriggeredBy` when triggered by linked pipeline is clicked', () => { - const btnWrapper = wrapper.find('.linked-pipeline-content'); + const btnWrapper = findExpandPipelineBtn(); btnWrapper.trigger('click'); @@ -213,7 +216,7 @@ describe('graph component', () => { ), }); - const btnWrappers = wrapper.findAll('.linked-pipeline-content'); + const btnWrappers = findAllExpandPipelineBtns(); const downstreamBtnWrapper = btnWrappers.at(btnWrappers.length - 1); downstreamBtnWrapper.trigger('click'); diff --git a/spec/frontend/pipelines/graph/job_item_spec.js b/spec/frontend/pipelines/graph/job_item_spec.js index 2c5e7a1f6e9..e844cbc5bf8 100644 --- a/spec/frontend/pipelines/graph/job_item_spec.js +++ b/spec/frontend/pipelines/graph/job_item_spec.js @@ -6,6 +6,7 @@ describe('pipeline graph job item', () => { let wrapper; const findJobWithoutLink = () => wrapper.find('[data-testid="job-without-link"]'); + const findJobWithLink = () => wrapper.find('[data-testid="job-with-link"]'); const createWrapper = propsData => { wrapper = mount(JobItem, { @@ -13,6 +14,7 @@ describe('pipeline graph job item', () => { }); }; + const triggerActiveClass = 'gl-shadow-x0-y0-b3-s1-blue-500'; const delayedJobFixture = getJSONFixture('jobs/delayed.json'); const mockJob = { id: 4256, @@ -33,6 +35,18 @@ describe('pipeline graph job item', () => { }, }, }; + const mockJobWithoutDetails = { + id: 4257, + name: 'job_without_details', + status: { + icon: 'status_success', + text: 'passed', + label: 'passed', + group: 'success', + details_path: '/root/ci-mock/builds/4257', + has_details: false, + }, + }; afterEach(() => { wrapper.destroy(); @@ -47,7 +61,7 @@ describe('pipeline graph job item', () => { expect(link.attributes('href')).toBe(mockJob.status.details_path); - expect(link.attributes('title')).toEqual(`${mockJob.name} - ${mockJob.status.label}`); + expect(link.attributes('title')).toBe(`${mockJob.name} - ${mockJob.status.label}`); expect(wrapper.find('.ci-status-icon-success').exists()).toBe(true); @@ -61,18 +75,7 @@ describe('pipeline graph job item', () => { describe('name without link', () => { beforeEach(() => { createWrapper({ - job: { - id: 4257, - name: 'test', - status: { - icon: 'status_success', - text: 'passed', - label: 'passed', - group: 'success', - details_path: '/root/ci-mock/builds/4257', - has_details: false, - }, - }, + job: mockJobWithoutDetails, cssClassJobName: 'css-class-job-name', jobHovered: 'test', }); @@ -82,11 +85,10 @@ describe('pipeline graph job item', () => { expect(wrapper.find('.ci-status-icon-success').exists()).toBe(true); expect(wrapper.find('a').exists()).toBe(false); - expect(trimText(wrapper.find('.ci-status-text').text())).toEqual(mockJob.name); + expect(trimText(wrapper.find('.ci-status-text').text())).toBe(mockJobWithoutDetails.name); }); it('should apply hover class and provided class name', () => { - expect(findJobWithoutLink().classes()).toContain('gl-inset-border-1-blue-500'); expect(findJobWithoutLink().classes()).toContain('css-class-job-name'); }); }); @@ -137,9 +139,7 @@ describe('pipeline graph job item', () => { }, }); - expect(wrapper.find('.js-job-component-tooltip').attributes('title')).toEqual( - 'test - success', - ); + expect(wrapper.find('.js-job-component-tooltip').attributes('title')).toBe('test - success'); }); }); @@ -149,9 +149,39 @@ describe('pipeline graph job item', () => { job: delayedJobFixture, }); - expect(wrapper.find('.js-pipeline-graph-job-link').attributes('title')).toEqual( + expect(findJobWithLink().attributes('title')).toBe( `delayed job - delayed manual action (${wrapper.vm.remainingTime})`, ); }); }); + + describe('trigger job highlighting', () => { + it.each` + job | jobName | expanded | link + ${mockJob} | ${mockJob.name} | ${true} | ${true} + ${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${true} | ${false} + `( + `trigger job should stay highlighted when downstream is expanded`, + ({ job, jobName, expanded, link }) => { + createWrapper({ job, pipelineExpanded: { jobName, expanded } }); + const findJobEl = link ? findJobWithLink : findJobWithoutLink; + + expect(findJobEl().classes()).toContain(triggerActiveClass); + }, + ); + + it.each` + job | jobName | expanded | link + ${mockJob} | ${mockJob.name} | ${false} | ${true} + ${mockJobWithoutDetails} | ${mockJobWithoutDetails.name} | ${false} | ${false} + `( + `trigger job should not be highlighted when downstream is not expanded`, + ({ job, jobName, expanded, link }) => { + createWrapper({ job, pipelineExpanded: { jobName, expanded } }); + const findJobEl = link ? findJobWithLink : findJobWithoutLink; + + expect(findJobEl().classes()).not.toContain(triggerActiveClass); + }, + ); + }); }); diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js index 59121c54ff3..8e65f0d4f71 100644 --- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js +++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js @@ -1,5 +1,5 @@ import { mount } from '@vue/test-utils'; -import { GlButton } from '@gitlab/ui'; +import { GlButton, GlLoadingIcon } from '@gitlab/ui'; import LinkedPipelineComponent from '~/pipelines/components/graph/linked_pipeline.vue'; import CiStatus from '~/vue_shared/components/ci_icon.vue'; @@ -16,10 +16,18 @@ describe('Linked pipeline', () => { const findButton = () => wrapper.find(GlButton); const findPipelineLabel = () => wrapper.find('[data-testid="downstream-pipeline-label"]'); const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' }); + const findLoadingIcon = () => wrapper.find(GlLoadingIcon); + const findPipelineLink = () => wrapper.find('[data-testid="pipelineLink"]'); + const findExpandButton = () => wrapper.find('[data-testid="expandPipelineButton"]'); - const createWrapper = propsData => { + const createWrapper = (propsData, data = []) => { wrapper = mount(LinkedPipelineComponent, { propsData, + data() { + return { + ...data, + }; + }, }); }; @@ -39,7 +47,7 @@ describe('Linked pipeline', () => { }); it('should render a list item as the containing element', () => { - expect(wrapper.is('li')).toBe(true); + expect(wrapper.element.tagName).toBe('LI'); }); it('should render a button', () => { @@ -76,7 +84,7 @@ describe('Linked pipeline', () => { }); it('should render the tooltip text as the title attribute', () => { - const titleAttr = findButton().attributes('title'); + const titleAttr = findLinkedPipeline().attributes('title'); expect(titleAttr).toContain(mockPipeline.project.name); expect(titleAttr).toContain(mockPipeline.details.status.label); @@ -117,6 +125,56 @@ describe('Linked pipeline', () => { createWrapper(upstreamProps); expect(findPipelineLabel().exists()).toBe(true); }); + + it('downstream pipeline should contain the correct link', () => { + createWrapper(downstreamProps); + expect(findPipelineLink().attributes('href')).toBe(mockData.triggered_by.path); + }); + + it('upstream pipeline should contain the correct link', () => { + createWrapper(upstreamProps); + expect(findPipelineLink().attributes('href')).toBe(mockData.triggered_by.path); + }); + + it.each` + presentClass | missingClass + ${'gl-right-0'} | ${'gl-left-0'} + ${'gl-border-l-1!'} | ${'gl-border-r-1!'} + `( + 'pipeline expand button should be postioned right when child pipeline', + ({ presentClass, missingClass }) => { + createWrapper(downstreamProps); + expect(findExpandButton().classes()).toContain(presentClass); + expect(findExpandButton().classes()).not.toContain(missingClass); + }, + ); + + it.each` + presentClass | missingClass + ${'gl-left-0'} | ${'gl-right-0'} + ${'gl-border-r-1!'} | ${'gl-border-l-1!'} + `( + 'pipeline expand button should be postioned left when parent pipeline', + ({ presentClass, missingClass }) => { + createWrapper(upstreamProps); + expect(findExpandButton().classes()).toContain(presentClass); + expect(findExpandButton().classes()).not.toContain(missingClass); + }, + ); + + it.each` + pipelineType | anglePosition | expanded + ${downstreamProps} | ${'angle-right'} | ${false} + ${downstreamProps} | ${'angle-left'} | ${true} + ${upstreamProps} | ${'angle-left'} | ${false} + ${upstreamProps} | ${'angle-right'} | ${true} + `( + '$pipelineType.columnTitle pipeline button icon should be $anglePosition if expanded state is $expanded', + ({ pipelineType, anglePosition, expanded }) => { + createWrapper(pipelineType, { expanded }); + expect(findExpandButton().props('icon')).toBe(anglePosition); + }, + ); }); describe('when isLoading is true', () => { @@ -130,8 +188,8 @@ describe('Linked pipeline', () => { createWrapper(props); }); - it('sets the loading prop to true', () => { - expect(findButton().props('loading')).toBe(true); + it('loading icon is visible', () => { + expect(findLoadingIcon().exists()).toBe(true); }); }); @@ -172,5 +230,10 @@ describe('Linked pipeline', () => { findLinkedPipeline().trigger('mouseleave'); expect(wrapper.emitted().downstreamHovered).toStrictEqual([['']]); }); + + it('should emit pipelineExpanded with job name and expanded state on click', () => { + findExpandButton().trigger('click'); + expect(wrapper.emitted().pipelineExpandToggle).toStrictEqual([['trigger_job', true]]); + }); }); }); diff --git a/spec/frontend/pipelines/pipeline_graph/gitlab_ci_yaml_visualization_spec.js b/spec/frontend/pipelines/pipeline_graph/gitlab_ci_yaml_visualization_spec.js new file mode 100644 index 00000000000..fea42350959 --- /dev/null +++ b/spec/frontend/pipelines/pipeline_graph/gitlab_ci_yaml_visualization_spec.js @@ -0,0 +1,47 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlTab } from '@gitlab/ui'; +import { yamlString } from './mock_data'; +import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue'; +import GitlabCiYamlVisualization from '~/pipelines/components/pipeline_graph/gitlab_ci_yaml_visualization.vue'; + +describe('gitlab yaml visualization component', () => { + const defaultProps = { blobData: yamlString }; + let wrapper; + + const createComponent = props => { + return shallowMount(GitlabCiYamlVisualization, { + propsData: { + ...defaultProps, + ...props, + }, + }); + }; + + const findGlTabComponents = () => wrapper.findAll(GlTab); + const findPipelineGraph = () => wrapper.find(PipelineGraph); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('tabs component', () => { + beforeEach(() => { + wrapper = createComponent(); + }); + + it('renders the file and visualization tabs', () => { + expect(findGlTabComponents()).toHaveLength(2); + }); + }); + + describe('graph component', () => { + beforeEach(() => { + wrapper = createComponent(); + }); + + it('is hidden by default', () => { + expect(findPipelineGraph().exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/pipelines/pipeline_graph/mock_data.js b/spec/frontend/pipelines/pipeline_graph/mock_data.js new file mode 100644 index 00000000000..5a5d6c021a6 --- /dev/null +++ b/spec/frontend/pipelines/pipeline_graph/mock_data.js @@ -0,0 +1,80 @@ +export const yamlString = `stages: +- empty +- build +- test +- deploy +- final + +include: +- template: 'Workflows/MergeRequest-Pipelines.gitlab-ci.yml' + +build_a: + stage: build + script: echo hello +build_b: + stage: build + script: echo hello +build_c: + stage: build + script: echo hello +build_d: + stage: Queen + script: echo hello + +test_a: + stage: test + script: ls + needs: [build_a, build_b, build_c] +test_b: + stage: test + script: ls + needs: [build_a, build_b, build_d] +test_c: + stage: test + script: ls + needs: [build_a, build_b, build_c] + +deploy_a: + stage: deploy + script: echo hello +`; + +export const pipelineData = { + stages: [ + { + name: 'build', + groups: [], + }, + { + name: 'build', + groups: [ + { + name: 'build_1', + jobs: [{ script: 'echo hello', stage: 'build' }], + }, + ], + }, + { + name: 'test', + groups: [ + { + name: 'test_1', + jobs: [{ script: 'yarn test', stage: 'test' }], + }, + { + name: 'test_2', + jobs: [{ script: 'yarn karma', stage: 'test' }], + }, + ], + }, + { + name: 'deploy', + groups: [ + { + name: 'deploy_1', + jobs: [{ script: 'yarn magick', stage: 'deploy' }], + }, + ], + }, + ], +}; diff --git a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js new file mode 100644 index 00000000000..30e192e5726 --- /dev/null +++ b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js @@ -0,0 +1,59 @@ +import { shallowMount } from '@vue/test-utils'; +import { pipelineData } from './mock_data'; +import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue'; +import StagePill from '~/pipelines/components/pipeline_graph/stage_pill.vue'; +import JobPill from '~/pipelines/components/pipeline_graph/job_pill.vue'; + +describe('pipeline graph component', () => { + const defaultProps = { pipelineData }; + let wrapper; + + const createComponent = props => { + return shallowMount(PipelineGraph, { + propsData: { + ...defaultProps, + ...props, + }, + }); + }; + + const findAllStagePills = () => wrapper.findAll(StagePill); + const findAllJobPills = () => wrapper.findAll(JobPill); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('with no data', () => { + beforeEach(() => { + wrapper = createComponent({ pipelineData: {} }); + }); + + it('renders an empty section', () => { + expect(wrapper.text()).toContain('No content to show'); + expect(findAllStagePills()).toHaveLength(0); + expect(findAllJobPills()).toHaveLength(0); + }); + }); + + describe('with data', () => { + beforeEach(() => { + wrapper = createComponent(); + }); + it('renders the right number of stage pills', () => { + const expectedStagesLength = pipelineData.stages.length; + + expect(findAllStagePills()).toHaveLength(expectedStagesLength); + }); + + it('renders the right number of job pills', () => { + // We count the number of jobs in the mock data + const expectedJobsLength = pipelineData.stages.reduce((acc, val) => { + return acc + val.groups.length; + }, 0); + + expect(findAllJobPills()).toHaveLength(expectedJobsLength); + }); + }); +}); diff --git a/spec/frontend/pipelines/pipeline_graph/utils_spec.js b/spec/frontend/pipelines/pipeline_graph/utils_spec.js new file mode 100644 index 00000000000..dd85c8c2bd0 --- /dev/null +++ b/spec/frontend/pipelines/pipeline_graph/utils_spec.js @@ -0,0 +1,150 @@ +import { preparePipelineGraphData } from '~/pipelines/utils'; + +describe('preparePipelineGraphData', () => { + const emptyResponse = { stages: [] }; + const jobName1 = 'build_1'; + const jobName2 = 'build_2'; + const jobName3 = 'test_1'; + const jobName4 = 'deploy_1'; + const job1 = { [jobName1]: { script: 'echo hello', stage: 'build' } }; + const job2 = { [jobName2]: { script: 'echo build', stage: 'build' } }; + const job3 = { [jobName3]: { script: 'echo test', stage: 'test' } }; + const job4 = { [jobName4]: { script: 'echo deploy', stage: 'deploy' } }; + + describe('returns an object with an empty array of stages if', () => { + it('no data is passed', () => { + expect(preparePipelineGraphData({})).toEqual(emptyResponse); + }); + + it('no stages are found', () => { + expect(preparePipelineGraphData({ includes: 'template/myTemplate.gitlab-ci.yml' })).toEqual( + emptyResponse, + ); + }); + }); + + describe('returns the correct array of stages', () => { + it('when multiple jobs are in the same stage', () => { + const expectedData = { + stages: [ + { + name: job1[jobName1].stage, + groups: [ + { + name: jobName1, + jobs: [{ script: job1[jobName1].script, stage: job1[jobName1].stage }], + }, + { + name: jobName2, + jobs: [{ script: job2[jobName2].script, stage: job2[jobName2].stage }], + }, + ], + }, + ], + }; + + expect(preparePipelineGraphData({ ...job1, ...job2 })).toEqual(expectedData); + }); + + it('when stages are defined by the user', () => { + const userDefinedStage = 'myStage'; + const userDefinedStage2 = 'myStage2'; + + const expectedData = { + stages: [ + { + name: userDefinedStage, + groups: [], + }, + { + name: userDefinedStage2, + groups: [], + }, + ], + }; + + expect(preparePipelineGraphData({ stages: [userDefinedStage, userDefinedStage2] })).toEqual( + expectedData, + ); + }); + + it('by combining user defined stage and job stages, it preserves user defined order', () => { + const userDefinedStage = 'myStage'; + const userDefinedStageThatOverlaps = 'deploy'; + + const expectedData = { + stages: [ + { + name: userDefinedStage, + groups: [], + }, + { + name: job4[jobName4].stage, + groups: [ + { + name: jobName4, + jobs: [{ script: job4[jobName4].script, stage: job4[jobName4].stage }], + }, + ], + }, + { + name: job1[jobName1].stage, + groups: [ + { + name: jobName1, + jobs: [{ script: job1[jobName1].script, stage: job1[jobName1].stage }], + }, + { + name: jobName2, + jobs: [{ script: job2[jobName2].script, stage: job2[jobName2].stage }], + }, + ], + }, + { + name: job3[jobName3].stage, + groups: [ + { + name: jobName3, + jobs: [{ script: job3[jobName3].script, stage: job3[jobName3].stage }], + }, + ], + }, + ], + }; + + expect( + preparePipelineGraphData({ + stages: [userDefinedStage, userDefinedStageThatOverlaps], + ...job1, + ...job2, + ...job3, + ...job4, + }), + ).toEqual(expectedData); + }); + + it('with only unique values', () => { + const expectedData = { + stages: [ + { + name: job1[jobName1].stage, + groups: [ + { + name: jobName1, + jobs: [{ script: job1[jobName1].script, stage: job1[jobName1].stage }], + }, + ], + }, + ], + }; + + expect( + preparePipelineGraphData({ + stages: ['build'], + ...job1, + ...job1, + }), + ).toEqual(expectedData); + }); + }); +}); diff --git a/spec/frontend/pipelines/pipeline_triggerer_spec.js b/spec/frontend/pipelines/pipeline_triggerer_spec.js index 6fd9a143d82..ad8136890e6 100644 --- a/spec/frontend/pipelines/pipeline_triggerer_spec.js +++ b/spec/frontend/pipelines/pipeline_triggerer_spec.js @@ -36,7 +36,7 @@ describe('Pipelines Triggerer', () => { }); it('should render a table cell', () => { - expect(wrapper.contains('.table-section')).toBe(true); + expect(wrapper.find('.table-section').exists()).toBe(true); }); it('should pass triggerer information when triggerer is provided', () => { diff --git a/spec/frontend/pipelines/pipelines_actions_spec.js b/spec/frontend/pipelines/pipelines_actions_spec.js index cce4c2dfa7b..071a2b24889 100644 --- a/spec/frontend/pipelines/pipelines_actions_spec.js +++ b/spec/frontend/pipelines/pipelines_actions_spec.js @@ -1,7 +1,7 @@ import { shallowMount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import { TEST_HOST } from 'spec/test_constants'; -import { GlDeprecatedButton } from '@gitlab/ui'; +import { GlButton } from '@gitlab/ui'; import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; import PipelinesActions from '~/pipelines/components/pipelines_list/pipelines_actions.vue'; @@ -19,7 +19,7 @@ describe('Pipelines Actions dropdown', () => { }); }; - const findAllDropdownItems = () => wrapper.findAll(GlDeprecatedButton); + const findAllDropdownItems = () => wrapper.findAll(GlButton); const findAllCountdowns = () => wrapper.findAll(GlCountdown); beforeEach(() => { @@ -66,7 +66,7 @@ describe('Pipelines Actions dropdown', () => { it('makes a request and toggles the loading state', () => { mock.onPost(mockActions.path).reply(200); - wrapper.find(GlDeprecatedButton).vm.$emit('click'); + wrapper.find(GlButton).vm.$emit('click'); expect(wrapper.vm.isLoading).toBe(true); diff --git a/spec/frontend/pipelines/test_reports/stores/getters_spec.js b/spec/frontend/pipelines/test_reports/stores/getters_spec.js index ca9ebb54138..58e8065033f 100644 --- a/spec/frontend/pipelines/test_reports/stores/getters_spec.js +++ b/spec/frontend/pipelines/test_reports/stores/getters_spec.js @@ -1,6 +1,6 @@ import { getJSONFixture } from 'helpers/fixtures'; import * as getters from '~/pipelines/stores/test_reports/getters'; -import { iconForTestStatus } from '~/pipelines/stores/test_reports/utils'; +import { iconForTestStatus, formattedTime } from '~/pipelines/stores/test_reports/utils'; describe('Getters TestReports Store', () => { let state; @@ -34,7 +34,7 @@ describe('Getters TestReports Store', () => { const suites = getters.getTestSuites(state); const expected = testReports.test_suites.map(x => ({ ...x, - formattedTime: '00:00:00', + formattedTime: formattedTime(x.total_time), })); expect(suites).toEqual(expected); @@ -65,7 +65,7 @@ describe('Getters TestReports Store', () => { const cases = getters.getSuiteTests(state); const expected = testReports.test_suites[0].test_cases.map(x => ({ ...x, - formattedTime: '00:00:00', + formattedTime: formattedTime(x.execution_time), icon: iconForTestStatus(x.status), })); diff --git a/spec/frontend/pipelines/test_reports/stores/utils_spec.js b/spec/frontend/pipelines/test_reports/stores/utils_spec.js new file mode 100644 index 00000000000..7e632d099fc --- /dev/null +++ b/spec/frontend/pipelines/test_reports/stores/utils_spec.js @@ -0,0 +1,26 @@ +import { formattedTime } from '~/pipelines/stores/test_reports/utils'; + +describe('Test reports utils', () => { + describe('formattedTime', () => { + describe('when time is smaller than a second', () => { + it('should return time in milliseconds fixed to 2 decimals', () => { + const result = formattedTime(0.4815162342); + expect(result).toBe('481.52ms'); + }); + }); + + describe('when time is equal to a second', () => { + it('should return time in seconds fixed to 2 decimals', () => { + const result = formattedTime(1); + expect(result).toBe('1.00s'); + }); + }); + + describe('when time is greater than a second', () => { + it('should return time in seconds fixed to 2 decimals', () => { + const result = formattedTime(4.815162342); + expect(result).toBe('4.82s'); + }); + }); + }); +}); diff --git a/spec/frontend/pipelines/test_reports/test_reports_spec.js b/spec/frontend/pipelines/test_reports/test_reports_spec.js index a709edf5184..c8ab18b9086 100644 --- a/spec/frontend/pipelines/test_reports/test_reports_spec.js +++ b/spec/frontend/pipelines/test_reports/test_reports_spec.js @@ -1,4 +1,5 @@ import Vuex from 'vuex'; +import { GlLoadingIcon } from '@gitlab/ui'; import { shallowMount, createLocalVue } from '@vue/test-utils'; import { getJSONFixture } from 'helpers/fixtures'; import TestReports from '~/pipelines/components/test_reports/test_reports.vue'; @@ -15,9 +16,9 @@ describe('Test reports app', () => { const testReports = getJSONFixture('pipelines/test_report.json'); - const loadingSpinner = () => wrapper.find('.js-loading-spinner'); - const testsDetail = () => wrapper.find('.js-tests-detail'); - const noTestsToShow = () => wrapper.find('.js-no-tests-to-show'); + const loadingSpinner = () => wrapper.find(GlLoadingIcon); + const testsDetail = () => wrapper.find('[data-testid="tests-detail"]'); + const noTestsToShow = () => wrapper.find('[data-testid="no-tests-to-show"]'); const testSummary = () => wrapper.find(TestSummary); const testSummaryTable = () => wrapper.find(TestSummaryTable); @@ -88,6 +89,10 @@ describe('Test reports app', () => { expect(wrapper.vm.testReports).toBeTruthy(); expect(wrapper.vm.showTests).toBeTruthy(); }); + + it('shows tests details', () => { + expect(testsDetail().exists()).toBe(true); + }); }); describe('when a suite is clicked', () => { 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 3a4aa94571e..2feb6aa5799 100644 --- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js +++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js @@ -23,8 +23,6 @@ describe('Test reports suite table', () => { const noCasesMessage = () => wrapper.find('.js-no-test-cases'); const allCaseRows = () => wrapper.findAll('.js-case-row'); const findCaseRowAtIndex = index => wrapper.findAll('.js-case-row').at(index); - const allCaseNames = () => - wrapper.findAll('[data-testid="caseName"]').wrappers.map(el => el.attributes('text')); const findIconForRow = (row, status) => row.find(`.ci-status-icon-${status}`); const createComponent = (suite = testSuite) => { @@ -63,16 +61,6 @@ describe('Test reports suite table', () => { expect(allCaseRows().length).toBe(testCases.length); }); - it('renders the failed tests first, skipped tests next, then successful tests', () => { - const expectedCaseOrder = [ - ...testCases.filter(x => x.status === TestStatus.FAILED), - ...testCases.filter(x => x.status === TestStatus.SKIPPED), - ...testCases.filter(x => x.status === TestStatus.SUCCESS), - ].map(x => x.name); - - expect(allCaseNames()).toEqual(expectedCaseOrder); - }); - it('renders the correct icon for each status', () => { const failedTest = testCases.findIndex(x => x.status === TestStatus.FAILED); const skippedTest = testCases.findIndex(x => x.status === TestStatus.SKIPPED); diff --git a/spec/frontend/pipelines/test_reports/test_summary_spec.js b/spec/frontend/pipelines/test_reports/test_summary_spec.js index 79be6c168cf..dc5af7b160c 100644 --- a/spec/frontend/pipelines/test_reports/test_summary_spec.js +++ b/spec/frontend/pipelines/test_reports/test_summary_spec.js @@ -1,6 +1,7 @@ import { mount } from '@vue/test-utils'; import { getJSONFixture } from 'helpers/fixtures'; import Summary from '~/pipelines/components/test_reports/test_summary.vue'; +import { formattedTime } from '~/pipelines/stores/test_reports/utils'; describe('Test reports summary', () => { let wrapper; @@ -76,7 +77,7 @@ describe('Test reports summary', () => { }); it('displays the correctly formatted duration', () => { - expect(duration().text()).toBe('00:00:00'); + expect(duration().text()).toBe(formattedTime(testSuite.total_time)); }); }); diff --git a/spec/frontend/pipelines/time_ago_spec.js b/spec/frontend/pipelines/time_ago_spec.js index 04934fb93b0..b7bc8d08a0f 100644 --- a/spec/frontend/pipelines/time_ago_spec.js +++ b/spec/frontend/pipelines/time_ago_spec.js @@ -1,4 +1,5 @@ import { shallowMount } from '@vue/test-utils'; +import { GlIcon } from '@gitlab/ui'; import TimeAgo from '~/pipelines/components/pipelines_list/time_ago.vue'; describe('Timeago component', () => { @@ -22,14 +23,19 @@ describe('Timeago component', () => { wrapper = null; }); + const duration = () => wrapper.find('.duration'); + const finishedAt = () => wrapper.find('.finished-at'); + describe('with duration', () => { beforeEach(() => { createComponent({ duration: 10, finishedTime: '' }); }); it('should render duration and timer svg', () => { - expect(wrapper.find('.duration').exists()).toBe(true); - expect(wrapper.find('.duration svg').exists()).toBe(true); + const icon = duration().find(GlIcon); + + expect(duration().exists()).toBe(true); + expect(icon.props('name')).toBe('timer'); }); }); @@ -39,7 +45,7 @@ describe('Timeago component', () => { }); it('should not render duration and timer svg', () => { - expect(wrapper.find('.duration').exists()).toBe(false); + expect(duration().exists()).toBe(false); }); }); @@ -49,9 +55,12 @@ describe('Timeago component', () => { }); it('should render time and calendar icon', () => { - expect(wrapper.find('.finished-at').exists()).toBe(true); - expect(wrapper.find('.finished-at i.fa-calendar').exists()).toBe(true); - expect(wrapper.find('.finished-at time').exists()).toBe(true); + const icon = finishedAt().find(GlIcon); + const time = finishedAt().find('time'); + + expect(finishedAt().exists()).toBe(true); + expect(icon.props('name')).toBe('calendar'); + expect(time.exists()).toBe(true); }); }); @@ -61,7 +70,7 @@ describe('Timeago component', () => { }); it('should not render time and calendar icon', () => { - expect(wrapper.find('.finished-at').exists()).toBe(false); + expect(finishedAt().exists()).toBe(false); }); }); }); diff --git a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js index 096e4cd97f6..b53955ab743 100644 --- a/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js +++ b/spec/frontend/pipelines/tokens/pipeline_status_token_spec.js @@ -5,16 +5,17 @@ import PipelineStatusToken from '~/pipelines/components/pipelines_list/tokens/pi describe('Pipeline Status Token', () => { let wrapper; - const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken); - const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion); - const findAllGlIcons = () => wrapper.findAll(GlIcon); - const stubs = { GlFilteredSearchToken: { + props: GlFilteredSearchToken.props, template: `<div><slot name="suggestions"></slot></div>`, }, }; + const findFilteredSearchToken = () => wrapper.find(stubs.GlFilteredSearchToken); + const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion); + const findAllGlIcons = () => wrapper.findAll(GlIcon); + const defaultProps = { config: { type: 'status', @@ -27,12 +28,12 @@ describe('Pipeline Status Token', () => { }, }; - const createComponent = options => { + const createComponent = () => { wrapper = shallowMount(PipelineStatusToken, { propsData: { ...defaultProps, }, - ...options, + stubs, }); }; @@ -50,10 +51,6 @@ describe('Pipeline Status Token', () => { }); describe('shows statuses correctly', () => { - beforeEach(() => { - createComponent({ stubs }); - }); - it('renders all pipeline statuses available', () => { expect(findAllFilteredSearchSuggestions()).toHaveLength(wrapper.vm.statuses.length); expect(findAllGlIcons()).toHaveLength(wrapper.vm.statuses.length); diff --git a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js index c95d2ea1b7b..9363944a719 100644 --- a/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js +++ b/spec/frontend/pipelines/tokens/pipeline_trigger_author_token_spec.js @@ -7,16 +7,17 @@ import { users } from '../mock_data'; describe('Pipeline Trigger Author Token', () => { let wrapper; - const findFilteredSearchToken = () => wrapper.find(GlFilteredSearchToken); - const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion); - const findLoadingIcon = () => wrapper.find(GlLoadingIcon); - const stubs = { GlFilteredSearchToken: { + props: GlFilteredSearchToken.props, template: `<div><slot name="suggestions"></slot></div>`, }, }; + const findFilteredSearchToken = () => wrapper.find(stubs.GlFilteredSearchToken); + const findAllFilteredSearchSuggestions = () => wrapper.findAll(GlFilteredSearchSuggestion); + const findLoadingIcon = () => wrapper.find(GlLoadingIcon); + const defaultProps = { config: { type: 'username', @@ -31,7 +32,7 @@ describe('Pipeline Trigger Author Token', () => { }, }; - const createComponent = (options, data) => { + const createComponent = data => { wrapper = shallowMount(PipelineTriggerAuthorToken, { propsData: { ...defaultProps, @@ -41,7 +42,7 @@ describe('Pipeline Trigger Author Token', () => { ...data, }; }, - ...options, + stubs, }); }; @@ -69,13 +70,13 @@ describe('Pipeline Trigger Author Token', () => { describe('displays loading icon correctly', () => { it('shows loading icon', () => { - createComponent({ stubs }, { loading: true }); + createComponent({ loading: true }); expect(findLoadingIcon().exists()).toBe(true); }); it('does not show loading icon', () => { - createComponent({ stubs }, { loading: false }); + createComponent({ loading: false }); expect(findLoadingIcon().exists()).toBe(false); }); @@ -85,22 +86,17 @@ describe('Pipeline Trigger Author Token', () => { beforeEach(() => {}); it('renders all trigger authors', () => { - createComponent({ stubs }, { users, loading: false }); + createComponent({ users, loading: false }); // should have length of all users plus the static 'Any' option expect(findAllFilteredSearchSuggestions()).toHaveLength(users.length + 1); }); it('renders only the trigger author searched for', () => { - createComponent( - { stubs }, - { - users: [ - { name: 'Arnold', username: 'admin', state: 'active', avatar_url: 'avatar-link' }, - ], - loading: false, - }, - ); + createComponent({ + users: [{ name: 'Arnold', username: 'admin', state: 'active', avatar_url: 'avatar-link' }], + loading: false, + }); expect(findAllFilteredSearchSuggestions()).toHaveLength(2); }); |