diff options
Diffstat (limited to 'spec/frontend/pipeline_editor/components')
6 files changed, 341 insertions, 19 deletions
diff --git a/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js b/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js index 5dae77a4626..8040c9d701c 100644 --- a/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js +++ b/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js @@ -12,7 +12,7 @@ describe('Pipeline Editor | Commit Form', () => { wrapper = mountFn(CommitForm, { propsData: { defaultMessage: mockCommitMessage, - defaultBranch: mockDefaultBranch, + currentBranch: mockDefaultBranch, ...props, }, @@ -41,7 +41,7 @@ describe('Pipeline Editor | Commit Form', () => { expect(findCommitTextarea().attributes('value')).toBe(mockCommitMessage); }); - it('shows a default branch', () => { + it('shows current branch', () => { expect(findBranchInput().attributes('value')).toBe(mockDefaultBranch); }); @@ -66,7 +66,7 @@ describe('Pipeline Editor | Commit Form', () => { expect(wrapper.emitted('submit')[0]).toEqual([ { message: mockCommitMessage, - branch: mockDefaultBranch, + targetBranch: mockDefaultBranch, openMergeRequest: false, }, ]); @@ -101,7 +101,7 @@ describe('Pipeline Editor | Commit Form', () => { expect(wrapper.emitted('submit')[0]).toEqual([ { message: anotherMessage, - branch: anotherBranch, + targetBranch: anotherBranch, openMergeRequest: true, }, ]); diff --git a/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js index b87ff6ec0de..9e677425807 100644 --- a/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js +++ b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js @@ -3,7 +3,11 @@ import { mount } from '@vue/test-utils'; import { objectToQuery, redirectTo } from '~/lib/utils/url_utility'; import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue'; import CommitSection from '~/pipeline_editor/components/commit/commit_section.vue'; -import { COMMIT_SUCCESS } from '~/pipeline_editor/constants'; +import { + COMMIT_ACTION_CREATE, + COMMIT_ACTION_UPDATE, + COMMIT_SUCCESS, +} from '~/pipeline_editor/constants'; import commitCreate from '~/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql'; import { @@ -25,6 +29,7 @@ jest.mock('~/lib/utils/url_utility', () => ({ })); const mockVariables = { + action: COMMIT_ACTION_UPDATE, projectPath: mockProjectFullPath, startBranch: mockDefaultBranch, message: mockCommitMessage, @@ -35,7 +40,6 @@ const mockVariables = { const mockProvide = { ciConfigPath: mockCiConfigPath, - defaultBranch: mockDefaultBranch, projectFullPath: mockProjectFullPath, newMergeRequestPath: mockNewMergeRequestPath, }; @@ -64,6 +68,8 @@ describe('Pipeline Editor | Commit section', () => { data() { return { commitSha: mockCommitSha, + currentBranch: mockDefaultBranch, + isNewCiConfigFile: Boolean(options?.isNewCiConfigfile), }; }, mocks: { @@ -100,23 +106,58 @@ describe('Pipeline Editor | Commit section', () => { await findCancelBtn().trigger('click'); }; - beforeEach(() => { - createComponent(); - }); - afterEach(() => { mockMutate.mockReset(); - wrapper.destroy(); - wrapper = null; + }); + + describe('when the user commits a new file', () => { + beforeEach(async () => { + createComponent({ options: { isNewCiConfigfile: true } }); + await submitCommit(); + }); + + it('calls the mutation with the CREATE action', () => { + expect(mockMutate).toHaveBeenCalledTimes(1); + expect(mockMutate).toHaveBeenCalledWith({ + mutation: commitCreate, + update: expect.any(Function), + variables: { + ...mockVariables, + action: COMMIT_ACTION_CREATE, + branch: mockDefaultBranch, + }, + }); + }); + }); + + describe('when the user commits an update to an existing file', () => { + beforeEach(async () => { + createComponent(); + await submitCommit(); + }); + + it('calls the mutation with the UPDATE action', () => { + expect(mockMutate).toHaveBeenCalledTimes(1); + expect(mockMutate).toHaveBeenCalledWith({ + mutation: commitCreate, + update: expect.any(Function), + variables: { + ...mockVariables, + action: COMMIT_ACTION_UPDATE, + branch: mockDefaultBranch, + }, + }); + }); }); describe('when the user commits changes to the current branch', () => { beforeEach(async () => { + createComponent(); await submitCommit(); }); - it('calls the mutation with the default branch', () => { + it('calls the mutation with the current branch', () => { expect(mockMutate).toHaveBeenCalledTimes(1); expect(mockMutate).toHaveBeenCalledWith({ mutation: commitCreate, @@ -157,6 +198,7 @@ describe('Pipeline Editor | Commit section', () => { const newBranch = 'new-branch'; beforeEach(async () => { + createComponent(); await submitCommit({ branch: newBranch, }); @@ -178,6 +220,7 @@ describe('Pipeline Editor | Commit section', () => { const newBranch = 'new-branch'; beforeEach(async () => { + createComponent(); await submitCommit({ branch: newBranch, openMergeRequest: true, @@ -195,6 +238,10 @@ describe('Pipeline Editor | Commit section', () => { }); describe('when the commit is ocurring', () => { + beforeEach(() => { + createComponent(); + }); + it('shows a saving state', async () => { mockMutate.mockImplementationOnce(() => { expect(findCommitBtnLoadingIcon().exists()).toBe(true); diff --git a/spec/frontend/pipeline_editor/components/header/pipeline_editor_header_spec.js b/spec/frontend/pipeline_editor/components/header/pipeline_editor_header_spec.js index df15a6c8e7f..ef8ca574e59 100644 --- a/spec/frontend/pipeline_editor/components/header/pipeline_editor_header_spec.js +++ b/spec/frontend/pipeline_editor/components/header/pipeline_editor_header_spec.js @@ -1,21 +1,33 @@ import { shallowMount } from '@vue/test-utils'; import PipelineEditorHeader from '~/pipeline_editor/components/header/pipeline_editor_header.vue'; +import PipelineStatus from '~/pipeline_editor/components/header/pipeline_status.vue'; import ValidationSegment from '~/pipeline_editor/components/header/validation_segment.vue'; -import { mockLintResponse } from '../../mock_data'; +import { mockCiYml, mockLintResponse } from '../../mock_data'; describe('Pipeline editor header', () => { let wrapper; + const mockProvide = { + glFeatures: { + pipelineStatusForPipelineEditor: true, + }, + }; - const createComponent = () => { + const createComponent = ({ provide = {} } = {}) => { wrapper = shallowMount(PipelineEditorHeader, { - props: { + provide: { + ...mockProvide, + ...provide, + }, + propsData: { ciConfigData: mockLintResponse, + ciFileContent: mockCiYml, isCiConfigDataLoading: false, }, }); }; + const findPipelineStatus = () => wrapper.findComponent(PipelineStatus); const findValidationSegment = () => wrapper.findComponent(ValidationSegment); afterEach(() => { @@ -27,8 +39,27 @@ describe('Pipeline editor header', () => { beforeEach(() => { createComponent(); }); + + it('renders the pipeline status', () => { + expect(findPipelineStatus().exists()).toBe(true); + }); + it('renders the validation segment', () => { expect(findValidationSegment().exists()).toBe(true); }); }); + + describe('with pipeline status feature flag off', () => { + beforeEach(() => { + createComponent({ + provide: { + glFeatures: { pipelineStatusForPipelineEditor: false }, + }, + }); + }); + + it('does not render the pipeline status', () => { + expect(findPipelineStatus().exists()).toBe(false); + }); + }); }); diff --git a/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js b/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js new file mode 100644 index 00000000000..de6e112866b --- /dev/null +++ b/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js @@ -0,0 +1,150 @@ +import { GlIcon, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import PipelineStatus, { i18n } from '~/pipeline_editor/components/header/pipeline_status.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import { mockCommitSha, mockProjectPipeline, mockProjectFullPath } from '../../mock_data'; + +const localVue = createLocalVue(); +localVue.use(VueApollo); + +const mockProvide = { + projectFullPath: mockProjectFullPath, +}; + +describe('Pipeline Status', () => { + let wrapper; + let mockApollo; + let mockPipelineQuery; + + const createComponent = ({ hasPipeline = true, isQueryLoading = false }) => { + const pipeline = hasPipeline + ? { loading: isQueryLoading, ...mockProjectPipeline.pipeline } + : { loading: isQueryLoading }; + + wrapper = shallowMount(PipelineStatus, { + provide: mockProvide, + stubs: { GlLink, GlSprintf }, + data: () => (hasPipeline ? { pipeline } : {}), + mocks: { + $apollo: { + queries: { + pipeline, + }, + }, + }, + }); + }; + + const createComponentWithApollo = () => { + const resolvers = { + Query: { + project: mockPipelineQuery, + }, + }; + mockApollo = createMockApollo([], resolvers); + + wrapper = shallowMount(PipelineStatus, { + localVue, + apolloProvider: mockApollo, + provide: mockProvide, + stubs: { GlLink, GlSprintf }, + data() { + return { + commitSha: mockCommitSha, + }; + }, + }); + }; + + const findIcon = () => wrapper.findComponent(GlIcon); + const findCiIcon = () => wrapper.findComponent(CiIcon); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findPipelineId = () => wrapper.find('[data-testid="pipeline-id"]'); + const findPipelineCommit = () => wrapper.find('[data-testid="pipeline-commit"]'); + const findPipelineErrorMsg = () => wrapper.find('[data-testid="pipeline-error-msg"]'); + const findPipelineLoadingMsg = () => wrapper.find('[data-testid="pipeline-loading-msg"]'); + + beforeEach(() => { + mockPipelineQuery = jest.fn(); + }); + + afterEach(() => { + mockPipelineQuery.mockReset(); + + wrapper.destroy(); + wrapper = null; + }); + + describe('while querying', () => { + it('renders loading icon', () => { + createComponent({ isQueryLoading: true, hasPipeline: false }); + + expect(findLoadingIcon().exists()).toBe(true); + expect(findPipelineLoadingMsg().text()).toBe(i18n.fetchLoading); + }); + + it('does not render loading icon if pipeline data is already set', () => { + createComponent({ isQueryLoading: true }); + + expect(findLoadingIcon().exists()).toBe(false); + }); + }); + + describe('when querying data', () => { + describe('when data is set', () => { + beforeEach(async () => { + mockPipelineQuery.mockResolvedValue(mockProjectPipeline); + + createComponentWithApollo(); + await waitForPromises(); + }); + + it('query is called with correct variables', async () => { + expect(mockPipelineQuery).toHaveBeenCalledTimes(1); + expect(mockPipelineQuery).toHaveBeenCalledWith( + expect.anything(), + { + fullPath: mockProjectFullPath, + }, + expect.anything(), + expect.anything(), + ); + }); + + it('does not render error', () => { + expect(findIcon().exists()).toBe(false); + }); + + it('renders pipeline data', () => { + const { id } = mockProjectPipeline.pipeline; + + expect(findCiIcon().exists()).toBe(true); + expect(findPipelineId().text()).toBe(`#${id.match(/\d+/g)[0]}`); + expect(findPipelineCommit().text()).toBe(mockCommitSha); + }); + }); + + describe('when data cannot be fetched', () => { + beforeEach(async () => { + mockPipelineQuery.mockRejectedValue(new Error()); + + createComponentWithApollo(); + await waitForPromises(); + }); + + it('renders error', () => { + expect(findIcon().attributes('name')).toBe('warning-solid'); + expect(findPipelineErrorMsg().text()).toBe(i18n.fetchError); + }); + + it('does not render pipeline data', () => { + expect(findCiIcon().exists()).toBe(false); + expect(findPipelineId().exists()).toBe(false); + expect(findPipelineCommit().exists()).toBe(false); + }); + }); + }); +}); diff --git a/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js b/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js index cf1d89e1d7c..274c2d1b8da 100644 --- a/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js +++ b/spec/frontend/pipeline_editor/components/header/validation_segment_spec.js @@ -7,9 +7,9 @@ import ValidationSegment, { i18n, } from '~/pipeline_editor/components/header/validation_segment.vue'; import { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants'; -import { mockYmlHelpPagePath, mergeUnwrappedCiConfig } from '../../mock_data'; +import { mockYmlHelpPagePath, mergeUnwrappedCiConfig, mockCiYml } from '../../mock_data'; -describe('~/pipeline_editor/components/info/validation_segment.vue', () => { +describe('Validation segment component', () => { let wrapper; const createComponent = (props = {}) => { @@ -20,6 +20,7 @@ describe('~/pipeline_editor/components/info/validation_segment.vue', () => { }, propsData: { ciConfig: mergeUnwrappedCiConfig(), + ciFileContent: mockCiYml, loading: false, ...props, }, @@ -42,6 +43,20 @@ describe('~/pipeline_editor/components/info/validation_segment.vue', () => { expect(wrapper.text()).toBe(i18n.loading); }); + describe('when config is empty', () => { + beforeEach(() => { + createComponent({ ciFileContent: '' }); + }); + + it('has check icon', () => { + expect(findIcon().props('name')).toBe('check'); + }); + + it('shows a message for empty state', () => { + expect(findValidationMsg().text()).toBe(i18n.empty); + }); + }); + describe('when config is valid', () => { beforeEach(() => { createComponent({}); @@ -61,7 +76,7 @@ describe('~/pipeline_editor/components/info/validation_segment.vue', () => { }); }); - describe('when config is not valid', () => { + describe('when config is invalid', () => { beforeEach(() => { createComponent({ ciConfig: mergeUnwrappedCiConfig({ diff --git a/spec/frontend/pipeline_editor/components/ui/pipeline_editor_empty_state_spec.js b/spec/frontend/pipeline_editor/components/ui/pipeline_editor_empty_state_spec.js new file mode 100644 index 00000000000..b444d9dcfea --- /dev/null +++ b/spec/frontend/pipeline_editor/components/ui/pipeline_editor_empty_state_spec.js @@ -0,0 +1,79 @@ +import { GlButton, GlSprintf } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import PipelineEditorEmptyState from '~/pipeline_editor/components/ui/pipeline_editor_empty_state.vue'; + +describe('Pipeline editor empty state', () => { + let wrapper; + const defaultProvide = { + glFeatures: { + pipelineEditorEmptyStateAction: false, + }, + emptyStateIllustrationPath: 'my/svg/path', + }; + + const createComponent = ({ provide } = {}) => { + wrapper = shallowMount(PipelineEditorEmptyState, { + provide: { ...defaultProvide, ...provide }, + }); + }; + + const findSvgImage = () => wrapper.find('img'); + const findTitle = () => wrapper.find('h1'); + const findConfirmButton = () => wrapper.findComponent(GlButton); + const findDescription = () => wrapper.findComponent(GlSprintf); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('template', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders an svg image', () => { + expect(findSvgImage().exists()).toBe(true); + }); + + it('renders a title', () => { + expect(findTitle().exists()).toBe(true); + expect(findTitle().text()).toBe(wrapper.vm.$options.i18n.title); + }); + + it('renders a description', () => { + expect(findDescription().exists()).toBe(true); + expect(findDescription().html()).toContain(wrapper.vm.$options.i18n.body); + }); + + describe('with feature flag off', () => { + it('does not renders a CTA button', () => { + expect(findConfirmButton().exists()).toBe(false); + }); + }); + }); + + describe('with feature flag on', () => { + beforeEach(() => { + createComponent({ + provide: { + glFeatures: { + pipelineEditorEmptyStateAction: true, + }, + }, + }); + }); + + it('renders a CTA button', () => { + expect(findConfirmButton().exists()).toBe(true); + expect(findConfirmButton().text()).toBe(wrapper.vm.$options.i18n.btnText); + }); + + it('emits an event when clicking on the CTA', async () => { + const expectedEvent = 'createEmptyConfigFile'; + expect(wrapper.emitted(expectedEvent)).toBeUndefined(); + + await findConfirmButton().vm.$emit('click'); + expect(wrapper.emitted(expectedEvent)).toHaveLength(1); + }); + }); +}); |