diff options
Diffstat (limited to 'spec/frontend/ci/common/pipelines_table_spec.js')
-rw-r--r-- | spec/frontend/ci/common/pipelines_table_spec.js | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/spec/frontend/ci/common/pipelines_table_spec.js b/spec/frontend/ci/common/pipelines_table_spec.js new file mode 100644 index 00000000000..26dd1a2fcc5 --- /dev/null +++ b/spec/frontend/ci/common/pipelines_table_spec.js @@ -0,0 +1,280 @@ +import '~/commons'; +import { GlTableLite } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import fixture from 'test_fixtures/pipelines/pipelines.json'; +import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import LegacyPipelineMiniGraph from '~/ci/pipeline_mini_graph/legacy_pipeline_mini_graph.vue'; +import PipelineFailedJobsWidget from '~/ci/pipelines_page/components/failure_widget/pipeline_failed_jobs_widget.vue'; +import PipelineOperations from '~/ci/pipelines_page/components/pipeline_operations.vue'; +import PipelineTriggerer from '~/ci/pipelines_page/components/pipeline_triggerer.vue'; +import PipelineUrl from '~/ci/pipelines_page/components/pipeline_url.vue'; +import PipelinesTable from '~/ci/common/pipelines_table.vue'; +import PipelinesTimeago from '~/ci/pipelines_page/components/time_ago.vue'; +import { + PipelineKeyOptions, + BUTTON_TOOLTIP_RETRY, + BUTTON_TOOLTIP_CANCEL, + TRACKING_CATEGORIES, +} from '~/ci/constants'; + +import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; + +jest.mock('~/ci/event_hub'); + +describe('Pipelines Table', () => { + let pipeline; + let wrapper; + let trackingSpy; + + const defaultProvide = { + glFeatures: {}, + withFailedJobsDetails: false, + }; + + const provideWithDetails = { + glFeatures: { + ciJobFailuresInMr: true, + }, + withFailedJobsDetails: true, + }; + + const defaultProps = { + pipelines: [], + viewType: 'root', + pipelineKeyOption: PipelineKeyOptions[0], + }; + + const createMockPipeline = () => { + // Clone fixture as it could be modified by tests + const { pipelines } = JSON.parse(JSON.stringify(fixture)); + return pipelines.find((p) => p.user !== null && p.commit !== null); + }; + + const createComponent = (props = {}, provide = {}) => { + wrapper = extendedWrapper( + mount(PipelinesTable, { + propsData: { + ...defaultProps, + ...props, + }, + provide: { + ...defaultProvide, + ...provide, + }, + stubs: ['PipelineFailedJobsWidget'], + }), + ); + }; + + const findGlTableLite = () => wrapper.findComponent(GlTableLite); + const findCiBadgeLink = () => wrapper.findComponent(CiBadgeLink); + const findPipelineInfo = () => wrapper.findComponent(PipelineUrl); + const findTriggerer = () => wrapper.findComponent(PipelineTriggerer); + const findLegacyPipelineMiniGraph = () => wrapper.findComponent(LegacyPipelineMiniGraph); + const findTimeAgo = () => wrapper.findComponent(PipelinesTimeago); + const findActions = () => wrapper.findComponent(PipelineOperations); + + const findPipelineFailureWidget = () => wrapper.findComponent(PipelineFailedJobsWidget); + const findTableRows = () => wrapper.findAllByTestId('pipeline-table-row'); + const findStatusTh = () => wrapper.findByTestId('status-th'); + const findPipelineTh = () => wrapper.findByTestId('pipeline-th'); + const findStagesTh = () => wrapper.findByTestId('stages-th'); + const findActionsTh = () => wrapper.findByTestId('actions-th'); + const findRetryBtn = () => wrapper.findByTestId('pipelines-retry-button'); + const findCancelBtn = () => wrapper.findByTestId('pipelines-cancel-button'); + + beforeEach(() => { + pipeline = createMockPipeline(); + }); + + describe('Pipelines Table', () => { + beforeEach(() => { + createComponent({ pipelines: [pipeline], viewType: 'root' }); + }); + + it('displays table', () => { + expect(findGlTableLite().exists()).toBe(true); + }); + + it('should render table head with correct columns', () => { + expect(findStatusTh().text()).toBe('Status'); + expect(findPipelineTh().text()).toBe('Pipeline'); + expect(findStagesTh().text()).toBe('Stages'); + expect(findActionsTh().text()).toBe('Actions'); + }); + + it('should display a table row', () => { + expect(findTableRows()).toHaveLength(1); + }); + + describe('status cell', () => { + it('should render a status badge', () => { + expect(findCiBadgeLink().exists()).toBe(true); + }); + }); + + describe('pipeline cell', () => { + it('should render pipeline information', () => { + expect(findPipelineInfo().exists()).toBe(true); + }); + + it('should display the pipeline id', () => { + expect(findPipelineInfo().text()).toContain(`#${pipeline.id}`); + }); + }); + + describe('stages cell', () => { + it('should render pipeline mini graph', () => { + expect(findLegacyPipelineMiniGraph().exists()).toBe(true); + }); + + it('should render the right number of stages', () => { + const stagesLength = pipeline.details.stages.length; + expect(findLegacyPipelineMiniGraph().props('stages').length).toBe(stagesLength); + }); + + it('should render the latest downstream pipelines only', () => { + // component receives two downstream pipelines. one of them is already outdated + // because we retried the trigger job, so the mini pipeline graph will only + // render the newly created downstream pipeline instead + expect(pipeline.triggered).toHaveLength(2); + expect(findLegacyPipelineMiniGraph().props('downstreamPipelines')).toHaveLength(1); + }); + + describe('when pipeline does not have stages', () => { + beforeEach(() => { + pipeline = createMockPipeline(); + pipeline.details.stages = []; + + createComponent({ pipelines: [pipeline] }); + }); + + it('stages are not rendered', () => { + expect(findLegacyPipelineMiniGraph().props('stages')).toHaveLength(0); + }); + }); + }); + + describe('duration cell', () => { + it('should render duration information', () => { + expect(findTimeAgo().exists()).toBe(true); + }); + }); + + describe('operations cell', () => { + it('should render pipeline operations', () => { + expect(findActions().exists()).toBe(true); + }); + + it('should render retry action tooltip', () => { + expect(findRetryBtn().attributes('title')).toBe(BUTTON_TOOLTIP_RETRY); + }); + + it('should render cancel action tooltip', () => { + expect(findCancelBtn().attributes('title')).toBe(BUTTON_TOOLTIP_CANCEL); + }); + }); + + describe('triggerer cell', () => { + it('should render the pipeline triggerer', () => { + expect(findTriggerer().exists()).toBe(true); + }); + }); + + describe('failed jobs details', () => { + describe('row', () => { + describe('when the FF is disabled', () => { + beforeEach(() => { + createComponent({ pipelines: [pipeline] }); + }); + + it('does not render', () => { + expect(findTableRows()).toHaveLength(1); + expect(findPipelineFailureWidget().exists()).toBe(false); + }); + }); + + describe('when the FF is enabled', () => { + describe('and `withFailedJobsDetails` value is provided', () => { + beforeEach(() => { + createComponent({ pipelines: [pipeline] }, provideWithDetails); + }); + + it('renders', () => { + expect(findTableRows()).toHaveLength(2); + expect(findPipelineFailureWidget().exists()).toBe(true); + }); + + it('passes the expected props', () => { + expect(findPipelineFailureWidget().props()).toStrictEqual({ + failedJobsCount: pipeline.failed_builds.length, + isPipelineActive: pipeline.active, + pipelineIid: pipeline.iid, + pipelinePath: pipeline.path, + // Make sure the forward slash was removed + projectPath: 'frontend-fixtures/pipelines-project', + }); + }); + }); + + describe('and `withFailedJobsDetails` value is not provided', () => { + beforeEach(() => { + createComponent( + { pipelines: [pipeline] }, + { glFeatures: { ciJobFailuresInMr: true } }, + ); + }); + + it('does not render', () => { + expect(findTableRows()).toHaveLength(1); + expect(findPipelineFailureWidget().exists()).toBe(false); + }); + }); + }); + }); + }); + + describe('tracking', () => { + beforeEach(() => { + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + }); + + afterEach(() => { + unmockTracking(); + }); + + it('tracks status badge click', () => { + findCiBadgeLink().vm.$emit('ciStatusBadgeClick'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_ci_status_badge', { + label: TRACKING_CATEGORIES.table, + }); + }); + + it('tracks retry pipeline button click', () => { + findRetryBtn().vm.$emit('click'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_retry_button', { + label: TRACKING_CATEGORIES.table, + }); + }); + + it('tracks cancel pipeline button click', () => { + findCancelBtn().vm.$emit('click'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_cancel_button', { + label: TRACKING_CATEGORIES.table, + }); + }); + + it('tracks pipeline mini graph stage click', () => { + findLegacyPipelineMiniGraph().vm.$emit('miniGraphStageClick'); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_minigraph', { + label: TRACKING_CATEGORIES.table, + }); + }); + }); + }); +}); |