Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/pipeline_editor/pipeline_editor_app_spec.js')
-rw-r--r--spec/frontend/pipeline_editor/pipeline_editor_app_spec.js363
1 files changed, 310 insertions, 53 deletions
diff --git a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
index 46523baadf3..c6dafc6e258 100644
--- a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
+++ b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
@@ -1,32 +1,82 @@
import { nextTick } from 'vue';
-import { shallowMount } from '@vue/test-utils';
-import { GlAlert, GlLoadingIcon, GlTabs, GlTab } from '@gitlab/ui';
+import { mount, shallowMount, createLocalVue } from '@vue/test-utils';
+import {
+ GlAlert,
+ GlButton,
+ GlFormInput,
+ GlFormTextarea,
+ GlLoadingIcon,
+ GlTabs,
+ GlTab,
+} from '@gitlab/ui';
+import waitForPromises from 'helpers/wait_for_promises';
+import VueApollo from 'vue-apollo';
+import createMockApollo from 'jest/helpers/mock_apollo_helper';
+
+import { redirectTo, refreshCurrentPage, objectToQuery } from '~/lib/utils/url_utility';
+import {
+ mockCiConfigPath,
+ mockCiYml,
+ mockCommitId,
+ mockCommitMessage,
+ mockDefaultBranch,
+ mockProjectPath,
+ mockNewMergeRequestPath,
+} from './mock_data';
-import { mockProjectPath, mockDefaultBranch, mockCiConfigPath, mockCiYml } from './mock_data';
import TextEditor from '~/pipeline_editor/components/text_editor.vue';
-import EditorLite from '~/vue_shared/components/editor_lite.vue';
import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue';
import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue';
+import CommitForm from '~/pipeline_editor/components/commit/commit_form.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,
+}));
describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
let wrapper;
- const createComponent = (
- { props = {}, data = {}, loading = false } = {},
+ let mockMutate;
+ let mockApollo;
+ let mockBlobContentData;
+
+ const createComponent = ({
+ props = {},
+ loading = false,
+ options = {},
mountFn = shallowMount,
- ) => {
+ } = {}) => {
+ mockMutate = jest.fn().mockResolvedValue({
+ data: {
+ commitCreate: {
+ errors: [],
+ commit: {},
+ },
+ },
+ });
+
wrapper = mountFn(PipelineEditorApp, {
propsData: {
- projectPath: mockProjectPath,
- defaultBranch: mockDefaultBranch,
ciConfigPath: mockCiConfigPath,
+ commitId: mockCommitId,
+ defaultBranch: mockDefaultBranch,
+ projectPath: mockProjectPath,
+ newMergeRequestPath: mockNewMergeRequestPath,
...props,
},
- data() {
- return data;
- },
stubs: {
GlTabs,
+ GlButton,
+ CommitForm,
+ EditorLite: {
+ template: '<div/>',
+ },
TextEditor,
},
mocks: {
@@ -36,45 +86,73 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
loading,
},
},
+ mutate: mockMutate,
},
},
+ // attachToDocument is required for input/submit events
+ attachToDocument: mountFn === mount,
+ ...options,
});
};
+ const createComponentWithApollo = ({ props = {}, mountFn = shallowMount } = {}) => {
+ mockApollo = createMockApollo([], {
+ Query: {
+ blobContent() {
+ return {
+ __typename: 'BlobContent',
+ rawData: mockBlobContentData(),
+ };
+ },
+ },
+ });
+
+ const options = {
+ localVue,
+ mocks: {},
+ apolloProvider: mockApollo,
+ };
+
+ createComponent({ props, options }, mountFn);
+ };
+
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findAlert = () => wrapper.find(GlAlert);
const findTabAt = i => wrapper.findAll(GlTab).at(i);
- const findEditorLite = () => wrapper.find(EditorLite);
+ const findTextEditor = () => wrapper.find(TextEditor);
+ const findCommitForm = () => wrapper.find(CommitForm);
+ const findCommitBtnLoadingIcon = () => wrapper.find('[type="submit"]').find(GlLoadingIcon);
beforeEach(() => {
- createComponent();
+ mockBlobContentData = jest.fn();
});
afterEach(() => {
+ mockBlobContentData.mockReset();
+ refreshCurrentPage.mockReset();
+ redirectTo.mockReset();
+ mockMutate.mockReset();
+
wrapper.destroy();
wrapper = null;
});
- it('displays content', () => {
- createComponent({ data: { content: mockCiYml } });
-
- expect(findLoadingIcon().exists()).toBe(false);
- expect(findEditorLite().props('value')).toBe(mockCiYml);
- });
-
it('displays a loading icon if the query is loading', () => {
createComponent({ loading: true });
expect(findLoadingIcon().exists()).toBe(true);
+ expect(findTextEditor().exists()).toBe(false);
});
describe('tabs', () => {
- it('displays tabs and their content', () => {
- createComponent({ data: { content: mockCiYml } });
+ beforeEach(() => {
+ createComponent();
+ });
+ it('displays tabs and their content', async () => {
expect(
findTabAt(0)
- .find(EditorLite)
+ .find(TextEditor)
.exists(),
).toBe(true);
expect(
@@ -85,55 +163,234 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => {
});
it('displays editor tab lazily, until editor is ready', async () => {
- createComponent({ data: { content: mockCiYml } });
-
expect(findTabAt(0).attributes('lazy')).toBe('true');
- findEditorLite().vm.$emit('editor-ready');
+ findTextEditor().vm.$emit('editor-ready');
+
await nextTick();
expect(findTabAt(0).attributes('lazy')).toBe(undefined);
});
});
- describe('when in error state', () => {
- class MockError extends Error {
- constructor(message, data) {
- super(message);
- if (data) {
- this.networkError = {
- response: { data },
- };
+ describe('when data is set', () => {
+ beforeEach(async () => {
+ createComponent({ mountFn: mount });
+
+ wrapper.setData({
+ content: mockCiYml,
+ contentModel: mockCiYml,
+ });
+
+ await nextTick();
+ });
+
+ it('displays content after the query loads', () => {
+ expect(findLoadingIcon().exists()).toBe(false);
+ expect(findTextEditor().attributes('value')).toBe(mockCiYml);
+ });
+
+ describe('commit form', () => {
+ const mockVariables = {
+ content: mockCiYml,
+ filePath: mockCiConfigPath,
+ lastCommitId: mockCommitId,
+ message: mockCommitMessage,
+ projectPath: mockProjectPath,
+ 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('shows a generic error', () => {
- const error = new MockError('An error message');
- createComponent({ data: { error } });
+ it('refreshes the page', () => {
+ expect(refreshCurrentPage).toHaveBeenCalled();
+ });
- expect(findAlert().text()).toBe('CI file could not be loaded: An error message');
+ it('shows no saving state', () => {
+ expect(findCommitBtnLoadingIcon().exists()).toBe(false);
+ });
+ });
+
+ 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,
+ },
+ });
+ });
+
+ it('refreshes the page', () => {
+ expect(refreshCurrentPage).toHaveBeenCalledWith();
+ });
+ });
+
+ 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 a the 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(findTextEditor().attributes('value')).toBe(mockCiYml);
+ });
+ });
+ });
+ });
+
+ describe('displays fetch content errors', () => {
+ it('no error is show when data is set', async () => {
+ mockBlobContentData.mockResolvedValue(mockCiYml);
+ createComponentWithApollo();
+
+ await waitForPromises();
+
+ expect(findAlert().exists()).toBe(false);
+ expect(findTextEditor().attributes('value')).toBe(mockCiYml);
});
- it('shows a ref missing error state', () => {
- const error = new MockError('Ref missing!', {
- error: 'ref is missing, ref is empty',
+ it('shows a 404 error message', async () => {
+ mockBlobContentData.mockRejectedValueOnce({
+ response: {
+ status: 404,
+ },
});
- createComponent({ data: { error } });
+ createComponentWithApollo();
- expect(findAlert().text()).toMatch(
- 'CI file could not be loaded: ref is missing, ref is empty',
- );
+ await waitForPromises();
+
+ expect(findAlert().text()).toMatch('No CI file found in this repository, please add one.');
});
- it('shows a file missing error state', async () => {
- const error = new MockError('File missing!', {
- message: 'file not found',
+ it('shows a 400 error message', async () => {
+ mockBlobContentData.mockRejectedValueOnce({
+ response: {
+ status: 400,
+ },
});
+ createComponentWithApollo();
+
+ await waitForPromises();
+
+ expect(findAlert().text()).toMatch(
+ 'Repository does not have a default branch, please set one.',
+ );
+ });
- await wrapper.setData({ error });
+ it('shows a unkown error message', async () => {
+ mockBlobContentData.mockRejectedValueOnce(new Error('My error!'));
+ createComponentWithApollo();
+ await waitForPromises();
- expect(findAlert().text()).toMatch('CI file could not be loaded: file not found');
+ expect(findAlert().text()).toMatch('The CI configuration was not loaded, please try again.');
});
});
});