Commit 629207ab authored by Tom Quirk's avatar Tom Quirk

Add new_branch_form for Jira Connect

Adds a form component for managing
the new branch via Jira connect feature.
parent 90f27cfe
<script>
import { GlFormGroup, GlButton, GlFormInput, GlForm, GlAlert } from '@gitlab/ui';
import { __ } from '~/locale';
import createBranchMutation from '../graphql/mutations/create_branch.mutation.graphql';
import ProjectDropdown from './project_dropdown.vue';
import SourceBranchDropdown from './source_branch_dropdown.vue';
const DEFAULT_ALERT_VARIANT = 'danger';
const DEFAULT_ALERT_PARAMS = {
title: '',
message: '',
variant: DEFAULT_ALERT_VARIANT,
primaryButtonLink: '',
primaryButtonText: '',
};
export default {
name: 'JiraConnectNewBranch',
components: {
GlFormGroup,
GlButton,
GlFormInput,
GlForm,
GlAlert,
ProjectDropdown,
SourceBranchDropdown,
},
props: {
initialBranchName: {
type: String,
required: false,
default: '',
},
},
data() {
return {
selectedProject: null,
selectedSourceBranchName: null,
branchName: this.initialBranchName,
createBranchLoading: false,
alertParams: {
...DEFAULT_ALERT_PARAMS,
},
};
},
computed: {
selectedProjectId() {
return this.selectedProject?.id;
},
showAlert() {
return Boolean(this.alertParams?.message);
},
disableSubmitButton() {
return !(this.selectedProject && this.selectedSourceBranchName && this.branchName);
},
},
methods: {
displayAlert({
title,
message,
variant = DEFAULT_ALERT_VARIANT,
primaryButtonLink,
primaryButtonText,
} = {}) {
this.alertParams = {
title,
message,
variant,
primaryButtonLink,
primaryButtonText,
};
},
onAlertDismiss() {
this.alertParams = {
...DEFAULT_ALERT_PARAMS,
};
},
async onProjectSelect(project) {
this.selectedProject = project;
this.selectedSourceBranchName = null; // reset branch selection
},
onSourceBranchSelect(branchName) {
this.selectedSourceBranchName = branchName;
},
onError({ title, message } = {}) {
this.displayAlert({
message,
title,
});
},
onSubmit() {
this.createBranch();
},
async createBranch() {
this.createBranchLoading = true;
this.$apollo
.mutate({
mutation: createBranchMutation,
variables: {
name: this.branchName,
ref: this.selectedSourceBranchName,
projectPath: this.selectedProject.fullPath,
},
})
.then(({ data }) => {
const { errors } = data.createBranch;
if (errors.length > 0) {
this.onError({
title: __('Failed to create branch.'),
message: errors[0],
});
return;
}
this.displayAlert({
title: __('New branch was successfully created.'),
message: __('You can now close this window and return to Jira'),
variant: 'success',
primaryButtonLink: 'jira',
primaryButtonText: __('Return to Jira'),
});
})
.catch(() => {
this.onError({
message: __('Failed to create branch. Please try again.'),
});
})
.finally(() => {
this.createBranchLoading = false;
});
},
},
i18n: {
pageTitle: __('New branch'),
projectDropdownLabel: __('Project'),
branchNameInputLabel: __('Branch name'),
sourceBranchDropdownLabel: __('Source branch'),
formSubmitButtonText: __('Create branch'),
},
};
</script>
<template>
<div>
<div class="gl-border-1 gl-border-b-solid gl-border-gray-100 gl-mb-5 gl-mt-7">
<h1 class="page-title">
{{ $options.i18n.pageTitle }}
</h1>
</div>
<gl-alert
v-if="showAlert"
class="gl-mb-5"
:variant="alertParams.variant"
:title="alertParams.title"
:primary-button-link="alertParams.primaryButtonLink"
:primary-button-text="alertParams.primaryButtonText"
@dismiss="onAlertDismiss"
>
{{ alertParams.message }}
</gl-alert>
<gl-form @submit.prevent="onSubmit">
<gl-form-group :label="$options.i18n.projectDropdownLabel" label-for="project-select">
<project-dropdown
id="project-select"
:selected-project="selectedProject"
@change="onProjectSelect"
@error="onError"
/>
</gl-form-group>
<gl-form-group :label="$options.i18n.branchNameInputLabel" label-for="branch-name-input">
<gl-form-input id="branch-name-input" v-model="branchName" type="text" required />
</gl-form-group>
<gl-form-group
:label="$options.i18n.sourceBranchDropdownLabel"
label-for="source-branch-select"
>
<source-branch-dropdown
id="source-branch-select"
:selected-project="selectedProject"
:selected-branch-name="selectedSourceBranchName"
@change="onSourceBranchSelect"
@error="onError"
/>
</gl-form-group>
<div class="form-actions">
<gl-button
:loading="createBranchLoading"
type="submit"
variant="confirm"
:disabled="disableSubmitButton"
>
{{ $options.i18n.formSubmitButtonText }}
</gl-button>
</div>
</gl-form>
</div>
</template>
mutation createBranch($name: String!, $projectPath: ID!, $ref: String!) {
createBranch(input: { name: $name, projectPath: $projectPath, ref: $ref }) {
clientMutationId
errors
}
}
import { GlAlert, GlForm, GlFormInput, GlButton } 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 NewBranchForm from '~/jira_connect/branches/components/new_branch_form.vue';
import ProjectDropdown from '~/jira_connect/branches/components/project_dropdown.vue';
import SourceBranchDropdown from '~/jira_connect/branches/components/source_branch_dropdown.vue';
import createBranchMutation from '~/jira_connect/branches/graphql/mutations/create_branch.mutation.graphql';
const mockProject = {
id: 'test',
fullPath: 'test-path',
repository: {
branchNames: ['main', 'f-test', 'release'],
rootRef: 'main',
},
};
const localVue = createLocalVue();
const mockCreateBranchMutationResponse = {
data: {
createBranch: {
clientMutationId: 1,
errors: [],
},
},
};
const mockCreateBranchMutationSuccess = jest
.fn()
.mockResolvedValue(mockCreateBranchMutationResponse);
const mockCreateBranchMutationFailed = jest.fn().mockRejectedValue(new Error('GraphQL error'));
const mockMutationLoading = jest.fn().mockReturnValue(new Promise(() => {}));
describe('NewBranchForm', () => {
let wrapper;
const findSourceBranchDropdown = () => wrapper.findComponent(SourceBranchDropdown);
const findProjectDropdown = () => wrapper.findComponent(ProjectDropdown);
const findAlert = () => wrapper.findComponent(GlAlert);
const findForm = () => wrapper.findComponent(GlForm);
const findInput = () => wrapper.findComponent(GlFormInput);
const findButton = () => wrapper.findComponent(GlButton);
const completeForm = async () => {
await findInput().vm.$emit('input', 'cool-branch-name');
await findProjectDropdown().vm.$emit('change', mockProject);
await findSourceBranchDropdown().vm.$emit('change', 'source-branch');
};
function createMockApolloProvider({
mockCreateBranchMutation = mockCreateBranchMutationSuccess,
} = {}) {
localVue.use(VueApollo);
const mockApollo = createMockApollo([[createBranchMutation, mockCreateBranchMutation]]);
return mockApollo;
}
function createComponent({ mockApollo, mountFn = shallowMount } = {}) {
wrapper = mountFn(NewBranchForm, {
localVue,
apolloProvider: mockApollo || createMockApolloProvider(),
});
}
afterEach(() => {
wrapper.destroy();
});
describe('when selecting items from dropdowns', () => {
describe('when a project is selected', () => {
it('sets the `selectedProject` prop for ProjectDropdown and SourceBranchDropdown', async () => {
createComponent();
const projectDropdown = findProjectDropdown();
await projectDropdown.vm.$emit('change', mockProject);
expect(projectDropdown.props('selectedProject')).toEqual(mockProject);
expect(findSourceBranchDropdown().props('selectedProject')).toEqual(mockProject);
});
});
describe('when a source branch is selected', () => {
it('sets the `selectedBranchName` prop for SourceBranchDropdown', async () => {
createComponent();
const mockBranchName = 'main';
const sourceBranchDropdown = findSourceBranchDropdown();
await sourceBranchDropdown.vm.$emit('change', mockBranchName);
expect(sourceBranchDropdown.props('selectedBranchName')).toBe(mockBranchName);
});
});
});
describe('when submitting form', () => {
describe('when form submission is loading', () => {
it('sets submit button `loading` prop to `true`', async () => {
createComponent({
mockApollo: createMockApolloProvider({
mockCreateBranchMutation: mockMutationLoading,
}),
});
await completeForm();
await findForm().vm.$emit('submit', new Event('submit'));
await waitForPromises();
expect(findButton().props('loading')).toBe(true);
});
});
describe('when form submission is successful', () => {
beforeEach(async () => {
createComponent();
await completeForm();
await findForm().vm.$emit('submit', new Event('submit'));
await waitForPromises();
});
it('displays a success message', () => {
const alert = findAlert();
expect(alert.exists()).toBe(true);
expect(alert.props()).toMatchObject({
primaryButtonLink: 'jira',
primaryButtonText: 'Return to Jira',
title: 'New branch was successfully created.',
variant: 'success',
});
});
it('called `createBranch` mutation correctly', () => {
expect(mockCreateBranchMutationSuccess).toHaveBeenCalledWith({
name: 'cool-branch-name',
projectPath: mockProject.fullPath,
ref: 'source-branch',
});
});
it('sets submit button `loading` prop to `false`', () => {
expect(findButton().props('loading')).toBe(false);
});
});
describe('when form submission fails', () => {
beforeEach(async () => {
createComponent({
mockApollo: createMockApolloProvider({
mockCreateBranchMutation: mockCreateBranchMutationFailed,
}),
});
await completeForm();
await findForm().vm.$emit('submit', new Event('submit'));
await waitForPromises();
});
it('displays an alert', () => {
const alert = findAlert();
expect(alert.exists()).toBe(true);
});
it('sets submit button `loading` prop to `false`', () => {
expect(findButton().props('loading')).toBe(false);
});
});
});
describe('error handling', () => {
describe.each`
component | componentName
${SourceBranchDropdown} | ${'SourceBranchDropdown'}
${ProjectDropdown} | ${'ProjectDropdown'}
`('when $componentName emits error', ({ component }) => {
const mockErrorMessage = 'oh noes!';
beforeEach(async () => {
createComponent();
await wrapper.findComponent(component).vm.$emit('error', { message: mockErrorMessage });
});
it('displays an alert', () => {
const alert = findAlert();
expect(alert.exists()).toBe(true);
expect(alert.text()).toBe(mockErrorMessage);
});
describe('when alert is dismissed', () => {
it('hides alert', async () => {
const alert = findAlert();
expect(alert.exists()).toBe(true);
await alert.vm.$emit('dismiss');
expect(alert.exists()).toBe(false);
});
});
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment