Commit 9cbc13c7 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'ide-new-branch' into 'master'

IDE create new branch

Closes #4545

See merge request gitlab-org/gitlab-ee!4534
parents 6bfc4e5f 5bb9a78d
<script>
import { mapState } from 'vuex';
import { sprintf, __ } from '../../../locale';
import * as consts from '../../stores/modules/commit/constants';
import RadioGroup from './radio_group.vue';
export default {
components: {
RadioGroup,
},
computed: {
...mapState([
'currentBranchId',
]),
newMergeRequestHelpText() {
return sprintf(
__('Creates a new branch from %{branchName} and re-directs to create a new merge request'),
{ branchName: this.currentBranchId },
);
},
commitToCurrentBranchText() {
return sprintf(
__('Commit to %{branchName} branch'),
{ branchName: `<strong>${this.currentBranchId}</strong>` },
false,
);
},
commitToNewBranchText() {
return sprintf(
__('Creates a new branch from %{branchName}'),
{ branchName: this.currentBranchId },
);
},
},
commitToCurrentBranch: consts.COMMIT_TO_CURRENT_BRANCH,
commitToNewBranch: consts.COMMIT_TO_NEW_BRANCH,
commitToNewBranchMR: consts.COMMIT_TO_NEW_BRANCH_MR,
};
</script>
<template>
<div class="append-bottom-15 ide-commit-radios">
<radio-group
:value="$options.commitToCurrentBranch"
:checked="true"
>
<span
v-html="commitToCurrentBranchText"
>
</span>
</radio-group>
<radio-group
:value="$options.commitToNewBranch"
:label="__('Create a new branch')"
:show-input="true"
:help-text="commitToNewBranchText"
/>
<radio-group
:value="$options.commitToNewBranchMR"
:label="__('Create a new branch and merge request')"
:show-input="true"
:help-text="newMergeRequestHelpText"
/>
</div>
</template>
<script>
import { mapActions, mapState, mapGetters } from 'vuex';
import tooltip from '../../../vue_shared/directives/tooltip';
export default {
directives: {
tooltip,
},
props: {
value: {
type: String,
required: true,
},
label: {
type: String,
required: false,
default: null,
},
checked: {
type: Boolean,
required: false,
default: false,
},
showInput: {
type: Boolean,
required: false,
default: false,
},
helpText: {
type: String,
required: false,
default: null,
},
},
computed: {
...mapState('commit', [
'commitAction',
]),
...mapGetters('commit', [
'newBranchName',
]),
},
methods: {
...mapActions('commit', [
'updateCommitAction',
'updateBranchName',
]),
},
};
</script>
<template>
<fieldset>
<label>
<input
type="radio"
name="commit-action"
:value="value"
@change="updateCommitAction($event.target.value)"
:checked="checked"
v-once
/>
<span class="prepend-left-10">
<template v-if="label">
{{ label }}
</template>
<slot v-else></slot>
<span
v-if="helpText"
v-tooltip
class="help-block inline"
:title="helpText"
>
<i
class="fa fa-question-circle"
aria-hidden="true"
>
</i>
</span>
</span>
</label>
<div
v-if="commitAction === value && showInput"
class="ide-commit-new-branch"
>
<input
type="text"
class="form-control"
:placeholder="newBranchName"
@input="updateBranchName($event.target.value)"
/>
</div>
</fieldset>
</template>
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
}, },
data() { data() {
return { return {
width: 290, width: 340,
}; };
}, },
computed: { computed: {
...@@ -120,7 +120,7 @@ ...@@ -120,7 +120,7 @@
<panel-resizer <panel-resizer
:size.sync="width" :size.sync="width"
:enabled="!rightPanelCollapsed" :enabled="!rightPanelCollapsed"
:start-size="290" :start-size="340"
:min-size="200" :min-size="200"
:max-size="maxSize" :max-size="maxSize"
@resize-start="resizingStarted" @resize-start="resizingStarted"
......
<script> <script>
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions, mapGetters } from 'vuex';
import * as consts from '../stores/modules/commit/constants';
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
import icon from '../../vue_shared/components/icon.vue'; import icon from '../../vue_shared/components/icon.vue';
import modal from '../../vue_shared/components/modal.vue'; import modal from '../../vue_shared/components/modal.vue';
import commitFilesList from './commit_sidebar/list.vue'; import commitFilesList from './commit_sidebar/list.vue';
import Actions from './commit_sidebar/actions.vue';
import LoadingButton from '../../vue_shared/components/loading_button.vue';
export default { export default {
components: { components: {
modal, modal,
icon, icon,
commitFilesList, commitFilesList,
Actions,
LoadingButton,
}, },
directives: { directives: {
tooltip, tooltip,
...@@ -24,14 +29,6 @@ export default { ...@@ -24,14 +29,6 @@ export default {
required: true, required: true,
}, },
}, },
data() {
return {
showNewBranchModal: false,
submitCommitsLoading: false,
startNewMR: false,
commitMessage: '',
};
},
computed: { computed: {
...mapState([ ...mapState([
'currentProjectId', 'currentProjectId',
...@@ -40,74 +37,40 @@ export default { ...@@ -40,74 +37,40 @@ export default {
'lastCommitMsg', 'lastCommitMsg',
'changedFiles', 'changedFiles',
]), ]),
commitButtonDisabled() { ...mapState('commit', [
return this.commitMessage === '' || this.submitCommitsLoading || !this.changedFiles.length; 'commitMessage',
}, 'submitCommitLoading',
commitMessageCount() { ]),
return this.commitMessage.length; ...mapGetters('commit', [
}, 'commitButtonDisabled',
'discardDraftButtonDisabled',
'branchName',
]),
statusSvg() { statusSvg() {
return this.lastCommitMsg ? this.committedStateSvgPath : this.noChangesStateSvgPath; return this.lastCommitMsg ? this.committedStateSvgPath : this.noChangesStateSvgPath;
}, },
}, },
methods: { methods: {
...mapActions([ ...mapActions([
'checkCommitStatus',
'commitChanges',
'getTreeData', 'getTreeData',
'setPanelCollapsedStatus', 'setPanelCollapsedStatus',
]), ]),
makeCommit(newBranch = false) { ...mapActions('commit', [
const createNewBranch = newBranch || this.startNewMR; 'updateCommitMessage',
'discardDraft',
const payload = { 'commitChanges',
branch: createNewBranch ? 'updateCommitAction',
`${this.currentBranchId}-${new Date().getTime().toString()}` : ]),
this.currentBranchId,
commit_message: this.commitMessage,
actions: this.changedFiles.map(f => ({
action: f.tempFile ? 'create' : 'update',
file_path: f.path,
content: f.content,
encoding: f.base64 ? 'base64' : 'text',
})),
start_branch: createNewBranch ? this.currentBranchId : undefined,
};
this.showNewBranchModal = false;
this.submitCommitsLoading = true;
this.commitChanges({ payload, newMr: this.startNewMR })
.then(() => {
this.submitCommitsLoading = false;
this.commitMessage = '';
this.startNewMR = false;
})
.catch(() => {
this.submitCommitsLoading = false;
});
},
tryCommit() {
this.submitCommitsLoading = true;
this.checkCommitStatus()
.then((branchChanged) => {
if (branchChanged) {
this.showNewBranchModal = true;
} else {
this.makeCommit();
}
})
.catch(() => {
this.submitCommitsLoading = false;
});
},
toggleCollapsed() { toggleCollapsed() {
this.setPanelCollapsedStatus({ this.setPanelCollapsedStatus({
side: 'right', side: 'right',
collapsed: !this.rightPanelCollapsed, collapsed: !this.rightPanelCollapsed,
}); });
}, },
forceCreateNewBranch() {
return this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH)
.then(() => this.commitChanges());
},
}, },
}; };
</script> </script>
...@@ -120,15 +83,17 @@ export default { ...@@ -120,15 +83,17 @@ export default {
}" }"
> >
<modal <modal
v-if="showNewBranchModal" id="ide-create-branch-modal"
:primary-button-label="__('Create new branch')" :primary-button-label="__('Create new branch')"
kind="primary" kind="success"
:title="__('Branch has changed')" :title="__('Branch has changed')"
:text="__(`This branch has changed since @submit="forceCreateNewBranch"
you started editing. Would you like to create a new branch?`)" >
@cancel="showNewBranchModal = false" <template slot="body">
@submit="makeCommit(true)" {{ __(`This branch has changed since you started editing.
/> Would you like to create a new branch?`) }}
</template>
</modal>
<commit-files-list <commit-files-list
title="Staged" title="Staged"
:file-list="changedFiles" :file-list="changedFiles"
...@@ -140,51 +105,36 @@ you started editing. Would you like to create a new branch?`)" ...@@ -140,51 +105,36 @@ you started editing. Would you like to create a new branch?`)"
> >
<form <form
class="form-horizontal multi-file-commit-form" class="form-horizontal multi-file-commit-form"
@submit.prevent="tryCommit" @submit.prevent.stop="commitChanges"
v-if="!rightPanelCollapsed" v-if="!rightPanelCollapsed"
> >
<div class="multi-file-commit-fieldset"> <div class="multi-file-commit-fieldset">
<textarea <textarea
class="form-control multi-file-commit-message ref-name" class="form-control multi-file-commit-message"
name="commit-message" name="commit-message"
v-model="commitMessage" :value="commitMessage"
placeholder="Commit message" :placeholder="__('Write a commit message...')"
@input="updateCommitMessage($event.target.value)"
> >
</textarea> </textarea>
</div> </div>
<div class="multi-file-commit-fieldset"> <div class="clearfix prepend-top-15">
<label <actions />
v-tooltip <loading-button
title="Create a new merge request with these changes" :loading="submitCommitLoading"
data-container="body" :disabled="commitButtonDisabled"
data-placement="top" container-class="btn btn-success btn-sm pull-left"
> :label="__('Commit')"
<input @click="commitChanges"
type="checkbox"
v-model="startNewMR"
/> />
{{ __('Merge Request') }}
</label>
<button <button
type="submit" v-if="!discardDraftButtonDisabled"
:disabled="commitButtonDisabled" type="button"
class="btn btn-default btn-sm append-right-10 prepend-left-10" class="btn btn-default btn-sm pull-right"
:class="{ disabled: submitCommitsLoading }" @click="discardDraft"
> >
<i {{ __('Discard draft') }}
v-if="submitCommitsLoading"
class="js-commit-loading-icon fa fa-spinner fa-spin"
aria-hidden="true"
aria-label="loading"
>
</i>
{{ __('Commit') }}
</button> </button>
<div
class="multi-file-commit-message-count"
>
{{ commitMessageCount }}
</div>
</div> </div>
</form> </form>
</template> </template>
...@@ -216,8 +166,7 @@ you started editing. Would you like to create a new branch?`)" ...@@ -216,8 +166,7 @@ you started editing. Would you like to create a new branch?`)"
<h4> <h4>
{{ __('All changes are committed') }} {{ __('All changes are committed') }}
</h4> </h4>
<p> <p v-html="lastCommitMsg">
{{ lastCommitMsg }}
</p> </p>
</div> </div>
</div> </div>
......
import Vue from 'vue';
export default new Vue();
/* global monaco */ /* global monaco */
import Disposable from './disposable'; import Disposable from './disposable';
import eventHub from '../../eventhub';
export default class Model { export default class Model {
constructor(monaco, file) { constructor(monaco, file) {
...@@ -22,6 +23,9 @@ export default class Model { ...@@ -22,6 +23,9 @@ export default class Model {
); );
this.events = new Map(); this.events = new Map();
this.updateContent = this.updateContent.bind(this);
eventHub.$on(`editor.update.model.content.${this.file.path}`, this.updateContent);
} }
get url() { get url() {
...@@ -61,8 +65,15 @@ export default class Model { ...@@ -61,8 +65,15 @@ export default class Model {
); );
} }
updateContent(content) {
this.getModel().setValue(content);
this.getOriginalModel().setValue(content);
}
dispose() { dispose() {
this.disposable.dispose(); this.disposable.dispose();
this.events.clear(); this.events.clear();
eventHub.$off(`editor.update.model.content.${this.file.path}`, this.updateContent);
} }
} }
import Vue from 'vue'; import Vue from 'vue';
import { visitUrl } from '../../lib/utils/url_utility'; import { visitUrl } from '../../lib/utils/url_utility';
import flash from '../../flash';
import service from '../services';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { stripHtml } from '../../lib/utils/text_utility';
export const redirectToUrl = (_, url) => visitUrl(url); export const redirectToUrl = (_, url) => visitUrl(url);
...@@ -51,84 +48,6 @@ export const setResizingStatus = ({ commit }, resizing) => { ...@@ -51,84 +48,6 @@ export const setResizingStatus = ({ commit }, resizing) => {
commit(types.SET_RESIZING_STATUS, resizing); commit(types.SET_RESIZING_STATUS, resizing);
}; };
export const checkCommitStatus = ({ state }) =>
service
.getBranchData(state.currentProjectId, state.currentBranchId)
.then(({ data }) => {
const { id } = data.commit;
const selectedBranch =
state.projects[state.currentProjectId].branches[state.currentBranchId];
if (selectedBranch.workingReference !== id) {
return true;
}
return false;
})
.catch(() => flash('Error checking branch data. Please try again.', 'alert', document, null, false, true));
export const commitChanges = (
{ commit, state, dispatch },
{ payload, newMr },
) =>
service
.commit(state.currentProjectId, payload)
.then(({ data }) => {
const { branch } = payload;
if (!data.short_id) {
flash(data.message, 'alert', document, null, false, true);
return;
}
const selectedProject = state.projects[state.currentProjectId];
const lastCommit = {
commit_path: `${selectedProject.web_url}/commit/${data.id}`,
commit: {
message: data.message,
authored_date: data.committed_date,
},
};
let commitMsg = `Your changes have been committed. Commit ${data.short_id}`;
if (data.stats) {
commitMsg += ` with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`;
}
commit(types.SET_LAST_COMMIT_MSG, commitMsg);
if (newMr) {
dispatch('discardAllChanges');
dispatch(
'redirectToUrl',
`${selectedProject.web_url}/merge_requests/new?merge_request%5Bsource_branch%5D=${branch}`,
);
} else {
commit(types.SET_BRANCH_WORKING_REFERENCE, {
projectId: state.currentProjectId,
branchId: state.currentBranchId,
reference: data.id,
});
state.changedFiles.forEach((entry) => {
commit(types.SET_LAST_COMMIT_DATA, {
entry,
lastCommit,
});
});
dispatch('discardAllChanges');
window.scrollTo(0, 0);
}
})
.catch((err) => {
let errMsg = 'Error committing changes. Please try again.';
if (err.response.data && err.response.data.message) {
errMsg += ` (${stripHtml(err.response.data.message)})`;
}
flash(errMsg, 'alert', document, null, false, true);
window.dispatchEvent(new Event('resize'));
});
export const createTempEntry = ( export const createTempEntry = (
{ state, dispatch }, { state, dispatch },
{ projectId, branchId, parent, name, type, content = '', base64 = false }, { projectId, branchId, parent, name, type, content = '', base64 = false },
......
...@@ -4,6 +4,7 @@ import state from './state'; ...@@ -4,6 +4,7 @@ import state from './state';
import * as actions from './actions'; import * as actions from './actions';
import * as getters from './getters'; import * as getters from './getters';
import mutations from './mutations'; import mutations from './mutations';
import commitModule from './modules/commit';
Vue.use(Vuex); Vue.use(Vuex);
...@@ -12,4 +13,7 @@ export default new Vuex.Store({ ...@@ -12,4 +13,7 @@ export default new Vuex.Store({
actions, actions,
mutations, mutations,
getters, getters,
modules: {
commit: commitModule,
},
}); });
import { sprintf, __ } from '../../../../locale';
import * as types from './mutation_types';
import * as consts from './constants';
import * as rootTypes from '../../mutation_types';
import { createCommitPayload, createNewMergeRequestUrl } from '../../utils';
import router from '../../../ide_router';
import service from '../../../services';
import flash from '../../../../flash';
import { stripHtml } from '../../../../lib/utils/text_utility';
import eventHub from '../../../eventhub';
export const updateCommitMessage = ({ commit }, message) => {
commit(types.UPDATE_COMMIT_MESSAGE, message);
};
export const discardDraft = ({ commit }) => {
commit(types.UPDATE_COMMIT_MESSAGE, '');
};
export const updateCommitAction = ({ commit }, commitAction) => {
commit(types.UPDATE_COMMIT_ACTION, commitAction);
};
export const updateBranchName = ({ commit }, branchName) => {
commit(types.UPDATE_NEW_BRANCH_NAME, branchName);
};
export const setLastCommitMessage = ({ rootState, commit }, data) => {
const currentProject = rootState.projects[rootState.currentProjectId];
const commitStats = data.stats ?
sprintf(
__('with %{additions} additions, %{deletions} deletions.'),
{ additions: data.stats.additions, deletions: data.stats.deletions },
)
: '';
const commitMsg = sprintf(
__('Your changes have been committed. Commit %{commitId} %{commitStats}'),
{
commitId: `<a href="${currentProject.web_url}/commit/${data.short_id}" class="commit-sha">${data.short_id}</a>`,
commitStats,
},
false,
);
commit(rootTypes.SET_LAST_COMMIT_MSG, commitMsg, { root: true });
};
export const checkCommitStatus = ({ rootState }) =>
service
.getBranchData(rootState.currentProjectId, rootState.currentBranchId)
.then(({ data }) => {
const { id } = data.commit;
const selectedBranch =
rootState.projects[rootState.currentProjectId].branches[rootState.currentBranchId];
if (selectedBranch.workingReference !== id) {
return true;
}
return false;
})
.catch(() => flash(__('Error checking branch data. Please try again.'), 'alert', document, null, false, true));
export const updateFilesAfterCommit = (
{ commit, dispatch, state, rootState, rootGetters },
{ data, branch },
) => {
const selectedProject = rootState.projects[rootState.currentProjectId];
const lastCommit = {
commit_path: `${selectedProject.web_url}/commit/${data.id}`,
commit: {
id: data.id,
message: data.message,
authored_date: data.committed_date,
author_name: data.committer_name,
},
};
commit(rootTypes.SET_BRANCH_WORKING_REFERENCE, {
projectId: rootState.currentProjectId,
branchId: rootState.currentBranchId,
reference: data.id,
}, { root: true });
rootState.changedFiles.forEach((entry) => {
commit(rootTypes.SET_LAST_COMMIT_DATA, {
entry,
lastCommit,
}, { root: true });
commit(rootTypes.SET_FILE_RAW_DATA, {
file: entry,
raw: entry.content,
}, { root: true });
eventHub.$emit(`editor.update.model.content.${entry.path}`, entry.raw);
});
commit(rootTypes.REMOVE_ALL_CHANGES_FILES, null, { root: true });
if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH) {
router.push(`/project/${rootState.currentProjectId}/blob/${branch}/${rootGetters.activeFile.path}`);
}
dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH);
};
export const commitChanges = ({ commit, state, getters, dispatch, rootState }) => {
const newBranch = state.commitAction !== consts.COMMIT_TO_CURRENT_BRANCH;
const payload = createCommitPayload(getters.branchName, newBranch, state, rootState);
const getCommitStatus = newBranch ? Promise.resolve(false) : dispatch('checkCommitStatus');
commit(types.UPDATE_LOADING, true);
return getCommitStatus.then(branchChanged => new Promise((resolve) => {
if (branchChanged) {
// show the modal with a Bootstrap call
$('#ide-create-branch-modal').modal('show');
} else {
resolve();
}
}))
.then(() => service.commit(rootState.currentProjectId, payload))
.then(({ data }) => {
commit(types.UPDATE_LOADING, false);
if (!data.short_id) {
flash(data.message, 'alert', document, null, false, true);
return;
}
dispatch('setLastCommitMessage', data);
if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH_MR) {
dispatch(
'redirectToUrl',
createNewMergeRequestUrl(
rootState.projects[rootState.currentProjectId].web_url,
getters.branchName,
rootState.currentBranchId,
),
{ root: true },
);
} else {
dispatch('updateFilesAfterCommit', { data, branch: getters.branchName });
}
})
.catch((err) => {
let errMsg = __('Error committing changes. Please try again.');
if (err.response.data && err.response.data.message) {
errMsg += ` (${stripHtml(err.response.data.message)})`;
}
flash(errMsg, 'alert', document, null, false, true);
window.dispatchEvent(new Event('resize'));
commit(types.UPDATE_LOADING, false);
});
};
export const COMMIT_TO_CURRENT_BRANCH = '1';
export const COMMIT_TO_NEW_BRANCH = '2';
export const COMMIT_TO_NEW_BRANCH_MR = '3';
import * as consts from './constants';
export const discardDraftButtonDisabled = state => state.commitMessage === '' || state.submitCommitLoading;
export const commitButtonDisabled = (state, getters, rootState) =>
getters.discardDraftButtonDisabled || !rootState.changedFiles.length;
export const newBranchName = (state, _, rootState) =>
`${gon.current_username}-${rootState.currentBranchId}-patch-${`${new Date().getTime()}`.substr(-5)}`;
export const branchName = (state, getters, rootState) => {
if (
state.commitAction === consts.COMMIT_TO_NEW_BRANCH ||
state.commitAction === consts.COMMIT_TO_NEW_BRANCH_MR
) {
if (state.newBranchName === '') {
return getters.newBranchName;
}
return state.newBranchName;
}
return rootState.currentBranchId;
};
import state from './state';
import mutations from './mutations';
import * as actions from './actions';
import * as getters from './getters';
export default {
namespaced: true,
state: state(),
mutations,
actions,
getters,
};
export const UPDATE_COMMIT_MESSAGE = 'UPDATE_COMMIT_MESSAGE';
export const UPDATE_COMMIT_ACTION = 'UPDATE_COMMIT_ACTION';
export const UPDATE_NEW_BRANCH_NAME = 'UPDATE_NEW_BRANCH_NAME';
export const UPDATE_LOADING = 'UPDATE_LOADING';
import * as types from './mutation_types';
export default {
[types.UPDATE_COMMIT_MESSAGE](state, commitMessage) {
Object.assign(state, {
commitMessage,
});
},
[types.UPDATE_COMMIT_ACTION](state, commitAction) {
Object.assign(state, {
commitAction,
});
},
[types.UPDATE_NEW_BRANCH_NAME](state, newBranchName) {
Object.assign(state, {
newBranchName,
});
},
[types.UPDATE_LOADING](state, submitCommitLoading) {
Object.assign(state, {
submitCommitLoading,
});
},
};
export default () => ({
commitMessage: '',
commitAction: '1',
newBranchName: '',
submitCommitLoading: false,
});
...@@ -187,3 +187,18 @@ export const createOrMergeEntry = ({ projectId, ...@@ -187,3 +187,18 @@ export const createOrMergeEntry = ({ projectId,
level, level,
}); });
}; };
export const createCommitPayload = (branch, newBranch, state, rootState) => ({
branch,
commit_message: state.commitMessage,
actions: rootState.changedFiles.map(f => ({
action: f.tempFile ? 'create' : 'update',
file_path: f.path,
content: f.content,
encoding: f.base64 ? 'base64' : 'text',
})),
start_branch: newBranch ? rootState.currentBranchId : undefined,
});
export const createNewMergeRequestUrl = (projectUrl, source, target) =>
`${projectUrl}/merge_requests/new?merge_request[source_branch]=${source}&merge_request[target_branch]=${target}`;
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
.ide-view { .ide-view {
display: flex; display: flex;
height: calc(100vh - #{$header-height}); height: calc(100vh - #{$header-height});
margin-top: 40px;
color: $almost-black; color: $almost-black;
border-top: 1px solid $white-dark; border-top: 1px solid $white-dark;
border-bottom: 1px solid $white-dark; border-bottom: 1px solid $white-dark;
...@@ -265,7 +266,7 @@ table.table tr td.multi-file-table-name { ...@@ -265,7 +266,7 @@ table.table tr td.multi-file-table-name {
display: flex; display: flex;
position: relative; position: relative;
flex-direction: column; flex-direction: column;
width: 290px; width: 340px;
padding: 0; padding: 0;
background-color: $gray-light; background-color: $gray-light;
padding-right: 3px; padding-right: 3px;
...@@ -465,20 +466,14 @@ table.table tr td.multi-file-table-name { ...@@ -465,20 +466,14 @@ table.table tr td.multi-file-table-name {
.multi-file-commit-form { .multi-file-commit-form {
padding: $gl-padding; padding: $gl-padding;
border-top: 1px solid $white-dark; border-top: 1px solid $white-dark;
}
.multi-file-commit-fieldset {
display: flex;
align-items: center;
padding-bottom: 12px;
.btn { .btn {
flex: 1; font-size: $gl-font-size;
} }
} }
.multi-file-commit-message.form-control { .multi-file-commit-message.form-control {
height: 80px; height: 160px;
resize: none; resize: none;
} }
...@@ -639,3 +634,18 @@ table.table tr td.multi-file-table-name { ...@@ -639,3 +634,18 @@ table.table tr td.multi-file-table-name {
left: 0; left: 0;
} }
} }
.ide-commit-radios {
label {
font-weight: normal;
}
.help-block {
margin-top: 0;
line-height: 0;
}
}
.ide-commit-new-branch {
margin-left: 25px;
}
import Vue from 'vue';
import store from '~/ide/stores';
import commitActions from '~/ide/components/commit_sidebar/actions.vue';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { resetStore } from '../../helpers';
describe('IDE commit sidebar actions', () => {
let vm;
beforeEach((done) => {
const Component = Vue.extend(commitActions);
vm = createComponentWithStore(Component, store);
vm.$store.state.currentBranchId = 'master';
vm.$mount();
Vue.nextTick(done);
});
afterEach(() => {
vm.$destroy();
resetStore(vm.$store);
});
it('renders 3 groups', () => {
expect(vm.$el.querySelectorAll('input[type="radio"]').length).toBe(3);
});
it('renders current branch text', () => {
expect(vm.$el.textContent).toContain('Commit to master branch');
});
});
import Vue from 'vue';
import store from '~/ide/stores';
import radioGroup from '~/ide/components/commit_sidebar/radio_group.vue';
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
import { resetStore } from '../../helpers';
describe('IDE commit sidebar radio group', () => {
let vm;
beforeEach((done) => {
const Component = Vue.extend(radioGroup);
store.state.commit.commitAction = '2';
vm = createComponentWithStore(Component, store, {
value: '1',
label: 'test',
checked: true,
});
vm.$mount();
Vue.nextTick(done);
});
afterEach(() => {
vm.$destroy();
resetStore(vm.$store);
});
it('uses label if present', () => {
expect(vm.$el.textContent).toContain('test');
});
it('uses slot if label is not present', (done) => {
vm.$destroy();
vm = new Vue({
components: {
radioGroup,
},
store,
template: `
<radio-group
value="1"
>
Testing slot
</radio-group>
`,
});
vm.$mount();
Vue.nextTick(() => {
expect(vm.$el.textContent).toContain('Testing slot');
done();
});
});
it('updates store when changing radio button', (done) => {
vm.$el.querySelector('input').dispatchEvent(new Event('change'));
Vue.nextTick(() => {
expect(store.state.commit.commitAction).toBe('1');
done();
});
});
it('renders helpText tooltip', (done) => {
vm.helpText = 'help text';
Vue.nextTick(() => {
const help = vm.$el.querySelector('.help-block');
expect(help).not.toBeNull();
expect(help.getAttribute('data-original-title')).toBe('help text');
done();
});
});
describe('with input', () => {
beforeEach((done) => {
vm.$destroy();
const Component = Vue.extend(radioGroup);
store.state.commit.commitAction = '1';
vm = createComponentWithStore(Component, store, {
value: '1',
label: 'test',
checked: true,
showInput: true,
});
vm.$mount();
Vue.nextTick(done);
});
it('renders input box when commitAction matches value', () => {
expect(vm.$el.querySelector('.form-control')).not.toBeNull();
});
it('hides input when commitAction doesnt match value', (done) => {
store.state.commit.commitAction = '2';
Vue.nextTick(() => {
expect(vm.$el.querySelector('.form-control')).toBeNull();
done();
});
});
it('updates branch name in store on input', (done) => {
const input = vm.$el.querySelector('.form-control');
input.value = 'testing-123';
input.dispatchEvent(new Event('input'));
Vue.nextTick(() => {
expect(store.state.commit.newBranchName).toBe('testing-123');
done();
});
});
});
});
import Vue from 'vue'; import Vue from 'vue';
import * as urlUtils from '~/lib/utils/url_utility';
import store from '~/ide/stores'; import store from '~/ide/stores';
import service from '~/ide/services'; import service from '~/ide/services';
import repoCommitSection from '~/ide/components/repo_commit_section.vue'; import repoCommitSection from '~/ide/components/repo_commit_section.vue';
...@@ -76,8 +75,6 @@ describe('RepoCommitSection', () => { ...@@ -76,8 +75,6 @@ describe('RepoCommitSection', () => {
committedStateSvgPath: 'svg', committedStateSvgPath: 'svg',
}).$mount(); }).$mount();
// Vue.nextTick();
expect(vm.$el.querySelector('.js-empty-state').textContent.trim()).toContain('No changes'); expect(vm.$el.querySelector('.js-empty-state').textContent.trim()).toContain('No changes');
expect(vm.$el.querySelector('.js-empty-state img').getAttribute('src')).toBe('nochangessvg'); expect(vm.$el.querySelector('.js-empty-state img').getAttribute('src')).toBe('nochangessvg');
}); });
...@@ -98,62 +95,57 @@ describe('RepoCommitSection', () => { ...@@ -98,62 +95,57 @@ describe('RepoCommitSection', () => {
expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeNull(); expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeNull();
}); });
describe('when submitting', () => { it('updates commitMessage in store on input', (done) => {
let changedFiles; const textarea = vm.$el.querySelector('textarea');
beforeEach(() => { textarea.value = 'testing commit message';
vm.commitMessage = 'testing';
changedFiles = JSON.parse(JSON.stringify(vm.$store.state.changedFiles));
spyOn(service, 'commit').and.returnValue(Promise.resolve({ textarea.dispatchEvent(new Event('input'));
data: {
short_id: '1', getSetTimeoutPromise()
stats: {}, .then(() => {
}, expect(vm.$store.state.commit.commitMessage).toBe('testing commit message');
})); })
.then(done)
.catch(done.fail);
}); });
it('allows you to submit', () => { describe('discard draft button', () => {
expect(vm.$el.querySelector('form .btn').disabled).toBeTruthy(); it('hidden when commitMessage is empty', () => {
expect(vm.$el.querySelector('.multi-file-commit-form .btn-default')).toBeNull();
}); });
it('submits commit', (done) => { it('resets commitMessage when clicking discard button', (done) => {
vm.makeCommit(); vm.$store.state.commit.commitMessage = 'testing commit message';
// Wait for the branch check to finish
getSetTimeoutPromise() getSetTimeoutPromise()
.then(() => Vue.nextTick())
.then(() => { .then(() => {
const args = service.commit.calls.allArgs()[0]; vm.$el.querySelector('.multi-file-commit-form .btn-default').click();
const { commit_message, actions, branch: payloadBranch } = args[1]; })
.then(Vue.nextTick)
expect(commit_message).toBe('testing'); .then(() => {
expect(actions.length).toEqual(2); expect(vm.$store.state.commit.commitMessage).not.toBe('testing commit message');
expect(payloadBranch).toEqual('master');
expect(actions[0].action).toEqual('update');
expect(actions[1].action).toEqual('update');
expect(actions[0].content).toEqual(changedFiles[0].content);
expect(actions[1].content).toEqual(changedFiles[1].content);
expect(actions[0].file_path).toEqual(changedFiles[0].path);
expect(actions[1].file_path).toEqual(changedFiles[1].path);
expect(vm.$el.querySelector('.js-empty-state').textContent.trim()).toContain('All changes are committed');
expect(vm.$el.querySelector('.js-empty-state img').getAttribute('src')).toBe('commitsvg');
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
}); });
});
it('redirects to MR creation page if start new MR checkbox checked', (done) => { describe('when submitting', () => {
spyOn(urlUtils, 'visitUrl'); beforeEach(() => {
vm.startNewMR = true; spyOn(vm, 'commitChanges');
});
vm.makeCommit(); it('calls commitChanges', (done) => {
vm.$store.state.commit.commitMessage = 'testing commit message';
getSetTimeoutPromise() getSetTimeoutPromise()
.then(() => Vue.nextTick())
.then(() => { .then(() => {
expect(urlUtils.visitUrl).toHaveBeenCalled(); vm.$el.querySelector('.multi-file-commit-form .btn-success').click();
})
.then(Vue.nextTick)
.then(() => {
expect(vm.commitChanges).toHaveBeenCalled();
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
......
import { decorateData } from '~/ide/stores/utils'; import { decorateData } from '~/ide/stores/utils';
import state from '~/ide/stores/state'; import state from '~/ide/stores/state';
import commitState from '~/ide/stores/modules/commit/state';
export const resetStore = (store) => { export const resetStore = (store) => {
store.replaceState(state()); const newState = {
...state(),
commit: commitState(),
};
store.replaceState(newState);
}; };
export const file = (name = 'name', id = name, type = '') => decorateData({ export const file = (name = 'name', id = name, type = '') => decorateData({
......
import Vue from 'vue'; import Vue from 'vue';
import * as urlUtils from '~/lib/utils/url_utility'; import * as urlUtils from '~/lib/utils/url_utility';
import store from '~/ide/stores'; import store from '~/ide/stores';
import service from '~/ide/services';
import { resetStore, file } from '../helpers'; import { resetStore, file } from '../helpers';
describe('Multi-file store actions', () => { describe('Multi-file store actions', () => {
...@@ -130,190 +129,6 @@ describe('Multi-file store actions', () => { ...@@ -130,190 +129,6 @@ describe('Multi-file store actions', () => {
}); });
}); });
describe('checkCommitStatus', () => {
beforeEach(() => {
store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'master';
store.state.projects.abcproject = {
branches: {
master: {
workingReference: '1',
},
},
};
});
it('calls service', (done) => {
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
data: {
commit: { id: '123' },
},
}));
store.dispatch('checkCommitStatus')
.then(() => {
expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master');
done();
})
.catch(done.fail);
});
it('returns true if current ref does not equal returned ID', (done) => {
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
data: {
commit: { id: '123' },
},
}));
store.dispatch('checkCommitStatus')
.then((val) => {
expect(val).toBeTruthy();
done();
})
.catch(done.fail);
});
it('returns false if current ref equals returned ID', (done) => {
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
data: {
commit: { id: '1' },
},
}));
store.dispatch('checkCommitStatus')
.then((val) => {
expect(val).toBeFalsy();
done();
})
.catch(done.fail);
});
});
describe('commitChanges', () => {
let payload;
beforeEach(() => {
spyOn(window, 'scrollTo');
document.body.innerHTML += '<div class="flash-container"></div>';
store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'master';
store.state.projects.abcproject = {
web_url: 'webUrl',
branches: {
master: {
workingReference: '1',
},
},
};
payload = {
branch: 'master',
};
});
afterEach(() => {
document.querySelector('.flash-container').remove();
});
describe('success', () => {
beforeEach(() => {
spyOn(service, 'commit').and.returnValue(Promise.resolve({
data: {
id: '123456',
short_id: '123',
message: 'test message',
committed_date: 'date',
stats: {
additions: '1',
deletions: '2',
},
},
}));
});
it('calls service', (done) => {
store.dispatch('commitChanges', { payload, newMr: false })
.then(() => {
expect(service.commit).toHaveBeenCalledWith('abcproject', payload);
done();
}).catch(done.fail);
});
it('sets last Commit Msg', (done) => {
store.dispatch('commitChanges', { payload, newMr: false })
.then(() => {
expect(store.state.lastCommitMsg).toBe(
'Your changes have been committed. Commit 123 with 1 additions, 2 deletions.',
);
done();
}).catch(done.fail);
});
it('adds commit data to changed files', (done) => {
const changedFile = file();
store.state.changedFiles.push(changedFile);
store.dispatch('commitChanges', { payload, newMr: false })
.then(() => {
expect(changedFile.lastCommit.message).toBe('test message');
done();
}).catch(done.fail);
});
it('scrolls to top of page', (done) => {
store.dispatch('commitChanges', { payload, newMr: false })
.then(() => {
expect(window.scrollTo).toHaveBeenCalledWith(0, 0);
done();
}).catch(done.fail);
});
it('redirects to new merge request page', (done) => {
spyOn(urlUtils, 'visitUrl');
store.dispatch('commitChanges', { payload, newMr: true })
.then(() => {
expect(urlUtils.visitUrl).toHaveBeenCalledWith('webUrl/merge_requests/new?merge_request%5Bsource_branch%5D=master');
done();
}).catch(done.fail);
});
});
describe('failed', () => {
beforeEach(() => {
spyOn(service, 'commit').and.returnValue(Promise.resolve({
data: {
message: 'failed message',
},
}));
});
it('shows failed message', (done) => {
store.dispatch('commitChanges', { payload, newMr: false })
.then(() => {
const alert = document.querySelector('.flash-container');
expect(alert.textContent.trim()).toBe(
'failed message',
);
done();
}).catch(done.fail);
});
});
});
describe('createTempEntry', () => { describe('createTempEntry', () => {
beforeEach(() => { beforeEach(() => {
store.state.trees['abcproject/mybranch'] = { store.state.trees['abcproject/mybranch'] = {
......
import store from '~/ide/stores';
import service from '~/ide/services';
import router from '~/ide/ide_router';
import eventHub from '~/ide/eventhub';
import * as consts from '~/ide/stores/modules/commit/constants';
import * as urlUtils from '~/lib/utils/url_utility';
import { resetStore, file } from '../../../helpers';
describe('IDE commit module actions', () => {
afterEach(() => {
resetStore(store);
});
describe('updateCommitMessage', () => {
it('updates store with new commit message', (done) => {
store.dispatch('commit/updateCommitMessage', 'testing')
.then(() => {
expect(store.state.commit.commitMessage).toBe('testing');
})
.then(done)
.catch(done.fail);
});
});
describe('discardDraft', () => {
it('resets commit message to blank', (done) => {
store.state.commit.commitMessage = 'testing';
store.dispatch('commit/discardDraft')
.then(() => {
expect(store.state.commit.commitMessage).not.toBe('testing');
})
.then(done)
.catch(done.fail);
});
});
describe('updateCommitAction', () => {
it('updates store with new commit action', (done) => {
store.dispatch('commit/updateCommitAction', '1')
.then(() => {
expect(store.state.commit.commitAction).toBe('1');
})
.then(done)
.catch(done.fail);
});
});
describe('updateBranchName', () => {
it('updates store with new branch name', (done) => {
store.dispatch('commit/updateBranchName', 'branch-name')
.then(() => {
expect(store.state.commit.newBranchName).toBe('branch-name');
})
.then(done)
.catch(done.fail);
});
});
describe('setLastCommitMessage', () => {
beforeEach(() => {
Object.assign(store.state, {
currentProjectId: 'abcproject',
projects: {
abcproject: {
web_url: 'http://testing',
},
},
});
});
it('updates commit message with short_id', (done) => {
store.dispatch('commit/setLastCommitMessage', { short_id: '123' })
.then(() => {
expect(store.state.lastCommitMsg).toContain(
'Your changes have been committed. Commit <a href="http://testing/commit/123" class="commit-sha">123</a>',
);
})
.then(done)
.catch(done.fail);
});
it('updates commit message with stats', (done) => {
store.dispatch('commit/setLastCommitMessage', {
short_id: '123',
stats: {
additions: '1',
deletions: '2',
},
})
.then(() => {
expect(store.state.lastCommitMsg).toBe('Your changes have been committed. Commit <a href="http://testing/commit/123" class="commit-sha">123</a> with 1 additions, 2 deletions.');
})
.then(done)
.catch(done.fail);
});
});
describe('checkCommitStatus', () => {
beforeEach(() => {
store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'master';
store.state.projects.abcproject = {
branches: {
master: {
workingReference: '1',
},
},
};
});
it('calls service', (done) => {
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
data: {
commit: { id: '123' },
},
}));
store.dispatch('commit/checkCommitStatus')
.then(() => {
expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master');
done();
})
.catch(done.fail);
});
it('returns true if current ref does not equal returned ID', (done) => {
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
data: {
commit: { id: '123' },
},
}));
store.dispatch('commit/checkCommitStatus')
.then((val) => {
expect(val).toBeTruthy();
done();
})
.catch(done.fail);
});
it('returns false if current ref equals returned ID', (done) => {
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
data: {
commit: { id: '1' },
},
}));
store.dispatch('commit/checkCommitStatus')
.then((val) => {
expect(val).toBeFalsy();
done();
})
.catch(done.fail);
});
});
describe('updateFilesAfterCommit', () => {
const data = {
id: '123',
message: 'testing commit message',
committed_date: '123',
committer_name: 'root',
};
const branch = 'master';
let f;
beforeEach(() => {
spyOn(eventHub, '$emit');
spyOn(router, 'push');
f = file('changedFile');
Object.assign(f, {
active: true,
content: 'file content',
});
store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'master';
store.state.projects.abcproject = {
web_url: 'web_url',
branches: {
master: {
workingReference: '',
},
},
};
store.state.changedFiles.push(f);
store.state.openFiles.push(f);
});
it('updates stores working reference', (done) => {
store.dispatch('commit/updateFilesAfterCommit', {
data,
branch,
})
.then(() => {
expect(
store.state.projects.abcproject.branches.master.workingReference,
).toBe(data.id);
})
.then(done)
.catch(done.fail);
});
it('removes all changed files', (done) => {
store.dispatch('commit/updateFilesAfterCommit', {
data,
branch,
})
.then(() => {
expect(store.state.changedFiles.length).toBe(0);
})
.then(done)
.catch(done.fail);
});
it('sets files commit data', (done) => {
store.dispatch('commit/updateFilesAfterCommit', {
data,
branch,
})
.then(() => {
expect(f.lastCommit.message).toBe(data.message);
})
.then(done)
.catch(done.fail);
});
it('updates raw content for changed file', (done) => {
store.dispatch('commit/updateFilesAfterCommit', {
data,
branch,
})
.then(() => {
expect(f.raw).toBe(f.content);
})
.then(done)
.catch(done.fail);
});
it('emits changed event for file', (done) => {
store.dispatch('commit/updateFilesAfterCommit', {
data,
branch,
})
.then(() => {
expect(eventHub.$emit).toHaveBeenCalledWith(`editor.update.model.content.${f.path}`, f.content);
})
.then(done)
.catch(done.fail);
});
it('pushes route to new branch if commitAction is new branch', (done) => {
store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
store.dispatch('commit/updateFilesAfterCommit', {
data,
branch,
})
.then(() => {
expect(router.push).toHaveBeenCalledWith(
`/project/abcproject/blob/master/${f.path}`,
);
})
.then(done)
.catch(done.fail);
});
it('resets stores commit actions', (done) => {
store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
store.dispatch('commit/updateFilesAfterCommit', {
data,
branch,
})
.then(() => {
expect(store.state.commit.commitAction).not.toBe(consts.COMMIT_TO_NEW_BRANCH);
})
.then(done)
.catch(done.fail);
});
});
describe('commitChanges', () => {
beforeEach(() => {
spyOn(router, 'push');
document.body.innerHTML += '<div class="flash-container"></div>';
store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'master';
store.state.projects.abcproject = {
web_url: 'webUrl',
branches: {
master: {
workingReference: '1',
},
},
};
store.state.changedFiles.push(file('changed'));
store.state.changedFiles[0].active = true;
store.state.openFiles = store.state.changedFiles;
store.state.commit.commitAction = '2';
store.state.commit.commitMessage = 'testing 123';
});
afterEach(() => {
document.querySelector('.flash-container').remove();
});
describe('success', () => {
beforeEach(() => {
spyOn(service, 'commit').and.returnValue(Promise.resolve({
data: {
id: '123456',
short_id: '123',
message: 'test message',
committed_date: 'date',
stats: {
additions: '1',
deletions: '2',
},
},
}));
});
it('calls service', (done) => {
store.dispatch('commit/commitChanges')
.then(() => {
expect(service.commit).toHaveBeenCalledWith('abcproject', {
branch: jasmine.anything(),
commit_message: 'testing 123',
actions: [{
action: 'update',
file_path: jasmine.anything(),
content: jasmine.anything(),
encoding: jasmine.anything(),
}],
start_branch: 'master',
});
done();
}).catch(done.fail);
});
it('pushes router to new route', (done) => {
store.dispatch('commit/commitChanges')
.then(() => {
expect(router.push).toHaveBeenCalledWith(
`/project/${store.state.currentProjectId}/blob/${store.getters['commit/newBranchName']}/changed`,
);
done();
}).catch(done.fail);
});
it('sets last Commit Msg', (done) => {
store.dispatch('commit/commitChanges')
.then(() => {
expect(store.state.lastCommitMsg).toBe(
'Your changes have been committed. Commit <a href="webUrl/commit/123" class="commit-sha">123</a> with 1 additions, 2 deletions.',
);
done();
}).catch(done.fail);
});
it('adds commit data to changed files', (done) => {
store.dispatch('commit/commitChanges')
.then(() => {
expect(store.state.openFiles[0].lastCommit.message).toBe('test message');
done();
}).catch(done.fail);
});
it('redirects to new merge request page', (done) => {
spyOn(urlUtils, 'visitUrl');
store.state.commit.commitAction = '3';
store.dispatch('commit/commitChanges')
.then(() => {
expect(urlUtils.visitUrl).toHaveBeenCalledWith(
`webUrl/merge_requests/new?merge_request[source_branch]=${store.getters['commit/newBranchName']}&merge_request[target_branch]=master`,
);
done();
}).catch(done.fail);
});
});
describe('failed', () => {
beforeEach(() => {
spyOn(service, 'commit').and.returnValue(Promise.resolve({
data: {
message: 'failed message',
},
}));
});
it('shows failed message', (done) => {
store.dispatch('commit/commitChanges')
.then(() => {
const alert = document.querySelector('.flash-container');
expect(alert.textContent.trim()).toBe(
'failed message',
);
done();
}).catch(done.fail);
});
});
});
});
import commitState from '~/ide/stores/modules/commit/state';
import * as consts from '~/ide/stores/modules/commit/constants';
import * as getters from '~/ide/stores/modules/commit/getters';
describe('IDE commit module getters', () => {
let state;
beforeEach(() => {
state = commitState();
});
describe('discardDraftButtonDisabled', () => {
it('returns true when commitMessage is empty', () => {
expect(getters.discardDraftButtonDisabled(state)).toBeTruthy();
});
it('returns false when commitMessage is not empty & loading is false', () => {
state.commitMessage = 'test';
state.submitCommitLoading = false;
expect(getters.discardDraftButtonDisabled(state)).toBeFalsy();
});
it('returns true when commitMessage is not empty & loading is true', () => {
state.commitMessage = 'test';
state.submitCommitLoading = true;
expect(getters.discardDraftButtonDisabled(state)).toBeTruthy();
});
});
describe('commitButtonDisabled', () => {
const localGetters = {
discardDraftButtonDisabled: false,
};
const rootState = {
changedFiles: ['a'],
};
it('returns false when discardDraftButtonDisabled is false & changedFiles is not empty', () => {
expect(getters.commitButtonDisabled(state, localGetters, rootState)).toBeFalsy();
});
it('returns true when discardDraftButtonDisabled is false & changedFiles is empty', () => {
rootState.changedFiles.length = 0;
expect(getters.commitButtonDisabled(state, localGetters, rootState)).toBeTruthy();
});
it('returns true when discardDraftButtonDisabled is true', () => {
localGetters.discardDraftButtonDisabled = true;
expect(getters.commitButtonDisabled(state, localGetters, rootState)).toBeTruthy();
});
it('returns true when discardDraftButtonDisabled is false & changedFiles is not empty', () => {
localGetters.discardDraftButtonDisabled = false;
rootState.changedFiles.length = 0;
expect(getters.commitButtonDisabled(state, localGetters, rootState)).toBeTruthy();
});
});
describe('newBranchName', () => {
it('includes username, currentBranchId, patch & random number', () => {
gon.current_username = 'username';
const branch = getters.newBranchName(state, null, { currentBranchId: 'testing' });
expect(branch).toMatch(/username-testing-patch-\d{5}$/);
});
});
describe('branchName', () => {
const rootState = {
currentBranchId: 'master',
};
const localGetters = {
newBranchName: 'newBranchName',
};
beforeEach(() => {
Object.assign(state, {
newBranchName: 'state-newBranchName',
});
});
it('defualts to currentBranchId', () => {
expect(getters.branchName(state, null, rootState)).toBe('master');
});
['COMMIT_TO_NEW_BRANCH', 'COMMIT_TO_NEW_BRANCH_MR'].forEach((type) => {
describe(type, () => {
beforeEach(() => {
Object.assign(state, {
commitAction: consts[type],
});
});
it('uses newBranchName when not empty', () => {
expect(getters.branchName(state, localGetters, rootState)).toBe('state-newBranchName');
});
it('uses getters newBranchName when state newBranchName is empty', () => {
Object.assign(state, {
newBranchName: '',
});
expect(getters.branchName(state, localGetters, rootState)).toBe('newBranchName');
});
});
});
});
});
import commitState from '~/ide/stores/modules/commit/state';
import mutations from '~/ide/stores/modules/commit/mutations';
describe('IDE commit module mutations', () => {
let state;
beforeEach(() => {
state = commitState();
});
describe('UPDATE_COMMIT_MESSAGE', () => {
it('updates commitMessage', () => {
mutations.UPDATE_COMMIT_MESSAGE(state, 'testing');
expect(state.commitMessage).toBe('testing');
});
});
describe('UPDATE_COMMIT_ACTION', () => {
it('updates commitAction', () => {
mutations.UPDATE_COMMIT_ACTION(state, 'testing');
expect(state.commitAction).toBe('testing');
});
});
describe('UPDATE_NEW_BRANCH_NAME', () => {
it('updates newBranchName', () => {
mutations.UPDATE_NEW_BRANCH_NAME(state, 'testing');
expect(state.newBranchName).toBe('testing');
});
});
describe('UPDATE_LOADING', () => {
it('updates submitCommitLoading', () => {
mutations.UPDATE_LOADING(state, true);
expect(state.submitCommitLoading).toBeTruthy();
});
});
});
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