diff options
Diffstat (limited to 'spec/frontend/pipeline_editor/pipeline_editor_app_spec.js')
-rw-r--r-- | spec/frontend/pipeline_editor/pipeline_editor_app_spec.js | 425 |
1 files changed, 88 insertions, 337 deletions
diff --git a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js index d6b90900600..46d0452f437 100644 --- a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js +++ b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js @@ -1,119 +1,70 @@ -import { nextTick } from 'vue'; -import { mount, shallowMount, createLocalVue } from '@vue/test-utils'; -import { GlAlert, GlButton, GlFormInput, GlFormTextarea, GlLoadingIcon, GlTabs } from '@gitlab/ui'; -import waitForPromises from 'helpers/wait_for_promises'; +import { GlAlert, GlButton, GlLoadingIcon, GlTabs } 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 httpStatusCodes from '~/lib/utils/http_status'; -import { objectToQuery, redirectTo, refreshCurrentPage } from '~/lib/utils/url_utility'; +import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue'; +import TextEditor from '~/pipeline_editor/components/editor/text_editor.vue'; + +import { COMMIT_SUCCESS, COMMIT_FAILURE, LOAD_FAILURE_UNKNOWN } from '~/pipeline_editor/constants'; +import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.graphql'; +import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue'; +import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue'; import { mockCiConfigPath, mockCiConfigQueryResponse, mockCiYml, - mockCommitSha, - mockCommitNextSha, - mockCommitMessage, mockDefaultBranch, - mockProjectPath, mockProjectFullPath, - mockProjectNamespace, - mockNewMergeRequestPath, } from './mock_data'; -import CommitForm from '~/pipeline_editor/components/commit/commit_form.vue'; -import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.graphql'; -import EditorTab from '~/pipeline_editor/components/ui/editor_tab.vue'; -import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue'; -import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue'; -import TextEditor from '~/pipeline_editor/components/text_editor.vue'; - const localVue = createLocalVue(); localVue.use(VueApollo); -jest.mock('~/lib/utils/url_utility', () => ({ - redirectTo: jest.fn(), - refreshCurrentPage: jest.fn(), - objectToQuery: jest.requireActual('~/lib/utils/url_utility').objectToQuery, - mergeUrlParams: jest.requireActual('~/lib/utils/url_utility').mergeUrlParams, -})); - const MockEditorLite = { template: '<div/>', }; const mockProvide = { + ciConfigPath: mockCiConfigPath, + defaultBranch: mockDefaultBranch, projectFullPath: mockProjectFullPath, - projectPath: mockProjectPath, - projectNamespace: mockProjectNamespace, - glFeatures: { - ciConfigVisualizationTab: true, - }, }; -describe('~/pipeline_editor/pipeline_editor_app.vue', () => { +describe('Pipeline editor app component', () => { let wrapper; let mockApollo; let mockBlobContentData; let mockCiConfigData; - let mockMutate; - - const createComponent = ({ - props = {}, - blobLoading = false, - lintLoading = false, - options = {}, - mountFn = shallowMount, - provide = mockProvide, - } = {}) => { - mockMutate = jest.fn().mockResolvedValue({ - data: { - commitCreate: { - errors: [], - commit: { - sha: mockCommitNextSha, - }, - }, - }, - }); - wrapper = mountFn(PipelineEditorApp, { - propsData: { - ciConfigPath: mockCiConfigPath, - commitSha: mockCommitSha, - defaultBranch: mockDefaultBranch, - newMergeRequestPath: mockNewMergeRequestPath, - ...props, - }, - provide, + const createComponent = ({ blobLoading = false, options = {} } = {}) => { + wrapper = shallowMount(PipelineEditorApp, { + provide: mockProvide, stubs: { GlTabs, GlButton, CommitForm, EditorLite: MockEditorLite, - TextEditor, }, mocks: { $apollo: { queries: { - content: { + initialCiFileContent: { loading: blobLoading, }, ciConfigData: { - loading: lintLoading, + loading: false, }, }, - mutate: mockMutate, }, }, - // attachTo is required for input/submit events - attachTo: mountFn === mount ? document.body : null, ...options, }); }; - const createComponentWithApollo = ({ props = {}, mountFn = shallowMount } = {}) => { + const createComponentWithApollo = ({ props = {} } = {}) => { const handlers = [[getCiConfigData, mockCiConfigData]]; const resolvers = { Query: { @@ -134,18 +85,13 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { apolloProvider: mockApollo, }; - createComponent({ props, options }, mountFn); + createComponent({ props, options }); }; - const findLoadingIcon = () => wrapper.find(GlLoadingIcon); - const findAlert = () => wrapper.find(GlAlert); - const findTabAt = (i) => wrapper.findAll(EditorTab).at(i); - const findVisualizationTab = () => wrapper.find('[data-testid="visualization-tab"]'); - const findTextEditor = () => wrapper.find(TextEditor); - const findEditorLite = () => wrapper.find(MockEditorLite); - const findCommitForm = () => wrapper.find(CommitForm); - const findPipelineGraph = () => wrapper.find(PipelineGraph); - const findCommitBtnLoadingIcon = () => wrapper.find('[type="submit"]').find(GlLoadingIcon); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findAlert = () => wrapper.findComponent(GlAlert); + const findEditorHome = () => wrapper.findComponent(PipelineEditorHome); + const findTextEditor = () => wrapper.findComponent(TextEditor); beforeEach(() => { mockBlobContentData = jest.fn(); @@ -155,9 +101,6 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { afterEach(() => { mockBlobContentData.mockReset(); mockCiConfigData.mockReset(); - refreshCurrentPage.mockReset(); - redirectTo.mockReset(); - mockMutate.mockReset(); wrapper.destroy(); wrapper = null; @@ -170,245 +113,6 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { expect(findTextEditor().exists()).toBe(false); }); - describe('tabs', () => { - describe('editor tab', () => { - it('displays editor only after the tab is mounted', async () => { - createComponent({ mountFn: mount }); - - expect(findTabAt(0).find(TextEditor).exists()).toBe(false); - - await nextTick(); - - expect(findTabAt(0).find(TextEditor).exists()).toBe(true); - }); - }); - - describe('visualization tab', () => { - describe('with feature flag on', () => { - beforeEach(() => { - createComponent(); - }); - - it('display the tab', () => { - expect(findVisualizationTab().exists()).toBe(true); - }); - - it('displays a loading icon if the lint query is loading', () => { - createComponent({ lintLoading: true }); - - expect(findLoadingIcon().exists()).toBe(true); - expect(findPipelineGraph().exists()).toBe(false); - }); - }); - - describe('with feature flag off', () => { - beforeEach(() => { - createComponent({ - provide: { - ...mockProvide, - glFeatures: { ciConfigVisualizationTab: false }, - }, - }); - }); - - it('does not display the tab', () => { - expect(findVisualizationTab().exists()).toBe(false); - }); - }); - }); - }); - - describe('when data is set', () => { - beforeEach(async () => { - createComponent({ mountFn: mount }); - - wrapper.setData({ - content: mockCiYml, - contentModel: mockCiYml, - }); - - await waitForPromises(); - }); - - it('displays content after the query loads', () => { - expect(findLoadingIcon().exists()).toBe(false); - - expect(findEditorLite().attributes('value')).toBe(mockCiYml); - expect(findEditorLite().attributes('file-name')).toBe(mockCiConfigPath); - }); - - it('configures text editor', () => { - expect(findTextEditor().props('commitSha')).toBe(mockCommitSha); - }); - - describe('commit form', () => { - const mockVariables = { - content: mockCiYml, - filePath: mockCiConfigPath, - lastCommitId: mockCommitSha, - message: mockCommitMessage, - projectPath: mockProjectFullPath, - startBranch: mockDefaultBranch, - }; - - const findInForm = (selector) => findCommitForm().find(selector); - - const submitCommit = async ({ - message = mockCommitMessage, - branch = mockDefaultBranch, - openMergeRequest = false, - } = {}) => { - await findInForm(GlFormTextarea).setValue(message); - await findInForm(GlFormInput).setValue(branch); - if (openMergeRequest) { - await findInForm('[data-testid="new-mr-checkbox"]').setChecked(openMergeRequest); - } - await findInForm('[type="submit"]').trigger('click'); - }; - - const cancelCommitForm = async () => { - const findCancelBtn = () => wrapper.find('[type="reset"]'); - await findCancelBtn().trigger('click'); - }; - - describe('when the user commits changes to the current branch', () => { - beforeEach(async () => { - await submitCommit(); - }); - - it('calls the mutation with the default branch', () => { - expect(mockMutate).toHaveBeenCalledWith({ - mutation: expect.any(Object), - variables: { - ...mockVariables, - branch: mockDefaultBranch, - }, - }); - }); - - it('displays an alert to indicate success', () => { - expect(findAlert().text()).toMatchInterpolatedText( - 'Your changes have been successfully committed.', - ); - }); - - it('shows no saving state', () => { - expect(findCommitBtnLoadingIcon().exists()).toBe(false); - }); - - it('a second commit submits the latest sha, keeping the form updated', async () => { - await submitCommit(); - - expect(mockMutate).toHaveBeenCalledTimes(2); - expect(mockMutate).toHaveBeenLastCalledWith({ - mutation: expect.any(Object), - variables: { - ...mockVariables, - lastCommitId: mockCommitNextSha, - branch: mockDefaultBranch, - }, - }); - }); - }); - - describe('when the user commits changes to a new branch', () => { - const newBranch = 'new-branch'; - - beforeEach(async () => { - await submitCommit({ - branch: newBranch, - }); - }); - - it('calls the mutation with the new branch', () => { - expect(mockMutate).toHaveBeenCalledWith({ - mutation: expect.any(Object), - variables: { - ...mockVariables, - branch: newBranch, - }, - }); - }); - }); - - describe('when the user commits changes to open a new merge request', () => { - const newBranch = 'new-branch'; - - beforeEach(async () => { - await submitCommit({ - branch: newBranch, - openMergeRequest: true, - }); - }); - - it('redirects to the merge request page with source and target branches', () => { - const branchesQuery = objectToQuery({ - 'merge_request[source_branch]': newBranch, - 'merge_request[target_branch]': mockDefaultBranch, - }); - - expect(redirectTo).toHaveBeenCalledWith(`${mockNewMergeRequestPath}?${branchesQuery}`); - }); - }); - - describe('when the commit is ocurring', () => { - it('shows a saving state', async () => { - await mockMutate.mockImplementationOnce(() => { - expect(findCommitBtnLoadingIcon().exists()).toBe(true); - return Promise.resolve(); - }); - - await submitCommit({ - message: mockCommitMessage, - branch: mockDefaultBranch, - openMergeRequest: false, - }); - }); - }); - - describe('when the commit fails', () => { - it('shows an error message', async () => { - mockMutate.mockRejectedValueOnce(new Error('commit failed')); - - await submitCommit(); - - await waitForPromises(); - - expect(findAlert().text()).toMatchInterpolatedText( - 'The GitLab CI configuration could not be updated. commit failed', - ); - }); - - it('shows an unkown error', async () => { - mockMutate.mockRejectedValueOnce(); - - await submitCommit(); - - await waitForPromises(); - - expect(findAlert().text()).toMatchInterpolatedText( - 'The GitLab CI configuration could not be updated.', - ); - }); - }); - - describe('when the commit form is cancelled', () => { - const otherContent = 'other content'; - - beforeEach(async () => { - findTextEditor().vm.$emit('input', otherContent); - await nextTick(); - }); - - it('content is restored after cancel is called', async () => { - await cancelCommitForm(); - - expect(findEditorLite().attributes('value')).toBe(mockCiYml); - }); - }); - }); - }); - describe('when queries are called', () => { beforeEach(() => { mockBlobContentData.mockResolvedValue(mockCiYml); @@ -422,14 +126,12 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { await waitForPromises(); }); - it('shows editor and commit form', () => { - expect(findEditorLite().exists()).toBe(true); - expect(findTextEditor().exists()).toBe(true); + it('shows pipeline editor home component', () => { + expect(findEditorHome().exists()).toBe(true); }); - it('no error is shown when data is set', async () => { + it('no error is shown when data is set', () => { expect(findAlert().exists()).toBe(false); - expect(findEditorLite().attributes('value')).toBe(mockCiYml); }); it('ci config query is called with correct variables', async () => { @@ -445,10 +147,10 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { }); describe('when no file exists', () => { - const expectedAlertMsg = + const noFileAlertMsg = 'There is no .gitlab-ci.yml file in this repository, please add one and visit the Pipeline Editor again.'; - it('shows a 404 error message and does not show editor or commit form', async () => { + it('shows a 404 error message and does not show editor home component', async () => { mockBlobContentData.mockRejectedValueOnce({ response: { status: httpStatusCodes.NOT_FOUND, @@ -458,12 +160,11 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { await waitForPromises(); - expect(findAlert().text()).toBe(expectedAlertMsg); - expect(findEditorLite().exists()).toBe(false); - expect(findTextEditor().exists()).toBe(false); + expect(findAlert().text()).toBe(noFileAlertMsg); + expect(findEditorHome().exists()).toBe(false); }); - it('shows a 400 error message and does not show editor or commit form', async () => { + it('shows a 400 error message and does not show editor home component', async () => { mockBlobContentData.mockRejectedValueOnce({ response: { status: httpStatusCodes.BAD_REQUEST, @@ -473,9 +174,8 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { await waitForPromises(); - expect(findAlert().text()).toBe(expectedAlertMsg); - expect(findEditorLite().exists()).toBe(false); - expect(findTextEditor().exists()).toBe(false); + expect(findAlert().text()).toBe(noFileAlertMsg); + expect(findEditorHome().exists()).toBe(false); }); it('shows a unkown error message', async () => { @@ -483,9 +183,60 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { createComponentWithApollo(); await waitForPromises(); - expect(findAlert().text()).toBe('The CI configuration was not loaded, please try again.'); - expect(findEditorLite().exists()).toBe(true); - expect(findTextEditor().exists()).toBe(true); + expect(findAlert().text()).toBe(wrapper.vm.$options.errorTexts[LOAD_FAILURE_UNKNOWN]); + expect(findEditorHome().exists()).toBe(true); + }); + }); + + describe('when the user commits', () => { + const updateFailureMessage = 'The GitLab CI configuration could not be updated.'; + + describe('and the commit mutation succeeds', () => { + beforeEach(() => { + createComponent(); + + findEditorHome().vm.$emit('commit', { type: COMMIT_SUCCESS }); + }); + + it('shows a confirmation message', () => { + expect(findAlert().text()).toBe(wrapper.vm.$options.successTexts[COMMIT_SUCCESS]); + }); + }); + describe('and the commit mutation fails', () => { + const commitFailedReasons = ['Commit failed']; + + beforeEach(() => { + createComponent(); + + findEditorHome().vm.$emit('showError', { + type: COMMIT_FAILURE, + reasons: commitFailedReasons, + }); + }); + + it('shows an error message', () => { + expect(findAlert().text()).toMatchInterpolatedText( + `${updateFailureMessage} ${commitFailedReasons[0]}`, + ); + }); + }); + describe('when an unknown error occurs', () => { + const unknownReasons = ['Commit failed']; + + beforeEach(() => { + createComponent(); + + findEditorHome().vm.$emit('showError', { + type: COMMIT_FAILURE, + reasons: unknownReasons, + }); + }); + + it('shows an error message', () => { + expect(findAlert().text()).toMatchInterpolatedText( + `${updateFailureMessage} ${unknownReasons[0]}`, + ); + }); }); }); }); |