Added staged files state to IDE
Closes https://gitlab.com/gitlab-org/gitlab-ee/issues/4541
This commit is contained in:
parent
4718f22f57
commit
51c64f3fc7
|
@ -0,0 +1,87 @@
|
|||
<script>
|
||||
import { mapActions, mapState, mapGetters } from 'vuex';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
},
|
||||
props: {
|
||||
noChangesStateSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
committedStateSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(['lastCommitMsg', 'rightPanelCollapsed']),
|
||||
...mapGetters(['collapseButtonIcon']),
|
||||
statusSvg() {
|
||||
return this.lastCommitMsg
|
||||
? this.committedStateSvgPath
|
||||
: this.noChangesStateSvgPath;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['toggleRightPanelCollapsed']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="multi-file-commit-panel-section ide-commity-empty-state js-empty-state"
|
||||
>
|
||||
<header
|
||||
class="multi-file-commit-panel-header"
|
||||
:class="{
|
||||
'is-collapsed': rightPanelCollapsed,
|
||||
}"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-transparent multi-file-commit-panel-collapse-btn"
|
||||
:aria-label="__('Toggle sidebar')"
|
||||
@click.stop="toggleRightPanelCollapsed"
|
||||
>
|
||||
<icon
|
||||
:name="collapseButtonIcon"
|
||||
:size="18"
|
||||
/>
|
||||
</button>
|
||||
</header>
|
||||
<div
|
||||
class="ide-commit-empty-state-container"
|
||||
v-if="!rightPanelCollapsed"
|
||||
>
|
||||
<div class="svg-content svg-80">
|
||||
<img :src="statusSvg" />
|
||||
</div>
|
||||
<div class="append-right-default prepend-left-default">
|
||||
<div
|
||||
class="text-content text-center"
|
||||
v-if="!lastCommitMsg"
|
||||
>
|
||||
<h4>
|
||||
{{ __('No changes') }}
|
||||
</h4>
|
||||
<p>
|
||||
{{ __('Edit files in the editor and commit changes here') }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="text-content text-center"
|
||||
v-else
|
||||
>
|
||||
<h4>
|
||||
{{ __('All changes are committed') }}
|
||||
</h4>
|
||||
<p v-html="lastCommitMsg"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import { mapActions, mapState, mapGetters } from 'vuex';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import listItem from './list_item.vue';
|
||||
import listCollapsed from './list_collapsed.vue';
|
||||
|
@ -19,20 +19,44 @@
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
showToggle: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
action: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
actionBtnText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
itemActionComponent: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'currentProjectId',
|
||||
'currentBranchId',
|
||||
'rightPanelCollapsed',
|
||||
]),
|
||||
isCommitInfoShown() {
|
||||
return this.rightPanelCollapsed || this.fileList.length;
|
||||
},
|
||||
...mapGetters([
|
||||
'collapseButtonIcon',
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
toggleCollapsed() {
|
||||
this.$emit('toggleCollapsed');
|
||||
...mapActions([
|
||||
'toggleRightPanelCollapsed',
|
||||
'stageAllChanges',
|
||||
'unstageAllChanges',
|
||||
]),
|
||||
actionBtnClicked() {
|
||||
this[this.action]();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -40,17 +64,60 @@
|
|||
|
||||
<template>
|
||||
<div
|
||||
class="ide-commit-list-container"
|
||||
:class="{
|
||||
'multi-file-commit-list': isCommitInfoShown
|
||||
'is-collapsed': rightPanelCollapsed,
|
||||
}"
|
||||
>
|
||||
<header
|
||||
class="multi-file-commit-panel-header"
|
||||
:class="{
|
||||
'is-collapsed': rightPanelCollapsed,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-if="!rightPanelCollapsed"
|
||||
class="multi-file-commit-panel-header-title"
|
||||
:class="{
|
||||
'append-right-10': showToggle,
|
||||
}"
|
||||
>
|
||||
<icon
|
||||
v-once
|
||||
:name="icon"
|
||||
:size="18"
|
||||
/>
|
||||
{{ title }}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-blank btn-link ide-staged-action-btn"
|
||||
@click="actionBtnClicked"
|
||||
>
|
||||
{{ actionBtnText }}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
v-if="showToggle"
|
||||
type="button"
|
||||
class="btn btn-transparent multi-file-commit-panel-collapse-btn"
|
||||
:aria-label="__('Toggle sidebar')"
|
||||
@click.stop="toggleRightPanelCollapsed"
|
||||
>
|
||||
<icon
|
||||
:name="collapseButtonIcon"
|
||||
:size="18"
|
||||
/>
|
||||
</button>
|
||||
</header>
|
||||
<list-collapsed
|
||||
v-if="rightPanelCollapsed"
|
||||
:files="fileList"
|
||||
:icon="icon"
|
||||
/>
|
||||
<template v-else>
|
||||
<ul
|
||||
v-if="fileList.length"
|
||||
class="list-unstyled append-bottom-0"
|
||||
class="multi-file-commit-list list-unstyled append-bottom-0"
|
||||
>
|
||||
<li
|
||||
v-for="file in fileList"
|
||||
|
@ -58,9 +125,16 @@
|
|||
>
|
||||
<list-item
|
||||
:file="file"
|
||||
:action-component="itemActionComponent"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
<p
|
||||
v-else
|
||||
class="multi-file-commit-list help-block"
|
||||
>
|
||||
{{ __('No changes') }}
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,35 +1,58 @@
|
|||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'addedFiles',
|
||||
'modifiedFiles',
|
||||
]),
|
||||
props: {
|
||||
files: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
addedFilesLength() {
|
||||
return this.files.filter(f => f.tempFile).length;
|
||||
},
|
||||
modifiedFilesLength() {
|
||||
return this.files.filter(f => !f.tempFile).length;
|
||||
},
|
||||
addedFilesIconClass() {
|
||||
return this.addedFilesLength ? 'multi-file-addition' : '';
|
||||
},
|
||||
modifiedFilesClass() {
|
||||
return this.modifiedFilesLength ? 'multi-file-modified' : '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="multi-file-commit-list-collapsed text-center"
|
||||
>
|
||||
<icon
|
||||
v-once
|
||||
:name="icon"
|
||||
:size="18"
|
||||
css-classes="append-bottom-15"
|
||||
/>
|
||||
<icon
|
||||
name="file-addition"
|
||||
:size="18"
|
||||
css-classes="multi-file-addition append-bottom-10"
|
||||
:css-classes="addedFilesIconClass + 'append-bottom-10'"
|
||||
/>
|
||||
{{ addedFiles.length }}
|
||||
{{ addedFilesLength }}
|
||||
<icon
|
||||
name="file-modified"
|
||||
:size="18"
|
||||
css-classes="multi-file-modified prepend-top-10 append-bottom-10"
|
||||
:css-classes="modifiedFilesClass + ' prepend-top-10 append-bottom-10'"
|
||||
/>
|
||||
{{ modifiedFiles.length }}
|
||||
{{ modifiedFilesLength }}
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,38 +1,44 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import router from '../../ide_router';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import StageButton from './stage_button.vue';
|
||||
import UnstageButton from './unstage_button.vue';
|
||||
import router from '../../ide_router';
|
||||
|
||||
export default {
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
Icon,
|
||||
StageButton,
|
||||
UnstageButton,
|
||||
},
|
||||
props: {
|
||||
file: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
actionComponent: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
iconName() {
|
||||
return this.file.tempFile ? 'file-addition' : 'file-modified';
|
||||
},
|
||||
iconClass() {
|
||||
return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
|
||||
return `multi-file-${
|
||||
this.file.tempFile ? 'addition' : 'modified'
|
||||
} append-right-8`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'discardFileChanges',
|
||||
'updateViewer',
|
||||
]),
|
||||
...mapActions(['updateViewer']),
|
||||
openFileInEditor(file) {
|
||||
this.updateViewer('diff');
|
||||
|
||||
router.push(`/project${file.url}`);
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -49,12 +55,9 @@
|
|||
/>{{ file.path }}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-blank multi-file-discard-btn"
|
||||
@click="discardFileChanges(file.path)"
|
||||
>
|
||||
Discard
|
||||
</button>
|
||||
<component
|
||||
:is="actionComponent"
|
||||
:file="file"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
},
|
||||
props: {
|
||||
file: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['stageChange', 'discardFileChanges']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-once
|
||||
class="multi-file-discard-btn"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-blank append-right-5"
|
||||
:aria-label="__('Stage change')"
|
||||
@click.stop="stageChange(file)"
|
||||
>
|
||||
<icon
|
||||
name="mobile-issue-close"
|
||||
:size="12"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-blank"
|
||||
:aria-label="__('Discard change')"
|
||||
@click.stop="discardFileChanges(file)"
|
||||
>
|
||||
<icon
|
||||
name="remove"
|
||||
:size="12"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,38 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
},
|
||||
props: {
|
||||
file: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['unstageChange']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-once
|
||||
class="multi-file-discard-btn"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-blank"
|
||||
:aria-label="__('Unstage change')"
|
||||
@click="unstageChange(file)"
|
||||
>
|
||||
<icon
|
||||
name="history"
|
||||
:size="12"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
|
@ -1,5 +1,4 @@
|
|||
<script>
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import panelResizer from '~/vue_shared/components/panel_resizer.vue';
|
||||
import repoCommitSection from './repo_commit_section.vue';
|
||||
|
@ -22,13 +21,6 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(['changedFiles', 'rightPanelCollapsed']),
|
||||
...mapGetters(['currentIcon']),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['setPanelCollapsedStatus']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -41,40 +33,6 @@ export default {
|
|||
<div
|
||||
class="multi-file-commit-panel-section"
|
||||
>
|
||||
<header
|
||||
class="multi-file-commit-panel-header"
|
||||
:class="{
|
||||
'is-collapsed': rightPanelCollapsed,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="multi-file-commit-panel-header-title"
|
||||
v-if="!rightPanelCollapsed"
|
||||
>
|
||||
<div
|
||||
v-if="changedFiles.length"
|
||||
>
|
||||
<icon
|
||||
name="list-bulleted"
|
||||
:size="18"
|
||||
/>
|
||||
Staged
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-transparent multi-file-commit-panel-collapse-btn"
|
||||
@click.stop="setPanelCollapsedStatus({
|
||||
side: 'right',
|
||||
collapsed: !rightPanelCollapsed,
|
||||
})"
|
||||
>
|
||||
<icon
|
||||
:name="currentIcon"
|
||||
:size="18"
|
||||
/>
|
||||
</button>
|
||||
</header>
|
||||
<repo-commit-section
|
||||
:no-changes-state-svg-path="noChangesStateSvgPath"
|
||||
:committed-state-svg-path="committedStateSvgPath"
|
||||
|
|
|
@ -5,14 +5,16 @@ import icon from '~/vue_shared/components/icon.vue';
|
|||
import modal from '~/vue_shared/components/modal.vue';
|
||||
import LoadingButton from '~/vue_shared/components/loading_button.vue';
|
||||
import commitFilesList from './commit_sidebar/list.vue';
|
||||
import * as consts from '../stores/modules/commit/constants';
|
||||
import EmptyState from './commit_sidebar/empty_state.vue';
|
||||
import Actions from './commit_sidebar/actions.vue';
|
||||
import * as consts from '../stores/modules/commit/constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
modal,
|
||||
icon,
|
||||
commitFilesList,
|
||||
EmptyState,
|
||||
Actions,
|
||||
LoadingButton,
|
||||
},
|
||||
|
@ -30,45 +32,26 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'currentProjectId',
|
||||
'currentBranchId',
|
||||
'rightPanelCollapsed',
|
||||
'lastCommitMsg',
|
||||
'changedFiles',
|
||||
]),
|
||||
...mapState('commit', [
|
||||
'commitMessage',
|
||||
'submitCommitLoading',
|
||||
]),
|
||||
...mapState(['stagedFiles', 'rightPanelCollapsed']),
|
||||
...mapState('commit', ['commitMessage', 'submitCommitLoading']),
|
||||
...mapGetters(['unstagedFiles']),
|
||||
...mapGetters('commit', [
|
||||
'commitButtonDisabled',
|
||||
'discardDraftButtonDisabled',
|
||||
'branchName',
|
||||
]),
|
||||
statusSvg() {
|
||||
return this.lastCommitMsg ? this.committedStateSvgPath : this.noChangesStateSvgPath;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'setPanelCollapsedStatus',
|
||||
]),
|
||||
...mapActions('commit', [
|
||||
'updateCommitMessage',
|
||||
'discardDraft',
|
||||
'commitChanges',
|
||||
'updateCommitAction',
|
||||
]),
|
||||
toggleCollapsed() {
|
||||
this.setPanelCollapsedStatus({
|
||||
side: 'right',
|
||||
collapsed: !this.rightPanelCollapsed,
|
||||
});
|
||||
},
|
||||
forceCreateNewBranch() {
|
||||
return this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH)
|
||||
.then(() => this.commitChanges());
|
||||
return this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH).then(() =>
|
||||
this.commitChanges(),
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -77,9 +60,6 @@ export default {
|
|||
<template>
|
||||
<div
|
||||
class="multi-file-commit-panel-section"
|
||||
:class="{
|
||||
'multi-file-commit-empty-state-container': !changedFiles.length
|
||||
}"
|
||||
>
|
||||
<modal
|
||||
id="ide-create-branch-modal"
|
||||
|
@ -93,15 +73,26 @@ export default {
|
|||
Would you like to create a new branch?`) }}
|
||||
</template>
|
||||
</modal>
|
||||
<commit-files-list
|
||||
title="Staged"
|
||||
:file-list="changedFiles"
|
||||
:collapsed="rightPanelCollapsed"
|
||||
@toggleCollapsed="toggleCollapsed"
|
||||
/>
|
||||
<template
|
||||
v-if="changedFiles.length"
|
||||
v-if="unstagedFiles.length || stagedFiles.length"
|
||||
>
|
||||
<commit-files-list
|
||||
icon="unstaged"
|
||||
:title="__('Unstaged')"
|
||||
:file-list="unstagedFiles"
|
||||
action="stageAllChanges"
|
||||
:action-btn-text="__('Stage all')"
|
||||
item-action-component="stage-button"
|
||||
/>
|
||||
<commit-files-list
|
||||
icon="staged"
|
||||
:title="__('Staged')"
|
||||
:file-list="stagedFiles"
|
||||
action="unstageAllChanges"
|
||||
:action-btn-text="__('Unstage all')"
|
||||
item-action-component="unstage-button"
|
||||
:show-toggle="false"
|
||||
/>
|
||||
<form
|
||||
class="form-horizontal multi-file-commit-form"
|
||||
@submit.prevent.stop="commitChanges"
|
||||
|
@ -137,38 +128,10 @@ export default {
|
|||
</div>
|
||||
</form>
|
||||
</template>
|
||||
<div
|
||||
v-else-if="!rightPanelCollapsed"
|
||||
class="row js-empty-state"
|
||||
>
|
||||
<div class="col-xs-10 col-xs-offset-1">
|
||||
<div class="svg-content svg-80">
|
||||
<img :src="statusSvg" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-10 col-xs-offset-1">
|
||||
<div
|
||||
class="text-content text-center"
|
||||
v-if="!lastCommitMsg"
|
||||
>
|
||||
<h4>
|
||||
{{ __('No changes') }}
|
||||
</h4>
|
||||
<p>
|
||||
{{ __('Edit files in the editor and commit changes here') }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="text-content text-center"
|
||||
<empty-state
|
||||
v-else
|
||||
>
|
||||
<h4>
|
||||
{{ __('All changes are committed') }}
|
||||
</h4>
|
||||
<p v-html="lastCommitMsg">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
:no-changes-state-svg-path="noChangesStateSvgPath"
|
||||
:committed-state-svg-path="committedStateSvgPath"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -33,6 +33,13 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const toggleRightPanelCollapsed = ({ dispatch, state }) => {
|
||||
dispatch('setPanelCollapsedStatus', {
|
||||
side: 'right',
|
||||
collapsed: !state.rightPanelCollapsed,
|
||||
});
|
||||
};
|
||||
|
||||
export const setResizingStatus = ({ commit }, resizing) => {
|
||||
commit(types.SET_RESIZING_STATUS, resizing);
|
||||
};
|
||||
|
@ -108,6 +115,14 @@ export const scrollToTab = () => {
|
|||
});
|
||||
};
|
||||
|
||||
export const stageAllChanges = ({ state, commit }) => {
|
||||
[...state.changedFiles].forEach(file => commit(types.STAGE_CHANGE, file));
|
||||
};
|
||||
|
||||
export const unstageAllChanges = ({ state, commit }) => {
|
||||
[...state.stagedFiles].forEach(file => commit(types.UNSTAGE_CHANGE, file));
|
||||
};
|
||||
|
||||
export const updateViewer = ({ commit }, viewer) => {
|
||||
commit(types.UPDATE_VIEWER, viewer);
|
||||
};
|
||||
|
|
|
@ -144,3 +144,11 @@ export const discardFileChanges = ({ state, commit }, path) => {
|
|||
|
||||
eventHub.$emit(`editor.update.model.content.${file.path}`, file.raw);
|
||||
};
|
||||
|
||||
export const stageChange = ({ commit }, file) => {
|
||||
commit(types.STAGE_CHANGE, file);
|
||||
};
|
||||
|
||||
export const unstageChange = ({ commit }, file) => {
|
||||
commit(types.UNSTAGE_CHANGE, file);
|
||||
};
|
||||
|
|
|
@ -28,3 +28,5 @@ export const currentIcon = state =>
|
|||
state.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
|
||||
|
||||
export const hasChanges = state => !!state.changedFiles.length;
|
||||
|
||||
export const unstagedFiles = state => state.changedFiles.filter(f => !f.staged);
|
||||
|
|
|
@ -131,9 +131,10 @@ export const updateFilesAfterCommit = (
|
|||
);
|
||||
});
|
||||
|
||||
commit(rootTypes.REMOVE_ALL_CHANGES_FILES, null, { root: true });
|
||||
|
||||
if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH) {
|
||||
if (
|
||||
state.commitAction === consts.COMMIT_TO_NEW_BRANCH &&
|
||||
rootGetters.activeFile
|
||||
) {
|
||||
router.push(
|
||||
`/project/${rootState.currentProjectId}/blob/${branch}/${
|
||||
rootGetters.activeFile.path
|
||||
|
@ -186,7 +187,6 @@ export const commitChanges = ({
|
|||
}
|
||||
|
||||
dispatch('setLastCommitMessage', data);
|
||||
dispatch('updateCommitMessage', '');
|
||||
|
||||
if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH_MR) {
|
||||
dispatch(
|
||||
|
@ -204,6 +204,10 @@ export const commitChanges = ({
|
|||
branch: getters.branchName,
|
||||
});
|
||||
}
|
||||
|
||||
commit(rootTypes.CLEAR_STAGED_CHANGES, null, { root: true });
|
||||
|
||||
dispatch('discardDraft');
|
||||
})
|
||||
.catch(err => {
|
||||
let errMsg = __('Error committing changes. Please try again.');
|
||||
|
|
|
@ -41,3 +41,7 @@ export const SET_ENTRIES = 'SET_ENTRIES';
|
|||
export const CREATE_TMP_ENTRY = 'CREATE_TMP_ENTRY';
|
||||
export const UPDATE_VIEWER = 'UPDATE_VIEWER';
|
||||
export const UPDATE_DELAY_VIEWER_CHANGE = 'UPDATE_DELAY_VIEWER_CHANGE';
|
||||
|
||||
export const CLEAR_STAGED_CHANGES = 'CLEAR_STAGED_CHANGES';
|
||||
export const STAGE_CHANGE = 'STAGE_CHANGE';
|
||||
export const UNSTAGE_CHANGE = 'UNSTAGE_CHANGE';
|
||||
|
|
|
@ -51,6 +51,11 @@ export default {
|
|||
lastCommitMsg,
|
||||
});
|
||||
},
|
||||
[types.CLEAR_STAGED_CHANGES](state) {
|
||||
Object.assign(state, {
|
||||
stagedFiles: [],
|
||||
});
|
||||
},
|
||||
[types.SET_ENTRIES](state, entries) {
|
||||
Object.assign(state, {
|
||||
entries,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as types from '../mutation_types';
|
||||
import { findIndexOfFile, findEntry } from '../utils';
|
||||
|
||||
export default {
|
||||
[types.SET_FILE_ACTIVE](state, { path, active }) {
|
||||
|
@ -75,6 +76,33 @@ export default {
|
|||
changedFiles: state.changedFiles.filter(f => f.path !== path),
|
||||
});
|
||||
},
|
||||
[types.STAGE_CHANGE](state, file) {
|
||||
const stagedFile = findEntry(state.stagedFiles, 'blob', file.name);
|
||||
|
||||
Object.assign(file, {
|
||||
staged: true,
|
||||
});
|
||||
|
||||
if (stagedFile) {
|
||||
Object.assign(stagedFile, {
|
||||
...file,
|
||||
});
|
||||
} else {
|
||||
state.stagedFiles.push({
|
||||
...file,
|
||||
});
|
||||
}
|
||||
},
|
||||
[types.UNSTAGE_CHANGE](state, file) {
|
||||
const indexOfStagedFile = findIndexOfFile(state.stagedFiles, file);
|
||||
const changedFile = findEntry(state.changedFiles, 'blob', file.name);
|
||||
|
||||
state.stagedFiles.splice(indexOfStagedFile, 1);
|
||||
|
||||
Object.assign(changedFile, {
|
||||
staged: false,
|
||||
});
|
||||
},
|
||||
[types.TOGGLE_FILE_CHANGED](state, { file, changed }) {
|
||||
Object.assign(state.entries[file.path], {
|
||||
changed,
|
||||
|
|
|
@ -2,6 +2,7 @@ export default () => ({
|
|||
currentProjectId: '',
|
||||
currentBranchId: '',
|
||||
changedFiles: [],
|
||||
stagedFiles: [],
|
||||
endpoints: {},
|
||||
lastCommitMsg: '',
|
||||
lastCommitPath: '',
|
||||
|
|
|
@ -13,6 +13,7 @@ export const dataStructure = () => ({
|
|||
opened: false,
|
||||
active: false,
|
||||
changed: false,
|
||||
staged: false,
|
||||
lastCommitPath: '',
|
||||
lastCommit: {
|
||||
id: '',
|
||||
|
@ -38,7 +39,7 @@ export const dataStructure = () => ({
|
|||
eol: '',
|
||||
});
|
||||
|
||||
export const decorateData = (entity) => {
|
||||
export const decorateData = entity => {
|
||||
const {
|
||||
id,
|
||||
projectId,
|
||||
|
@ -57,7 +58,6 @@ export const decorateData = (entity) => {
|
|||
base64 = false,
|
||||
|
||||
file_lock,
|
||||
|
||||
} = entity;
|
||||
|
||||
return {
|
||||
|
@ -80,24 +80,23 @@ export const decorateData = (entity) => {
|
|||
base64,
|
||||
|
||||
file_lock,
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
export const findEntry = (tree, type, name, prop = 'name') => tree.find(
|
||||
f => f.type === type && f[prop] === name,
|
||||
);
|
||||
export const findEntry = (tree, type, name, prop = 'name') =>
|
||||
tree.find(f => f.type === type && f[prop] === name);
|
||||
|
||||
export const findIndexOfFile = (state, file) => state.findIndex(f => f.path === file.path);
|
||||
export const findIndexOfFile = (state, file) =>
|
||||
state.findIndex(f => f.path === file.path);
|
||||
|
||||
export const setPageTitle = (title) => {
|
||||
export const setPageTitle = title => {
|
||||
document.title = title;
|
||||
};
|
||||
|
||||
export const createCommitPayload = (branch, newBranch, state, rootState) => ({
|
||||
branch,
|
||||
commit_message: state.commitMessage,
|
||||
actions: rootState.changedFiles.map(f => ({
|
||||
actions: rootState.stagedFiles.map(f => ({
|
||||
action: f.tempFile ? 'create' : 'update',
|
||||
file_path: f.path,
|
||||
content: f.content,
|
||||
|
@ -120,6 +119,11 @@ const sortTreesByTypeAndName = (a, b) => {
|
|||
return 0;
|
||||
};
|
||||
|
||||
export const sortTree = sortedTree => sortedTree.map(entity => Object.assign(entity, {
|
||||
export const sortTree = sortedTree =>
|
||||
sortedTree
|
||||
.map(entity =>
|
||||
Object.assign(entity, {
|
||||
tree: entity.tree.length ? sortTree(entity.tree) : [],
|
||||
})).sort(sortTreesByTypeAndName);
|
||||
}),
|
||||
)
|
||||
.sort(sortTreesByTypeAndName);
|
||||
|
|
|
@ -449,9 +449,13 @@
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
.multi-file-commit-empty-state-container {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.ide-commity-empty-state {
|
||||
padding: 0 $gl-padding;
|
||||
}
|
||||
|
||||
.ide-commit-empty-state-container {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.multi-file-commit-panel-header {
|
||||
|
@ -462,7 +466,8 @@
|
|||
padding: $gl-btn-padding 0;
|
||||
|
||||
&.is-collapsed {
|
||||
border-bottom: 1px solid $white-dark;
|
||||
margin-left: -$gl-padding;
|
||||
margin-right: -$gl-padding;
|
||||
|
||||
svg {
|
||||
margin-left: auto;
|
||||
|
@ -480,7 +485,6 @@
|
|||
.multi-file-commit-panel-header-title {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding: 0 $gl-btn-padding;
|
||||
|
||||
svg {
|
||||
margin-right: $gl-btn-padding;
|
||||
|
@ -489,6 +493,7 @@
|
|||
|
||||
.multi-file-commit-panel-collapse-btn {
|
||||
border-left: 1px solid $white-dark;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.multi-file-commit-list {
|
||||
|
@ -502,12 +507,14 @@
|
|||
display: flex;
|
||||
padding: 0;
|
||||
align-items: center;
|
||||
border-radius: $border-radius-default;
|
||||
|
||||
.multi-file-discard-btn {
|
||||
display: none;
|
||||
margin-top: -2px;
|
||||
margin-left: auto;
|
||||
margin-right: $grid-size;
|
||||
color: $gl-link-color;
|
||||
padding: 0 2px;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
|
@ -519,7 +526,7 @@
|
|||
background: $white-normal;
|
||||
|
||||
.multi-file-discard-btn {
|
||||
display: block;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -535,10 +542,12 @@
|
|||
.multi-file-commit-list-collapsed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: $gl-padding 0;
|
||||
|
||||
> svg {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
color: $theme-gray-700;
|
||||
}
|
||||
|
||||
.file-status-icon {
|
||||
|
@ -550,7 +559,7 @@
|
|||
|
||||
.multi-file-commit-list-path {
|
||||
padding: $grid-size / 2;
|
||||
padding-left: $gl-padding;
|
||||
padding-left: $grid-size;
|
||||
background: none;
|
||||
border: 0;
|
||||
text-align: left;
|
||||
|
@ -740,6 +749,22 @@
|
|||
}
|
||||
}
|
||||
|
||||
.ide-commit-list-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 0 16px;
|
||||
|
||||
&:not(.is-collapsed) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.ide-staged-action-btn {
|
||||
margin-left: auto;
|
||||
color: $gl-link-color;
|
||||
}
|
||||
|
||||
.ide-commit-radios {
|
||||
label {
|
||||
font-weight: normal;
|
||||
|
|
|
@ -44,6 +44,8 @@ feature 'Multi-file editor new directory', :js do
|
|||
|
||||
wait_for_requests
|
||||
|
||||
click_button 'Stage all'
|
||||
|
||||
fill_in('commit-message', with: 'commit message ide')
|
||||
|
||||
click_button('Commit')
|
||||
|
|
|
@ -34,6 +34,8 @@ feature 'Multi-file editor new file', :js do
|
|||
|
||||
wait_for_requests
|
||||
|
||||
click_button 'Stage all'
|
||||
|
||||
fill_in('commit-message', with: 'commit message ide')
|
||||
|
||||
click_button('Commit')
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import emptyState from '~/ide/components/commit_sidebar/empty_state.vue';
|
||||
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
|
||||
import { resetStore } from '../../helpers';
|
||||
|
||||
describe('IDE commit panel empty state', () => {
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
const Component = Vue.extend(emptyState);
|
||||
|
||||
vm = createComponentWithStore(Component, store, {
|
||||
noChangesStateSvgPath: 'no-changes',
|
||||
committedStateSvgPath: 'committed-state',
|
||||
});
|
||||
|
||||
vm.$mount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
describe('statusSvg', () => {
|
||||
it('uses noChangesStateSvgPath when commit message is empty', () => {
|
||||
expect(vm.statusSvg).toBe('no-changes');
|
||||
expect(vm.$el.querySelector('img').getAttribute('src')).toBe(
|
||||
'no-changes',
|
||||
);
|
||||
});
|
||||
|
||||
it('uses committedStateSvgPath when commit message exists', done => {
|
||||
vm.$store.state.lastCommitMsg = 'testing';
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.statusSvg).toBe('committed-state');
|
||||
expect(vm.$el.querySelector('img').getAttribute('src')).toBe(
|
||||
'committed-state',
|
||||
);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('renders no changes text when last commit message is empty', () => {
|
||||
expect(vm.$el.textContent).toContain('No changes');
|
||||
});
|
||||
|
||||
it('renders last commit message when it exists', done => {
|
||||
vm.$store.state.lastCommitMsg = 'testing commit message';
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$el.textContent).toContain('testing commit message');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggle button', () => {
|
||||
it('calls store action', () => {
|
||||
spyOn(vm, 'toggleRightPanelCollapsed');
|
||||
|
||||
vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click();
|
||||
|
||||
expect(vm.toggleRightPanelCollapsed).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders collapsed class', done => {
|
||||
vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('collapsed state', () => {
|
||||
beforeEach(done => {
|
||||
vm.$store.state.rightPanelCollapsed = true;
|
||||
|
||||
Vue.nextTick(done);
|
||||
});
|
||||
|
||||
it('does not render text & svg', () => {
|
||||
expect(vm.$el.querySelector('img')).toBeNull();
|
||||
expect(vm.$el.textContent).not.toContain('No changes');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -10,10 +10,16 @@ describe('Multi-file editor commit sidebar list collapsed', () => {
|
|||
beforeEach(() => {
|
||||
const Component = Vue.extend(listCollapsed);
|
||||
|
||||
vm = createComponentWithStore(Component, store);
|
||||
|
||||
vm.$store.state.changedFiles.push(file('file1'), file('file2'));
|
||||
vm.$store.state.changedFiles[0].tempFile = true;
|
||||
vm = createComponentWithStore(Component, store, {
|
||||
files: [
|
||||
{
|
||||
...file('file1'),
|
||||
tempFile: true,
|
||||
},
|
||||
file('file2'),
|
||||
],
|
||||
icon: 'staged',
|
||||
});
|
||||
|
||||
vm.$mount();
|
||||
});
|
||||
|
@ -25,4 +31,40 @@ describe('Multi-file editor commit sidebar list collapsed', () => {
|
|||
it('renders added & modified files count', () => {
|
||||
expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toBe('1 1');
|
||||
});
|
||||
|
||||
describe('addedFilesLength', () => {
|
||||
it('returns an length of temp files', () => {
|
||||
expect(vm.addedFilesLength).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('modifiedFilesLength', () => {
|
||||
it('returns an length of modified files', () => {
|
||||
expect(vm.modifiedFilesLength).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addedFilesIconClass', () => {
|
||||
it('includes multi-file-addition when addedFiles is not empty', () => {
|
||||
expect(vm.addedFilesIconClass).toContain('multi-file-addition');
|
||||
});
|
||||
|
||||
it('excludes multi-file-addition when addedFiles is empty', () => {
|
||||
vm.files = [];
|
||||
|
||||
expect(vm.addedFilesIconClass).not.toContain('multi-file-addition');
|
||||
});
|
||||
});
|
||||
|
||||
describe('modifiedFilesClass', () => {
|
||||
it('includes multi-file-modified when addedFiles is not empty', () => {
|
||||
expect(vm.modifiedFilesClass).toContain('multi-file-modified');
|
||||
});
|
||||
|
||||
it('excludes multi-file-modified when addedFiles is empty', () => {
|
||||
vm.files = [];
|
||||
|
||||
expect(vm.modifiedFilesClass).not.toContain('multi-file-modified');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,25 +1,33 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import listItem from '~/ide/components/commit_sidebar/list_item.vue';
|
||||
import router from '~/ide/ide_router';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import { file } from '../../helpers';
|
||||
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||
import { file, resetStore } from '../../helpers';
|
||||
|
||||
describe('Multi-file editor commit sidebar list item', () => {
|
||||
let vm;
|
||||
let f;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(done => {
|
||||
const Component = Vue.extend(listItem);
|
||||
|
||||
f = file('test-file');
|
||||
|
||||
vm = mountComponent(Component, {
|
||||
vm = createComponentWithStore(Component, store, {
|
||||
file: f,
|
||||
actionComponent: 'stage-button',
|
||||
});
|
||||
|
||||
vm.$mount();
|
||||
|
||||
Vue.nextTick(done);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('renders file path', () => {
|
||||
|
@ -28,12 +36,8 @@ describe('Multi-file editor commit sidebar list item', () => {
|
|||
).toBe(f.path);
|
||||
});
|
||||
|
||||
it('calls discardFileChanges when clicking discard button', () => {
|
||||
spyOn(vm, 'discardFileChanges');
|
||||
|
||||
vm.$el.querySelector('.multi-file-discard-btn').click();
|
||||
|
||||
expect(vm.discardFileChanges).toHaveBeenCalled();
|
||||
it('renders actionn button', () => {
|
||||
expect(vm.$el.querySelector('.multi-file-discard-btn')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('opens a closed file in the editor when clicking the file path', () => {
|
||||
|
|
|
@ -2,7 +2,7 @@ import Vue from 'vue';
|
|||
import store from '~/ide/stores';
|
||||
import commitSidebarList from '~/ide/components/commit_sidebar/list.vue';
|
||||
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||
import { file } from '../../helpers';
|
||||
import { file, resetStore } from '../../helpers';
|
||||
|
||||
describe('Multi-file editor commit sidebar list', () => {
|
||||
let vm;
|
||||
|
@ -13,6 +13,10 @@ describe('Multi-file editor commit sidebar list', () => {
|
|||
vm = createComponentWithStore(Component, store, {
|
||||
title: 'Staged',
|
||||
fileList: [],
|
||||
icon: 'staged',
|
||||
action: 'stageAllChanges',
|
||||
actionBtnText: 'stage all',
|
||||
itemActionComponent: 'stage-button',
|
||||
});
|
||||
|
||||
vm.$store.state.rightPanelCollapsed = false;
|
||||
|
@ -22,6 +26,8 @@ describe('Multi-file editor commit sidebar list', () => {
|
|||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
describe('with a list of files', () => {
|
||||
|
@ -38,6 +44,12 @@ describe('Multi-file editor commit sidebar list', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('empty files array', () => {
|
||||
it('renders no changes text when empty', () => {
|
||||
expect(vm.$el.textContent).toContain('No changes');
|
||||
});
|
||||
});
|
||||
|
||||
describe('collapsed', () => {
|
||||
beforeEach(done => {
|
||||
vm.$store.state.rightPanelCollapsed = true;
|
||||
|
@ -50,4 +62,32 @@ describe('Multi-file editor commit sidebar list', () => {
|
|||
expect(vm.$el.querySelector('.help-block')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with toggle', () => {
|
||||
beforeEach(done => {
|
||||
spyOn(vm, 'toggleRightPanelCollapsed');
|
||||
|
||||
vm.showToggle = true;
|
||||
|
||||
Vue.nextTick(done);
|
||||
});
|
||||
|
||||
it('calls setPanelCollapsedStatus when clickin toggle', () => {
|
||||
vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click();
|
||||
|
||||
expect(vm.toggleRightPanelCollapsed).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('action button', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(vm, 'stageAllChanges');
|
||||
});
|
||||
|
||||
it('calls store action when clicked', () => {
|
||||
vm.$el.querySelector('.ide-staged-action-btn').click();
|
||||
|
||||
expect(vm.stageAllChanges).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import stageButton from '~/ide/components/commit_sidebar/stage_button.vue';
|
||||
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
|
||||
import { file, resetStore } from '../../helpers';
|
||||
|
||||
describe('IDE stage file button', () => {
|
||||
let vm;
|
||||
let f;
|
||||
|
||||
beforeEach(() => {
|
||||
const Component = Vue.extend(stageButton);
|
||||
f = file();
|
||||
|
||||
vm = createComponentWithStore(Component, store, {
|
||||
file: f,
|
||||
});
|
||||
|
||||
spyOn(vm, 'stageChange');
|
||||
spyOn(vm, 'discardFileChanges');
|
||||
|
||||
vm.$mount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('renders button to discard & stage', () => {
|
||||
expect(vm.$el.querySelectorAll('.btn').length).toBe(2);
|
||||
});
|
||||
|
||||
it('calls store with stage button', () => {
|
||||
vm.$el.querySelectorAll('.btn')[0].click();
|
||||
|
||||
expect(vm.stageChange).toHaveBeenCalledWith(f);
|
||||
});
|
||||
|
||||
it('calls store with discard button', () => {
|
||||
vm.$el.querySelectorAll('.btn')[1].click();
|
||||
|
||||
expect(vm.discardFileChanges).toHaveBeenCalledWith(f);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import unstageButton from '~/ide/components/commit_sidebar/unstage_button.vue';
|
||||
import { createComponentWithStore } from '../../../helpers/vue_mount_component_helper';
|
||||
import { file, resetStore } from '../../helpers';
|
||||
|
||||
describe('IDE unstage file button', () => {
|
||||
let vm;
|
||||
let f;
|
||||
|
||||
beforeEach(() => {
|
||||
const Component = Vue.extend(unstageButton);
|
||||
f = file();
|
||||
|
||||
vm = createComponentWithStore(Component, store, {
|
||||
file: f,
|
||||
});
|
||||
|
||||
spyOn(vm, 'unstageChange');
|
||||
|
||||
vm.$mount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('renders button to unstage', () => {
|
||||
expect(vm.$el.querySelectorAll('.btn').length).toBe(1);
|
||||
});
|
||||
|
||||
it('calls store with unnstage button', () => {
|
||||
vm.$el.querySelector('.btn').click();
|
||||
|
||||
expect(vm.unstageChange).toHaveBeenCalledWith(f);
|
||||
});
|
||||
});
|
|
@ -28,10 +28,24 @@ describe('RepoCommitSection', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const files = [file('file1'), file('file2')].map(f =>
|
||||
Object.assign(f, {
|
||||
type: 'blob',
|
||||
}),
|
||||
);
|
||||
|
||||
vm.$store.state.rightPanelCollapsed = false;
|
||||
vm.$store.state.currentBranch = 'master';
|
||||
vm.$store.state.changedFiles = [file('file1'), file('file2')];
|
||||
vm.$store.state.changedFiles = [...files];
|
||||
vm.$store.state.changedFiles.forEach(f =>
|
||||
Object.assign(f, {
|
||||
changed: true,
|
||||
content: 'changedFile testing',
|
||||
}),
|
||||
);
|
||||
|
||||
vm.$store.state.stagedFiles = [{ ...files[0] }, { ...files[1] }];
|
||||
vm.$store.state.stagedFiles.forEach(f =>
|
||||
Object.assign(f, {
|
||||
changed: true,
|
||||
content: 'testing',
|
||||
|
@ -94,20 +108,93 @@ describe('RepoCommitSection', () => {
|
|||
...vm.$el.querySelectorAll('.multi-file-commit-list li'),
|
||||
];
|
||||
const submitCommit = vm.$el.querySelector('form .btn');
|
||||
const allFiles = vm.$store.state.changedFiles.concat(
|
||||
vm.$store.state.stagedFiles,
|
||||
);
|
||||
|
||||
expect(vm.$el.querySelector('.multi-file-commit-form')).not.toBeNull();
|
||||
expect(changedFileElements.length).toEqual(2);
|
||||
expect(changedFileElements.length).toEqual(4);
|
||||
|
||||
changedFileElements.forEach((changedFile, i) => {
|
||||
expect(changedFile.textContent.trim()).toContain(
|
||||
vm.$store.state.changedFiles[i].path,
|
||||
);
|
||||
expect(changedFile.textContent.trim()).toContain(allFiles[i].path);
|
||||
});
|
||||
|
||||
expect(submitCommit.disabled).toBeTruthy();
|
||||
expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeNull();
|
||||
});
|
||||
|
||||
it('adds changed files into staged files', done => {
|
||||
vm.$el.querySelector('.ide-staged-action-btn').click();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(
|
||||
vm.$el.querySelector('.ide-commit-list-container').textContent,
|
||||
).toContain('No changes');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('stages a single file', done => {
|
||||
vm.$el.querySelector('.multi-file-discard-btn .btn').click();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(
|
||||
vm.$el
|
||||
.querySelector('.ide-commit-list-container')
|
||||
.querySelectorAll('li').length,
|
||||
).toBe(1);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('discards a single file', done => {
|
||||
vm.$el.querySelectorAll('.multi-file-discard-btn .btn')[1].click();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(
|
||||
vm.$el.querySelector('.ide-commit-list-container').textContent,
|
||||
).not.toContain('file1');
|
||||
expect(
|
||||
vm.$el
|
||||
.querySelector('.ide-commit-list-container')
|
||||
.querySelectorAll('li').length,
|
||||
).toBe(1);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('removes all staged files', done => {
|
||||
vm.$el.querySelectorAll('.ide-staged-action-btn')[1].click();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(
|
||||
vm.$el.querySelectorAll('.ide-commit-list-container')[1].textContent,
|
||||
).toContain('No changes');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('unstages a single file', done => {
|
||||
vm.$el
|
||||
.querySelectorAll('.multi-file-discard-btn')[2]
|
||||
.querySelector('.btn')
|
||||
.click();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(
|
||||
vm.$el
|
||||
.querySelectorAll('.ide-commit-list-container')[1]
|
||||
.querySelectorAll('li').length,
|
||||
).toBe(1);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('updates commitMessage in store on input', done => {
|
||||
const textarea = vm.$el.querySelector('textarea');
|
||||
|
||||
|
|
|
@ -292,6 +292,84 @@ describe('Multi-file store actions', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('stageAllChanges', () => {
|
||||
it('adds all files from changedFiles to stagedFiles', done => {
|
||||
const f = file();
|
||||
store.state.changedFiles.push(f);
|
||||
store.state.changedFiles.push(file('new'));
|
||||
|
||||
store
|
||||
.dispatch('stageAllChanges')
|
||||
.then(() => {
|
||||
expect(store.state.stagedFiles.length).toBe(2);
|
||||
expect(store.state.stagedFiles[0]).toEqual(f);
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets all files from changedFiles as staged after adding to stagedFiles', done => {
|
||||
store.state.changedFiles.push(file());
|
||||
store.state.changedFiles.push(file('new'));
|
||||
|
||||
store
|
||||
.dispatch('stageAllChanges')
|
||||
.then(() => {
|
||||
expect(store.state.changedFiles.length).toBe(2);
|
||||
store.state.changedFiles.forEach(f => {
|
||||
expect(f.staged).toBeTruthy();
|
||||
});
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unstageAllChanges', () => {
|
||||
let f;
|
||||
|
||||
beforeEach(() => {
|
||||
f = {
|
||||
...file(),
|
||||
type: 'blob',
|
||||
staged: true,
|
||||
};
|
||||
|
||||
store.state.changedFiles.push({
|
||||
...f,
|
||||
});
|
||||
});
|
||||
|
||||
it('sets staged to false in changedFiles when unstaging', done => {
|
||||
store.state.stagedFiles.push(f);
|
||||
|
||||
store
|
||||
.dispatch('unstageAllChanges')
|
||||
.then(() => {
|
||||
expect(store.state.stagedFiles.length).toBe(0);
|
||||
expect(store.state.changedFiles[0].staged).toBeFalsy();
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('removes all files from stagedFiles after unstaging', done => {
|
||||
store.state.stagedFiles.push(file());
|
||||
|
||||
store
|
||||
.dispatch('unstageAllChanges')
|
||||
.then(() => {
|
||||
expect(store.state.stagedFiles.length).toBe(0);
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateViewer', () => {
|
||||
it('updates viewer state', done => {
|
||||
store
|
||||
|
|
|
@ -37,19 +37,11 @@ describe('Multi-file store getters', () => {
|
|||
expect(modifiedFiles.length).toBe(1);
|
||||
expect(modifiedFiles[0].name).toBe('changed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('addedFiles', () => {
|
||||
it('returns a list of added files', () => {
|
||||
localState.openFiles.push(file());
|
||||
localState.changedFiles.push(file('added'));
|
||||
localState.changedFiles[0].changed = true;
|
||||
localState.changedFiles[0].tempFile = true;
|
||||
it('returns angle left when collapsed', () => {
|
||||
localState.rightPanelCollapsed = true;
|
||||
|
||||
const modifiedFiles = getters.addedFiles(localState);
|
||||
|
||||
expect(modifiedFiles.length).toBe(1);
|
||||
expect(modifiedFiles[0].name).toBe('added');
|
||||
expect(getters.collapseButtonIcon(localState)).toBe('angle-double-left');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -359,12 +359,22 @@ describe('IDE commit module actions', () => {
|
|||
},
|
||||
},
|
||||
};
|
||||
store.state.changedFiles.push(file('changed'));
|
||||
store.state.changedFiles[0].active = true;
|
||||
|
||||
const f = {
|
||||
...file('changed'),
|
||||
type: 'blob',
|
||||
active: true,
|
||||
};
|
||||
store.state.stagedFiles.push(f);
|
||||
store.state.changedFiles = [
|
||||
{
|
||||
...f,
|
||||
},
|
||||
];
|
||||
store.state.openFiles = store.state.changedFiles;
|
||||
|
||||
store.state.openFiles.forEach(f => {
|
||||
store.state.entries[f.path] = f;
|
||||
store.state.openFiles.forEach(localF => {
|
||||
store.state.entries[localF.path] = localF;
|
||||
});
|
||||
|
||||
store.state.commit.commitAction = '2';
|
||||
|
@ -444,7 +454,7 @@ describe('IDE commit module actions', () => {
|
|||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('adds commit data to changed files', done => {
|
||||
it('adds commit data to files', done => {
|
||||
store
|
||||
.dispatch('commit/commitChanges')
|
||||
.then(() => {
|
||||
|
|
|
@ -144,6 +144,72 @@ describe('Multi-file store file mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('STAGE_CHANGE', () => {
|
||||
it('adds file into stagedFiles array', () => {
|
||||
const f = file();
|
||||
|
||||
mutations.STAGE_CHANGE(localState, f);
|
||||
|
||||
expect(localState.stagedFiles.length).toBe(1);
|
||||
expect(localState.stagedFiles[0]).toEqual(f);
|
||||
});
|
||||
|
||||
it('updates changedFiles file to staged', () => {
|
||||
const f = {
|
||||
...file(),
|
||||
type: 'blob',
|
||||
staged: false,
|
||||
};
|
||||
|
||||
localState.changedFiles.push(f);
|
||||
|
||||
mutations.STAGE_CHANGE(localState, f);
|
||||
|
||||
expect(localState.changedFiles[0].staged).toBeTruthy();
|
||||
});
|
||||
|
||||
it('updates stagedFile if it is already staged', () => {
|
||||
const f = file();
|
||||
f.type = 'blob';
|
||||
|
||||
mutations.STAGE_CHANGE(localState, f);
|
||||
|
||||
f.raw = 'testing 123';
|
||||
|
||||
mutations.STAGE_CHANGE(localState, f);
|
||||
|
||||
expect(localState.stagedFiles.length).toBe(1);
|
||||
expect(localState.stagedFiles[0].raw).toEqual('testing 123');
|
||||
});
|
||||
});
|
||||
|
||||
describe('UNSTAGE_CHANGE', () => {
|
||||
let f;
|
||||
|
||||
beforeEach(() => {
|
||||
f = {
|
||||
...file(),
|
||||
type: 'blob',
|
||||
staged: true,
|
||||
};
|
||||
|
||||
localState.stagedFiles.push(f);
|
||||
localState.changedFiles.push(f);
|
||||
});
|
||||
|
||||
it('removes from stagedFiles array', () => {
|
||||
mutations.UNSTAGE_CHANGE(localState, f);
|
||||
|
||||
expect(localState.stagedFiles.length).toBe(0);
|
||||
});
|
||||
|
||||
it('updates changedFiles array file to unstaged', () => {
|
||||
mutations.UNSTAGE_CHANGE(localState, f);
|
||||
|
||||
expect(localState.changedFiles[0].staged).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('TOGGLE_FILE_CHANGED', () => {
|
||||
it('updates file changed status', () => {
|
||||
mutations.TOGGLE_FILE_CHANGED(localState, {
|
||||
|
|
|
@ -69,6 +69,16 @@ describe('Multi-file store mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('CLEAR_STAGED_CHANGES', () => {
|
||||
it('clears stagedFiles array', () => {
|
||||
localState.stagedFiles.push('a');
|
||||
|
||||
mutations.CLEAR_STAGED_CHANGES(localState);
|
||||
|
||||
expect(localState.stagedFiles.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('UPDATE_VIEWER', () => {
|
||||
it('sets viewer state', () => {
|
||||
mutations.UPDATE_VIEWER(localState, 'diff');
|
||||
|
|
Loading…
Reference in New Issue