diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-20 09:55:51 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-20 09:55:51 +0000 |
commit | e8d2c2579383897a1dd7f9debd359abe8ae8373d (patch) | |
tree | c42be41678c2586d49a75cabce89322082698334 /spec/frontend/pipelines | |
parent | fc845b37ec3a90aaa719975f607740c22ba6a113 (diff) | |
download | gitlab-ce-e8d2c2579383897a1dd7f9debd359abe8ae8373d.tar.gz |
Add latest changes from gitlab-org/gitlab@14-1-stable-eev14.1.0-rc42
Diffstat (limited to 'spec/frontend/pipelines')
7 files changed, 146 insertions, 154 deletions
diff --git a/spec/frontend/pipelines/empty_state_spec.js b/spec/frontend/pipelines/empty_state_spec.js index 912bc7a104a..1af3065477d 100644 --- a/spec/frontend/pipelines/empty_state_spec.js +++ b/spec/frontend/pipelines/empty_state_spec.js @@ -1,14 +1,21 @@ +import '~/commons'; import { mount } from '@vue/test-utils'; import EmptyState from '~/pipelines/components/pipelines_list/empty_state.vue'; +import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue'; describe('Pipelines Empty State', () => { let wrapper; const findIllustration = () => wrapper.find('img'); const findButton = () => wrapper.find('a'); + const pipelinesCiTemplates = () => wrapper.findComponent(PipelinesCiTemplates); const createWrapper = (props = {}) => { wrapper = mount(EmptyState, { + provide: { + pipelineEditorPath: '', + suggestedCiTemplates: [], + }, propsData: { emptyStateSvgPath: 'foo.svg', canSetCi: true, @@ -27,27 +34,8 @@ describe('Pipelines Empty State', () => { wrapper = null; }); - it('should render empty state SVG', () => { - expect(findIllustration().attributes('src')).toBe('foo.svg'); - }); - - it('should render empty state header', () => { - expect(wrapper.text()).toContain('Build with confidence'); - }); - - it('should render empty state information', () => { - expect(wrapper.text()).toContain( - 'GitLab CI/CD can automatically build, test, and deploy your code. Let GitLab take care of time', - 'consuming tasks, so you can spend more time creating', - ); - }); - - it('should render button with help path', () => { - expect(findButton().attributes('href')).toBe('/help/ci/quick_start/index.md'); - }); - - it('should render button text', () => { - expect(findButton().text()).toBe('Get started with CI/CD'); + it('should render the CI/CD templates', () => { + expect(pipelinesCiTemplates()).toExist(); }); }); diff --git a/spec/frontend/pipelines/graph/mock_data.js b/spec/frontend/pipelines/graph/mock_data.js index 28fe3b67e7b..3812483766d 100644 --- a/spec/frontend/pipelines/graph/mock_data.js +++ b/spec/frontend/pipelines/graph/mock_data.js @@ -12,6 +12,10 @@ export const mockPipelineResponse = { usesNeeds: true, downstream: null, upstream: null, + userPermissions: { + __typename: 'PipelinePermissions', + updatePipeline: true, + }, stages: { __typename: 'CiStageConnection', nodes: [ @@ -573,6 +577,10 @@ export const wrappedPipelineReturn = { iid: '38', complete: true, usesNeeds: true, + userPermissions: { + __typename: 'PipelinePermissions', + updatePipeline: true, + }, downstream: { __typename: 'PipelineConnection', nodes: [], diff --git a/spec/frontend/pipelines/graph/stage_column_component_spec.js b/spec/frontend/pipelines/graph/stage_column_component_spec.js index f9f6c96a1a6..99e8ea9d0a4 100644 --- a/spec/frontend/pipelines/graph/stage_column_component_spec.js +++ b/spec/frontend/pipelines/graph/stage_column_component_spec.js @@ -31,6 +31,9 @@ const defaultProps = { name: 'Fish', groups: mockGroups, pipelineId: 159, + userPermissions: { + updatePipeline: true, + }, }; describe('stage column component', () => { @@ -53,7 +56,6 @@ describe('stage column component', () => { afterEach(() => { wrapper.destroy(); - wrapper = null; }); describe('when mounted', () => { @@ -152,36 +154,52 @@ describe('stage column component', () => { }); describe('with action', () => { - beforeEach(() => { + const defaults = { + groups: [ + { + id: 4259, + name: '<img src=x onerror=alert(document.domain)>', + status: { + icon: 'status_success', + label: 'success', + tooltip: '<img src=x onerror=alert(document.domain)>', + }, + jobs: [mockJob], + }, + ], + title: 'test', + hasTriggeredBy: false, + action: { + icon: 'play', + title: 'Play all', + path: 'action', + }, + }; + + it('renders action button if permissions are permitted', () => { createComponent({ method: mount, props: { - groups: [ - { - id: 4259, - name: '<img src=x onerror=alert(document.domain)>', - status: { - icon: 'status_success', - label: 'success', - tooltip: '<img src=x onerror=alert(document.domain)>', - }, - jobs: [mockJob], - }, - ], - title: 'test', - hasTriggeredBy: false, - action: { - icon: 'play', - title: 'Play all', - path: 'action', - }, + ...defaults, }, }); - }); - it('renders action button', () => { expect(findActionComponent().exists()).toBe(true); }); + + it('does not render action button if permissions are not permitted', () => { + createComponent({ + method: mount, + props: { + ...defaults, + userPermissions: { + updatePipeline: false, + }, + }, + }); + + expect(findActionComponent().exists()).toBe(false); + }); }); describe('without action', () => { diff --git a/spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap b/spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap index 16c28791514..82206e907ff 100644 --- a/spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap +++ b/spec/frontend/pipelines/graph_shared/__snapshots__/links_inner_spec.js.snap @@ -2,29 +2,29 @@ exports[`Links Inner component with a large number of needs matches snapshot and has expected path 1`] = ` "<div class=\\"gl-display-flex gl-relative\\" totalgroups=\\"10\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\"> - <path d=\\"M202,118L42,118C72,118,72,138,102,138\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> - <path d=\\"M202,118L52,118C82,118,82,148,112,148\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> - <path d=\\"M222,138L62,138C92,138,92,158,122,158\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> - <path d=\\"M212,128L72,128C102,128,102,168,132,168\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> - <path d=\\"M232,148L82,148C112,148,112,178,142,178\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> + <path d=\\"M202,118C52,118,52,138,102,138\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> + <path d=\\"M202,118C62,118,62,148,112,148\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> + <path d=\\"M222,138C72,138,72,158,122,158\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> + <path d=\\"M212,128C82,128,82,168,132,168\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> + <path d=\\"M232,148C92,148,92,178,142,178\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> </svg> </div>" `; exports[`Links Inner component with a parallel need matches snapshot and has expected path 1`] = ` "<div class=\\"gl-display-flex gl-relative\\" totalgroups=\\"10\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\"> - <path d=\\"M192,108L22,108C52,108,52,118,82,118\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> + <path d=\\"M192,108C32,108,32,118,82,118\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> </svg> </div>" `; exports[`Links Inner component with one need matches snapshot and has expected path 1`] = ` "<div class=\\"gl-display-flex gl-relative\\" totalgroups=\\"10\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\"> - <path d=\\"M202,118L42,118C72,118,72,138,102,138\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> + <path d=\\"M202,118C52,118,52,138,102,138\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> </svg> </div>" `; exports[`Links Inner component with same stage needs matches snapshot and has expected path 1`] = ` "<div class=\\"gl-display-flex gl-relative\\" totalgroups=\\"10\\"><svg id=\\"link-svg\\" viewBox=\\"0,0,1019,445\\" width=\\"1019px\\" height=\\"445px\\" class=\\"gl-absolute gl-pointer-events-none\\"> - <path d=\\"M192,108L22,108C52,108,52,118,82,118\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> - <path d=\\"M202,118L32,118C62,118,62,128,92,128\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> + <path d=\\"M192,108C32,108,32,118,82,118\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> + <path d=\\"M202,118C42,118,42,128,92,128\\" stroke-width=\\"2\\" class=\\"gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease gl-stroke-gray-200\\"></path> </svg> </div>" `; diff --git a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js index 7bac7036f46..1b89e322d31 100644 --- a/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js +++ b/spec/frontend/pipelines/pipeline_graph/pipeline_graph_spec.js @@ -6,7 +6,7 @@ import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue'; import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue'; import JobPill from '~/pipelines/components/pipeline_graph/job_pill.vue'; import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue'; -import StagePill from '~/pipelines/components/pipeline_graph/stage_pill.vue'; +import StageName from '~/pipelines/components/pipeline_graph/stage_name.vue'; import { pipelineData, singleStageData } from './mock_data'; describe('pipeline graph component', () => { @@ -35,11 +35,9 @@ describe('pipeline graph component', () => { const findAlert = () => wrapper.findComponent(GlAlert); const findAllJobPills = () => wrapper.findAll(JobPill); - const findAllStageBackgroundElements = () => wrapper.findAll('[data-testid="stage-background"]'); - const findAllStagePills = () => wrapper.findAllComponents(StagePill); + const findAllStageNames = () => wrapper.findAllComponents(StageName); const findLinksLayer = () => wrapper.findComponent(LinksLayer); const findPipelineGraph = () => wrapper.find('[data-testid="graph-container"]'); - const findStageBackgroundElementAt = (index) => findAllStageBackgroundElements().at(index); afterEach(() => { wrapper.destroy(); @@ -67,10 +65,10 @@ describe('pipeline graph component', () => { wrapper = createComponent({ pipelineData: singleStageData }); }); - it('renders the right number of stage pills', () => { + it('renders the right number of stage titles', () => { const expectedStagesLength = singleStageData.stages.length; - expect(findAllStagePills()).toHaveLength(expectedStagesLength); + expect(findAllStageNames()).toHaveLength(expectedStagesLength); }); it('renders the right number of job pills', () => { @@ -81,20 +79,6 @@ describe('pipeline graph component', () => { expect(findAllJobPills()).toHaveLength(expectedJobsLength); }); - - describe('rounds corner', () => { - it.each` - cssClass | expectedState - ${'gl-rounded-bottom-left-6'} | ${true} - ${'gl-rounded-top-left-6'} | ${true} - ${'gl-rounded-top-right-6'} | ${true} - ${'gl-rounded-bottom-right-6'} | ${true} - `('$cssClass should be $expectedState on the only element', ({ cssClass, expectedState }) => { - const classes = findStageBackgroundElementAt(0).classes(); - - expect(classes.includes(cssClass)).toBe(expectedState); - }); - }); }); describe('with multiple stages and jobs', () => { @@ -102,10 +86,10 @@ describe('pipeline graph component', () => { wrapper = createComponent(); }); - it('renders the right number of stage pills', () => { + it('renders the right number of stage titles', () => { const expectedStagesLength = pipelineData.stages.length; - expect(findAllStagePills()).toHaveLength(expectedStagesLength); + expect(findAllStageNames()).toHaveLength(expectedStagesLength); }); it('renders the right number of job pills', () => { @@ -116,34 +100,5 @@ describe('pipeline graph component', () => { expect(findAllJobPills()).toHaveLength(expectedJobsLength); }); - - describe('rounds corner', () => { - it.each` - cssClass | expectedState - ${'gl-rounded-bottom-left-6'} | ${true} - ${'gl-rounded-top-left-6'} | ${true} - ${'gl-rounded-top-right-6'} | ${false} - ${'gl-rounded-bottom-right-6'} | ${false} - `( - '$cssClass should be $expectedState on the first element', - ({ cssClass, expectedState }) => { - const classes = findStageBackgroundElementAt(0).classes(); - - expect(classes.includes(cssClass)).toBe(expectedState); - }, - ); - - it.each` - cssClass | expectedState - ${'gl-rounded-bottom-left-6'} | ${false} - ${'gl-rounded-top-left-6'} | ${false} - ${'gl-rounded-top-right-6'} | ${true} - ${'gl-rounded-bottom-right-6'} | ${true} - `('$cssClass should be $expectedState on the last element', ({ cssClass, expectedState }) => { - const classes = findStageBackgroundElementAt(pipelineData.stages.length - 1).classes(); - - expect(classes.includes(cssClass)).toBe(expectedState); - }); - }); }); }); diff --git a/spec/frontend/pipelines/pipelines_ci_templates_spec.js b/spec/frontend/pipelines/pipelines_ci_templates_spec.js index 0c37bf2d84a..db66b675fb9 100644 --- a/spec/frontend/pipelines/pipelines_ci_templates_spec.js +++ b/spec/frontend/pipelines/pipelines_ci_templates_spec.js @@ -1,30 +1,25 @@ +import '~/commons'; import { shallowMount } from '@vue/test-utils'; -import ExperimentTracking from '~/experimentation/experiment_tracking'; +import { mockTracking } from 'helpers/tracking_helper'; import PipelinesCiTemplate from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue'; -const addCiYmlPath = "/-/new/main?commit_message='Add%20.gitlab-ci.yml'"; +const pipelineEditorPath = '/-/ci/editor'; const suggestedCiTemplates = [ { name: 'Android', logo: '/assets/illustrations/logos/android.svg' }, { name: 'Bash', logo: '/assets/illustrations/logos/bash.svg' }, { name: 'C++', logo: '/assets/illustrations/logos/c_plus_plus.svg' }, ]; -jest.mock('~/experimentation/experiment_tracking'); - describe('Pipelines CI Templates', () => { let wrapper; - - const GlEmoji = { template: '<img/>' }; + let trackingSpy; const createWrapper = () => { return shallowMount(PipelinesCiTemplate, { provide: { - addCiYmlPath, + pipelineEditorPath, suggestedCiTemplates, }, - stubs: { - GlEmoji, - }, }); }; @@ -44,9 +39,9 @@ describe('Pipelines CI Templates', () => { wrapper = createWrapper(); }); - it('links to the hello world template', () => { + it('links to the getting started template', () => { expect(findTestTemplateLinks().at(0).attributes('href')).toBe( - addCiYmlPath.concat('&template=Hello-World'), + pipelineEditorPath.concat('?template=Getting-Started'), ); }); }); @@ -68,7 +63,7 @@ describe('Pipelines CI Templates', () => { it('links to the correct template', () => { expect(findTemplateLinks().at(0).attributes('href')).toBe( - addCiYmlPath.concat('&template=Android'), + pipelineEditorPath.concat('?template=Android'), ); }); @@ -88,24 +83,25 @@ describe('Pipelines CI Templates', () => { describe('tracking', () => { beforeEach(() => { wrapper = createWrapper(); + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); }); it('sends an event when template is clicked', () => { findTemplateLinks().at(0).vm.$emit('click'); - expect(ExperimentTracking).toHaveBeenCalledWith('pipeline_empty_state_templates', { + expect(trackingSpy).toHaveBeenCalledTimes(1); + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'template_clicked', { label: 'Android', }); - expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith('template_clicked'); }); - it('sends an event when Hello-World template is clicked', () => { + it('sends an event when Getting-Started template is clicked', () => { findTestTemplateLinks().at(0).vm.$emit('click'); - expect(ExperimentTracking).toHaveBeenCalledWith('pipeline_empty_state_templates', { - label: 'Hello-World', + expect(trackingSpy).toHaveBeenCalledTimes(1); + expect(trackingSpy).toHaveBeenCalledWith(undefined, 'template_clicked', { + label: 'Getting-Started', }); - expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith('template_clicked'); }); }); }); diff --git a/spec/frontend/pipelines/pipelines_spec.js b/spec/frontend/pipelines/pipelines_spec.js index 874ecbccf82..2166961cedd 100644 --- a/spec/frontend/pipelines/pipelines_spec.js +++ b/spec/frontend/pipelines/pipelines_spec.js @@ -12,6 +12,7 @@ import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import NavigationControls from '~/pipelines/components/pipelines_list/nav_controls.vue'; import PipelinesComponent from '~/pipelines/components/pipelines_list/pipelines.vue'; +import PipelinesCiTemplates from '~/pipelines/components/pipelines_list/pipelines_ci_templates.vue'; import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue'; import { RAW_TEXT_WARNING } from '~/pipelines/constants'; import Store from '~/pipelines/stores/pipelines_store'; @@ -82,6 +83,10 @@ describe('Pipelines', () => { const createComponent = (props = defaultProps) => { wrapper = extendedWrapper( mount(PipelinesComponent, { + provide: { + pipelineEditorPath: '', + suggestedCiTemplates: [], + }, propsData: { store: new Store(), projectId: mockProjectId, @@ -551,52 +556,74 @@ describe('Pipelines', () => { await waitForPromises(); }); - it('renders empty state', () => { - expect(findEmptyState().text()).toContain('Build with confidence'); - expect(findEmptyState().text()).toContain( - 'GitLab CI/CD can automatically build, test, and deploy your code.', - ); - - expect(findEmptyState().find(GlButton).text()).toBe('Get started with CI/CD'); - expect(findEmptyState().find(GlButton).attributes('href')).toBe( - '/help/ci/quick_start/index.md', - ); + it('renders the CI/CD templates', () => { + expect(wrapper.find(PipelinesCiTemplates)).toExist(); }); describe('when the code_quality_walkthrough experiment is active', () => { beforeAll(() => { getExperimentData.mockImplementation((name) => name === 'code_quality_walkthrough'); - getExperimentVariant.mockReturnValue('candidate'); }); - it('renders another CTA button', () => { - expect(findEmptyState().findComponent(GlButton).text()).toBe('Add a code quality job'); - expect(findEmptyState().findComponent(GlButton).attributes('href')).toBe( - paths.codeQualityPagePath, - ); + describe('the control state', () => { + beforeAll(() => { + getExperimentVariant.mockReturnValue('control'); + }); + + it('renders the CI/CD templates', () => { + expect(wrapper.find(PipelinesCiTemplates)).toExist(); + }); + }); + + describe('the candidate state', () => { + beforeAll(() => { + getExperimentVariant.mockReturnValue('candidate'); + }); + + it('renders another CTA button', () => { + expect(findEmptyState().findComponent(GlButton).text()).toBe('Add a code quality job'); + expect(findEmptyState().findComponent(GlButton).attributes('href')).toBe( + paths.codeQualityPagePath, + ); + }); }); }); describe('when the ci_runner_templates experiment is active', () => { beforeAll(() => { getExperimentData.mockImplementation((name) => name === 'ci_runner_templates'); - getExperimentVariant.mockReturnValue('candidate'); }); - it('renders two buttons', () => { - expect(findEmptyState().findAllComponents(GlButton).length).toBe(2); - expect(findEmptyState().findAllComponents(GlButton).at(0).text()).toBe( - 'Install GitLab Runners', - ); - expect(findEmptyState().findAllComponents(GlButton).at(0).attributes('href')).toBe( - paths.ciRunnerSettingsPath, - ); - expect(findEmptyState().findAllComponents(GlButton).at(1).text()).toBe( - 'Learn about Runners', - ); - expect(findEmptyState().findAllComponents(GlButton).at(1).attributes('href')).toBe( - '/help/ci/quick_start/index.md', - ); + describe('the control state', () => { + beforeAll(() => { + getExperimentVariant.mockReturnValue('control'); + }); + + it('renders the CI/CD templates', () => { + expect(wrapper.find(PipelinesCiTemplates)).toExist(); + }); + }); + + describe('the candidate state', () => { + beforeAll(() => { + getExperimentVariant.mockReturnValue('candidate'); + }); + + it('renders two buttons', () => { + expect(findEmptyState().findAllComponents(GlButton).length).toBe(2); + expect(findEmptyState().findAllComponents(GlButton).at(0).text()).toBe( + 'Install GitLab Runners', + ); + expect(findEmptyState().findAllComponents(GlButton).at(0).attributes('href')).toBe( + paths.ciRunnerSettingsPath, + ); + expect(findEmptyState().findAllComponents(GlButton).at(1).text()).toBe( + 'Learn about Runners', + ); + expect(findEmptyState().findAllComponents(GlButton).at(1).attributes('href')).toBe( + '/help/ci/quick_start/index.md', + ); + }); }); }); |