Merge branch 'ide-pending-tab' into 'master'
Added pending tabs to IDE See merge request gitlab-org/gitlab-ce!17936
This commit is contained in:
commit
eaed588bf2
|
@ -1,38 +1,36 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import router from '../../ide_router';
|
||||
import { mapActions } from 'vuex';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
},
|
||||
props: {
|
||||
file: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
file: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
iconName() {
|
||||
return this.file.tempFile ? 'file-addition' : 'file-modified';
|
||||
},
|
||||
computed: {
|
||||
iconName() {
|
||||
return this.file.tempFile ? 'file-addition' : 'file-modified';
|
||||
},
|
||||
iconClass() {
|
||||
return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
|
||||
},
|
||||
iconClass() {
|
||||
return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'discardFileChanges',
|
||||
'updateViewer',
|
||||
]),
|
||||
openFileInEditor(file) {
|
||||
this.updateViewer('diff');
|
||||
|
||||
router.push(`/project${file.url}`);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['discardFileChanges', 'updateViewer', 'openPendingTab']),
|
||||
openFileInEditor(file) {
|
||||
return this.openPendingTab(file).then(changeViewer => {
|
||||
if (changeViewer) {
|
||||
this.updateViewer('diff');
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -60,6 +60,7 @@ export default {
|
|||
v-if="activeFile"
|
||||
>
|
||||
<repo-tabs
|
||||
:active-file="activeFile"
|
||||
:files="openFiles"
|
||||
:viewer="viewer"
|
||||
:has-changes="hasChanges"
|
||||
|
|
|
@ -21,7 +21,8 @@ export default {
|
|||
},
|
||||
watch: {
|
||||
file(oldVal, newVal) {
|
||||
if (newVal.path !== this.file.path) {
|
||||
// Compare key to allow for files opened in review mode to be cached differently
|
||||
if (newVal.key !== this.file.key) {
|
||||
this.initMonaco();
|
||||
}
|
||||
},
|
||||
|
@ -70,7 +71,7 @@ export default {
|
|||
})
|
||||
.then(() => {
|
||||
const viewerPromise = this.delayViewerUpdated
|
||||
? this.updateViewer('editor')
|
||||
? this.updateViewer(this.file.pending ? 'diff' : 'editor')
|
||||
: Promise.resolve();
|
||||
|
||||
return viewerPromise;
|
||||
|
|
|
@ -62,11 +62,7 @@ export default {
|
|||
this.toggleTreeOpen(this.file.path);
|
||||
}
|
||||
|
||||
const delayPromise = this.file.changed
|
||||
? Promise.resolve()
|
||||
: this.updateDelayViewerUpdated(true);
|
||||
|
||||
return delayPromise.then(() => {
|
||||
return this.updateDelayViewerUpdated(true).then(() => {
|
||||
router.push(`/project${this.file.url}`);
|
||||
});
|
||||
},
|
||||
|
|
|
@ -1,60 +1,64 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import { mapActions } from 'vuex';
|
||||
|
||||
import fileIcon from '~/vue_shared/components/file_icon.vue';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import fileStatusIcon from './repo_file_status_icon.vue';
|
||||
import changedFileIcon from './changed_file_icon.vue';
|
||||
import FileIcon from '~/vue_shared/components/file_icon.vue';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import FileStatusIcon from './repo_file_status_icon.vue';
|
||||
import ChangedFileIcon from './changed_file_icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
fileStatusIcon,
|
||||
fileIcon,
|
||||
icon,
|
||||
changedFileIcon,
|
||||
export default {
|
||||
components: {
|
||||
FileStatusIcon,
|
||||
FileIcon,
|
||||
Icon,
|
||||
ChangedFileIcon,
|
||||
},
|
||||
props: {
|
||||
tab: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
tab: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabMouseOver: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
closeLabel() {
|
||||
if (this.tab.changed || this.tab.tempFile) {
|
||||
return `${this.tab.name} changed`;
|
||||
}
|
||||
return `Close ${this.tab.name}`;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabMouseOver: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
closeLabel() {
|
||||
if (this.tab.changed || this.tab.tempFile) {
|
||||
return `${this.tab.name} changed`;
|
||||
}
|
||||
return `Close ${this.tab.name}`;
|
||||
},
|
||||
showChangedIcon() {
|
||||
return this.tab.changed ? !this.tabMouseOver : false;
|
||||
},
|
||||
showChangedIcon() {
|
||||
return this.tab.changed ? !this.tabMouseOver : false;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions([
|
||||
'closeFile',
|
||||
]),
|
||||
clickFile(tab) {
|
||||
methods: {
|
||||
...mapActions(['closeFile', 'updateDelayViewerUpdated', 'openPendingTab']),
|
||||
clickFile(tab) {
|
||||
this.updateDelayViewerUpdated(true);
|
||||
|
||||
if (tab.pending) {
|
||||
this.openPendingTab(tab);
|
||||
} else {
|
||||
this.$router.push(`/project${tab.url}`);
|
||||
},
|
||||
mouseOverTab() {
|
||||
if (this.tab.changed) {
|
||||
this.tabMouseOver = true;
|
||||
}
|
||||
},
|
||||
mouseOutTab() {
|
||||
if (this.tab.changed) {
|
||||
this.tabMouseOver = false;
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
mouseOverTab() {
|
||||
if (this.tab.changed) {
|
||||
this.tabMouseOver = true;
|
||||
}
|
||||
},
|
||||
mouseOutTab() {
|
||||
if (this.tab.changed) {
|
||||
this.tabMouseOver = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -66,7 +70,7 @@
|
|||
<button
|
||||
type="button"
|
||||
class="multi-file-tab-close"
|
||||
@click.stop.prevent="closeFile(tab.path)"
|
||||
@click.stop.prevent="closeFile(tab)"
|
||||
:aria-label="closeLabel"
|
||||
>
|
||||
<icon
|
||||
|
@ -82,7 +86,9 @@
|
|||
|
||||
<div
|
||||
class="multi-file-tab"
|
||||
:class="{active : tab.active }"
|
||||
:class="{
|
||||
active: tab.active
|
||||
}"
|
||||
:title="tab.url"
|
||||
>
|
||||
<file-icon
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { mapActions } from 'vuex';
|
||||
import RepoTab from './repo_tab.vue';
|
||||
import EditorMode from './editor_mode_dropdown.vue';
|
||||
import router from '../ide_router';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -9,6 +10,10 @@ export default {
|
|||
EditorMode,
|
||||
},
|
||||
props: {
|
||||
activeFile: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
files: {
|
||||
type: Array,
|
||||
required: true,
|
||||
|
@ -38,7 +43,18 @@ export default {
|
|||
this.showShadow = this.$refs.tabsScroller.scrollWidth > this.$refs.tabsScroller.offsetWidth;
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['updateViewer']),
|
||||
...mapActions(['updateViewer', 'removePendingTab']),
|
||||
openFileViewer(viewer) {
|
||||
this.updateViewer(viewer);
|
||||
|
||||
if (this.activeFile.pending) {
|
||||
return this.removePendingTab(this.activeFile).then(() => {
|
||||
router.push(`/project${this.activeFile.url}`);
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -60,7 +76,7 @@ export default {
|
|||
:show-shadow="showShadow"
|
||||
:has-changes="hasChanges"
|
||||
:merge-request-id="mergeRequestId"
|
||||
@click="updateViewer"
|
||||
@click="openFileViewer"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -77,7 +77,11 @@ router.beforeEach((to, from, next) => {
|
|||
if (to.params[0]) {
|
||||
const path =
|
||||
to.params[0].slice(-1) === '/' ? to.params[0].slice(0, -1) : to.params[0];
|
||||
const treeEntry = store.state.entries[path];
|
||||
const treeEntryKey = Object.keys(store.state.entries).find(
|
||||
key => key === path && !store.state.entries[key].pending,
|
||||
);
|
||||
const treeEntry = store.state.entries[treeEntryKey];
|
||||
|
||||
if (treeEntry) {
|
||||
store.dispatch('handleTreeEntryAction', treeEntry);
|
||||
}
|
||||
|
|
|
@ -13,12 +13,12 @@ export default class Model {
|
|||
(this.originalModel = this.monaco.editor.createModel(
|
||||
this.file.raw,
|
||||
undefined,
|
||||
new this.monaco.Uri(null, null, `original/${this.file.path}`),
|
||||
new this.monaco.Uri(null, null, `original/${this.file.key}`),
|
||||
)),
|
||||
(this.model = this.monaco.editor.createModel(
|
||||
this.content,
|
||||
undefined,
|
||||
new this.monaco.Uri(null, null, this.file.path),
|
||||
new this.monaco.Uri(null, null, this.file.key),
|
||||
)),
|
||||
);
|
||||
if (this.file.mrChange) {
|
||||
|
@ -36,7 +36,7 @@ export default class Model {
|
|||
this.updateContent = this.updateContent.bind(this);
|
||||
this.dispose = this.dispose.bind(this);
|
||||
|
||||
eventHub.$on(`editor.update.model.dispose.${this.file.path}`, this.dispose);
|
||||
eventHub.$on(`editor.update.model.dispose.${this.file.key}`, this.dispose);
|
||||
eventHub.$on(`editor.update.model.content.${this.file.path}`, this.updateContent);
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ export default class Model {
|
|||
}
|
||||
|
||||
get path() {
|
||||
return this.file.path;
|
||||
return this.file.key;
|
||||
}
|
||||
|
||||
getModel() {
|
||||
|
@ -88,7 +88,7 @@ export default class Model {
|
|||
this.disposable.dispose();
|
||||
this.events.clear();
|
||||
|
||||
eventHub.$off(`editor.update.model.dispose.${this.file.path}`, this.dispose);
|
||||
eventHub.$off(`editor.update.model.dispose.${this.file.key}`, this.dispose);
|
||||
eventHub.$off(`editor.update.model.content.${this.file.path}`, this.updateContent);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,17 +9,17 @@ export default class ModelManager {
|
|||
this.models = new Map();
|
||||
}
|
||||
|
||||
hasCachedModel(path) {
|
||||
return this.models.has(path);
|
||||
hasCachedModel(key) {
|
||||
return this.models.has(key);
|
||||
}
|
||||
|
||||
getModel(path) {
|
||||
return this.models.get(path);
|
||||
getModel(key) {
|
||||
return this.models.get(key);
|
||||
}
|
||||
|
||||
addModel(file) {
|
||||
if (this.hasCachedModel(file.path)) {
|
||||
return this.getModel(file.path);
|
||||
if (this.hasCachedModel(file.key)) {
|
||||
return this.getModel(file.key);
|
||||
}
|
||||
|
||||
const model = new Model(this.monaco, file);
|
||||
|
@ -27,7 +27,7 @@ export default class ModelManager {
|
|||
this.disposable.add(model);
|
||||
|
||||
eventHub.$on(
|
||||
`editor.update.model.dispose.${file.path}`,
|
||||
`editor.update.model.dispose.${file.key}`,
|
||||
this.removeCachedModel.bind(this, file),
|
||||
);
|
||||
|
||||
|
@ -35,12 +35,9 @@ export default class ModelManager {
|
|||
}
|
||||
|
||||
removeCachedModel(file) {
|
||||
this.models.delete(file.path);
|
||||
this.models.delete(file.key);
|
||||
|
||||
eventHub.$off(
|
||||
`editor.update.model.dispose.${file.path}`,
|
||||
this.removeCachedModel,
|
||||
);
|
||||
eventHub.$off(`editor.update.model.dispose.${file.key}`, this.removeCachedModel);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
|
|
@ -21,7 +21,7 @@ export const discardAllChanges = ({ state, commit, dispatch }) => {
|
|||
};
|
||||
|
||||
export const closeAllFiles = ({ state, dispatch }) => {
|
||||
state.openFiles.forEach(file => dispatch('closeFile', file.path));
|
||||
state.openFiles.forEach(file => dispatch('closeFile', file));
|
||||
};
|
||||
|
||||
export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
|
||||
|
|
|
@ -6,24 +6,34 @@ import * as types from '../mutation_types';
|
|||
import router from '../../ide_router';
|
||||
import { setPageTitle } from '../utils';
|
||||
|
||||
export const closeFile = ({ commit, state, getters, dispatch }, path) => {
|
||||
const indexOfClosedFile = state.openFiles.findIndex(f => f.path === path);
|
||||
const file = state.entries[path];
|
||||
export const closeFile = ({ commit, state, dispatch }, file) => {
|
||||
const path = file.path;
|
||||
const indexOfClosedFile = state.openFiles.findIndex(f => f.key === file.key);
|
||||
const fileWasActive = file.active;
|
||||
|
||||
commit(types.TOGGLE_FILE_OPEN, path);
|
||||
commit(types.SET_FILE_ACTIVE, { path, active: false });
|
||||
if (file.pending) {
|
||||
commit(types.REMOVE_PENDING_TAB, file);
|
||||
} else {
|
||||
commit(types.TOGGLE_FILE_OPEN, path);
|
||||
commit(types.SET_FILE_ACTIVE, { path, active: false });
|
||||
}
|
||||
|
||||
if (state.openFiles.length > 0 && fileWasActive) {
|
||||
const nextIndexToOpen = indexOfClosedFile === 0 ? 0 : indexOfClosedFile - 1;
|
||||
const nextFileToOpen = state.entries[state.openFiles[nextIndexToOpen].path];
|
||||
const nextFileToOpen = state.openFiles[nextIndexToOpen];
|
||||
|
||||
router.push(`/project${nextFileToOpen.url}`);
|
||||
if (nextFileToOpen.pending) {
|
||||
dispatch('updateViewer', 'diff');
|
||||
dispatch('openPendingTab', nextFileToOpen);
|
||||
} else {
|
||||
dispatch('updateDelayViewerUpdated', true);
|
||||
router.push(`/project${nextFileToOpen.url}`);
|
||||
}
|
||||
} else if (!state.openFiles.length) {
|
||||
router.push(`/project/${file.projectId}/tree/${file.branchId}/`);
|
||||
}
|
||||
|
||||
eventHub.$emit(`editor.update.model.dispose.${file.path}`);
|
||||
eventHub.$emit(`editor.update.model.dispose.${file.key}`);
|
||||
};
|
||||
|
||||
export const setFileActive = ({ commit, state, getters, dispatch }, path) => {
|
||||
|
@ -151,3 +161,23 @@ export const discardFileChanges = ({ state, commit }, path) => {
|
|||
|
||||
eventHub.$emit(`editor.update.model.content.${file.path}`, file.raw);
|
||||
};
|
||||
|
||||
export const openPendingTab = ({ commit, getters, dispatch, state }, file) => {
|
||||
if (getters.activeFile && getters.activeFile.path === file.path && state.viewer === 'diff') {
|
||||
return false;
|
||||
}
|
||||
|
||||
commit(types.ADD_PENDING_TAB, { file });
|
||||
|
||||
dispatch('scrollToTab');
|
||||
|
||||
router.push(`/project/${file.projectId}/tree/${state.currentBranchId}/`);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const removePendingTab = ({ commit }, file) => {
|
||||
commit(types.REMOVE_PENDING_TAB, file);
|
||||
|
||||
eventHub.$emit(`editor.update.model.dispose.${file.key}`);
|
||||
};
|
||||
|
|
|
@ -49,3 +49,6 @@ export const CREATE_TMP_ENTRY = 'CREATE_TMP_ENTRY';
|
|||
export const SET_FILE_MERGE_REQUEST_CHANGE = 'SET_FILE_MERGE_REQUEST_CHANGE';
|
||||
export const UPDATE_VIEWER = 'UPDATE_VIEWER';
|
||||
export const UPDATE_DELAY_VIEWER_CHANGE = 'UPDATE_DELAY_VIEWER_CHANGE';
|
||||
|
||||
export const ADD_PENDING_TAB = 'ADD_PENDING_TAB';
|
||||
export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB';
|
||||
|
|
|
@ -5,6 +5,14 @@ export default {
|
|||
Object.assign(state.entries[path], {
|
||||
active,
|
||||
});
|
||||
|
||||
if (active && !state.entries[path].pending) {
|
||||
Object.assign(state, {
|
||||
openFiles: state.openFiles.map(f =>
|
||||
Object.assign(f, { active: f.pending ? false : f.active }),
|
||||
),
|
||||
});
|
||||
}
|
||||
},
|
||||
[types.TOGGLE_FILE_OPEN](state, path) {
|
||||
Object.assign(state.entries[path], {
|
||||
|
@ -12,10 +20,14 @@ export default {
|
|||
});
|
||||
|
||||
if (state.entries[path].opened) {
|
||||
state.openFiles.push(state.entries[path]);
|
||||
} else {
|
||||
Object.assign(state, {
|
||||
openFiles: state.openFiles.filter(f => f.path !== path),
|
||||
openFiles: state.openFiles.filter(f => f.path !== path).concat(state.entries[path]),
|
||||
});
|
||||
} else {
|
||||
const file = state.entries[path];
|
||||
|
||||
Object.assign(state, {
|
||||
openFiles: state.openFiles.filter(f => f.key !== file.key),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -92,4 +104,37 @@ export default {
|
|||
changed,
|
||||
});
|
||||
},
|
||||
[types.ADD_PENDING_TAB](state, { file, keyPrefix = 'pending' }) {
|
||||
const pendingTab = state.openFiles.find(f => f.path === file.path && f.pending);
|
||||
let openFiles = state.openFiles.map(f =>
|
||||
Object.assign(f, { active: f.path === file.path, opened: false }),
|
||||
);
|
||||
|
||||
if (!pendingTab) {
|
||||
const openFile = openFiles.find(f => f.path === file.path);
|
||||
|
||||
openFiles = openFiles.concat(openFile ? null : file).reduce((acc, f) => {
|
||||
if (!f) return acc;
|
||||
|
||||
if (f.path === file.path) {
|
||||
return acc.concat({
|
||||
...f,
|
||||
active: true,
|
||||
pending: true,
|
||||
opened: true,
|
||||
key: `${keyPrefix}-${f.key}`,
|
||||
});
|
||||
}
|
||||
|
||||
return acc.concat(f);
|
||||
}, []);
|
||||
}
|
||||
|
||||
Object.assign(state, { openFiles });
|
||||
},
|
||||
[types.REMOVE_PENDING_TAB](state, file) {
|
||||
Object.assign(state, {
|
||||
openFiles: state.openFiles.filter(f => f.key !== file.key),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
export const dataStructure = () => ({
|
||||
id: '',
|
||||
// Key will contain a mixture of ID and path
|
||||
// it can also contain a prefix `pending-` for files opened in review mode
|
||||
key: '',
|
||||
type: '',
|
||||
projectId: '',
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import Vue from 'vue';
|
||||
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 store from '~/ide/stores';
|
||||
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||
import { file, resetStore } from '../../helpers';
|
||||
|
||||
describe('Multi-file editor commit sidebar list item', () => {
|
||||
let vm;
|
||||
|
@ -13,19 +14,21 @@ describe('Multi-file editor commit sidebar list item', () => {
|
|||
|
||||
f = file('test-file');
|
||||
|
||||
vm = mountComponent(Component, {
|
||||
store.state.entries[f.path] = f;
|
||||
|
||||
vm = createComponentWithStore(Component, store, {
|
||||
file: f,
|
||||
});
|
||||
}).$mount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(store);
|
||||
});
|
||||
|
||||
it('renders file path', () => {
|
||||
expect(
|
||||
vm.$el.querySelector('.multi-file-commit-list-path').textContent.trim(),
|
||||
).toBe(f.path);
|
||||
expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent.trim()).toBe(f.path);
|
||||
});
|
||||
|
||||
it('calls discardFileChanges when clicking discard button', () => {
|
||||
|
@ -36,25 +39,32 @@ describe('Multi-file editor commit sidebar list item', () => {
|
|||
expect(vm.discardFileChanges).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('opens a closed file in the editor when clicking the file path', () => {
|
||||
it('opens a closed file in the editor when clicking the file path', done => {
|
||||
spyOn(vm, 'openFileInEditor').and.callThrough();
|
||||
spyOn(vm, 'updateViewer');
|
||||
spyOn(router, 'push');
|
||||
|
||||
vm.$el.querySelector('.multi-file-commit-list-path').click();
|
||||
|
||||
expect(vm.openFileInEditor).toHaveBeenCalled();
|
||||
expect(router.push).toHaveBeenCalled();
|
||||
setTimeout(() => {
|
||||
expect(vm.openFileInEditor).toHaveBeenCalled();
|
||||
expect(router.push).toHaveBeenCalled();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls updateViewer with diff when clicking file', () => {
|
||||
it('calls updateViewer with diff when clicking file', done => {
|
||||
spyOn(vm, 'openFileInEditor').and.callThrough();
|
||||
spyOn(vm, 'updateViewer');
|
||||
spyOn(vm, 'updateViewer').and.callThrough();
|
||||
spyOn(router, 'push');
|
||||
|
||||
vm.$el.querySelector('.multi-file-commit-list-path').click();
|
||||
|
||||
expect(vm.updateViewer).toHaveBeenCalledWith('diff');
|
||||
setTimeout(() => {
|
||||
expect(vm.updateViewer).toHaveBeenCalledWith('diff');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('computed', () => {
|
||||
|
|
|
@ -59,7 +59,7 @@ describe('RepoTab', () => {
|
|||
|
||||
vm.$el.querySelector('.multi-file-tab-close').click();
|
||||
|
||||
expect(vm.closeFile).toHaveBeenCalledWith(vm.tab.path);
|
||||
expect(vm.closeFile).toHaveBeenCalledWith(vm.tab);
|
||||
});
|
||||
|
||||
it('changes icon on hover', done => {
|
||||
|
|
|
@ -17,6 +17,7 @@ describe('RepoTabs', () => {
|
|||
files: openedFiles,
|
||||
viewer: 'editor',
|
||||
hasChanges: false,
|
||||
activeFile: file('activeFile'),
|
||||
hasMergeRequest: false,
|
||||
});
|
||||
openedFiles[0].active = true;
|
||||
|
@ -57,6 +58,7 @@ describe('RepoTabs', () => {
|
|||
files: [],
|
||||
viewer: 'editor',
|
||||
hasChanges: false,
|
||||
activeFile: file('activeFile'),
|
||||
hasMergeRequest: false,
|
||||
},
|
||||
'#test-app',
|
||||
|
|
|
@ -27,9 +27,10 @@ describe('Multi-file editor library model manager', () => {
|
|||
});
|
||||
|
||||
it('caches model by file path', () => {
|
||||
instance.addModel(file('path-name'));
|
||||
const f = file('path-name');
|
||||
instance.addModel(f);
|
||||
|
||||
expect(instance.models.keys().next().value).toBe('path-name');
|
||||
expect(instance.models.keys().next().value).toBe(f.key);
|
||||
});
|
||||
|
||||
it('adds model into disposable', () => {
|
||||
|
@ -56,7 +57,7 @@ describe('Multi-file editor library model manager', () => {
|
|||
instance.addModel(f);
|
||||
|
||||
expect(eventHub.$on).toHaveBeenCalledWith(
|
||||
`editor.update.model.dispose.${f.path}`,
|
||||
`editor.update.model.dispose.${f.key}`,
|
||||
jasmine.anything(),
|
||||
);
|
||||
});
|
||||
|
@ -68,9 +69,11 @@ describe('Multi-file editor library model manager', () => {
|
|||
});
|
||||
|
||||
it('returns true when model exists', () => {
|
||||
instance.addModel(file('path-name'));
|
||||
const f = file('path-name');
|
||||
|
||||
expect(instance.hasCachedModel('path-name')).toBeTruthy();
|
||||
instance.addModel(f);
|
||||
|
||||
expect(instance.hasCachedModel(f.key)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -103,7 +106,7 @@ describe('Multi-file editor library model manager', () => {
|
|||
instance.removeCachedModel(f);
|
||||
|
||||
expect(eventHub.$off).toHaveBeenCalledWith(
|
||||
`editor.update.model.dispose.${f.path}`,
|
||||
`editor.update.model.dispose.${f.key}`,
|
||||
jasmine.anything(),
|
||||
);
|
||||
});
|
||||
|
|
|
@ -32,14 +32,14 @@ describe('Multi-file editor library model', () => {
|
|||
|
||||
it('adds eventHub listener', () => {
|
||||
expect(eventHub.$on).toHaveBeenCalledWith(
|
||||
`editor.update.model.dispose.${model.file.path}`,
|
||||
`editor.update.model.dispose.${model.file.key}`,
|
||||
jasmine.anything(),
|
||||
);
|
||||
});
|
||||
|
||||
describe('path', () => {
|
||||
it('returns file path', () => {
|
||||
expect(model.path).toBe('path');
|
||||
expect(model.path).toBe(model.file.key);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -74,7 +74,7 @@ describe('Multi-file editor library model', () => {
|
|||
model.onChange(() => {});
|
||||
|
||||
expect(model.events.size).toBe(1);
|
||||
expect(model.events.keys().next().value).toBe('path');
|
||||
expect(model.events.keys().next().value).toBe(model.file.key);
|
||||
});
|
||||
|
||||
it('calls callback on change', done => {
|
||||
|
@ -115,7 +115,7 @@ describe('Multi-file editor library model', () => {
|
|||
model.dispose();
|
||||
|
||||
expect(eventHub.$off).toHaveBeenCalledWith(
|
||||
`editor.update.model.dispose.${model.file.path}`,
|
||||
`editor.update.model.dispose.${model.file.key}`,
|
||||
jasmine.anything(),
|
||||
);
|
||||
});
|
||||
|
|
|
@ -36,9 +36,7 @@ describe('Multi-file editor library decorations controller', () => {
|
|||
});
|
||||
|
||||
it('returns decorations by model URL', () => {
|
||||
controller.addDecorations(model, 'key', [
|
||||
{ decoration: 'decorationValue' },
|
||||
]);
|
||||
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
|
||||
|
||||
const decorations = controller.getAllDecorationsForModel(model);
|
||||
|
||||
|
@ -48,39 +46,29 @@ describe('Multi-file editor library decorations controller', () => {
|
|||
|
||||
describe('addDecorations', () => {
|
||||
it('caches decorations in a new map', () => {
|
||||
controller.addDecorations(model, 'key', [
|
||||
{ decoration: 'decorationValue' },
|
||||
]);
|
||||
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
|
||||
|
||||
expect(controller.decorations.size).toBe(1);
|
||||
});
|
||||
|
||||
it('does not create new cache model', () => {
|
||||
controller.addDecorations(model, 'key', [
|
||||
{ decoration: 'decorationValue' },
|
||||
]);
|
||||
controller.addDecorations(model, 'key', [
|
||||
{ decoration: 'decorationValue2' },
|
||||
]);
|
||||
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
|
||||
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue2' }]);
|
||||
|
||||
expect(controller.decorations.size).toBe(1);
|
||||
});
|
||||
|
||||
it('caches decorations by model URL', () => {
|
||||
controller.addDecorations(model, 'key', [
|
||||
{ decoration: 'decorationValue' },
|
||||
]);
|
||||
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
|
||||
|
||||
expect(controller.decorations.size).toBe(1);
|
||||
expect(controller.decorations.keys().next().value).toBe('path');
|
||||
expect(controller.decorations.keys().next().value).toBe('path--path');
|
||||
});
|
||||
|
||||
it('calls decorate method', () => {
|
||||
spyOn(controller, 'decorate');
|
||||
|
||||
controller.addDecorations(model, 'key', [
|
||||
{ decoration: 'decorationValue' },
|
||||
]);
|
||||
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
|
||||
|
||||
expect(controller.decorate).toHaveBeenCalled();
|
||||
});
|
||||
|
@ -92,10 +80,7 @@ describe('Multi-file editor library decorations controller', () => {
|
|||
|
||||
controller.decorate(model);
|
||||
|
||||
expect(controller.editor.instance.deltaDecorations).toHaveBeenCalledWith(
|
||||
[],
|
||||
[],
|
||||
);
|
||||
expect(controller.editor.instance.deltaDecorations).toHaveBeenCalledWith([], []);
|
||||
});
|
||||
|
||||
it('caches decorations', () => {
|
||||
|
@ -111,15 +96,13 @@ describe('Multi-file editor library decorations controller', () => {
|
|||
|
||||
controller.decorate(model);
|
||||
|
||||
expect(controller.editorDecorations.keys().next().value).toBe('path');
|
||||
expect(controller.editorDecorations.keys().next().value).toBe('path--path');
|
||||
});
|
||||
});
|
||||
|
||||
describe('dispose', () => {
|
||||
it('clears cached decorations', () => {
|
||||
controller.addDecorations(model, 'key', [
|
||||
{ decoration: 'decorationValue' },
|
||||
]);
|
||||
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
|
||||
|
||||
controller.dispose();
|
||||
|
||||
|
@ -127,9 +110,7 @@ describe('Multi-file editor library decorations controller', () => {
|
|||
});
|
||||
|
||||
it('clears cached editorDecorations', () => {
|
||||
controller.addDecorations(model, 'key', [
|
||||
{ decoration: 'decorationValue' },
|
||||
]);
|
||||
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
|
||||
|
||||
controller.dispose();
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ describe('Multi-file editor library dirty diff controller', () => {
|
|||
it('adds decorations into decorations controller', () => {
|
||||
spyOn(controller.decorationsController, 'addDecorations');
|
||||
|
||||
controller.decorate({ data: { changes: [], path: 'path' } });
|
||||
controller.decorate({ data: { changes: [], path: model.path } });
|
||||
|
||||
expect(
|
||||
controller.decorationsController.addDecorations,
|
||||
|
@ -145,7 +145,7 @@ describe('Multi-file editor library dirty diff controller', () => {
|
|||
);
|
||||
|
||||
controller.decorate({
|
||||
data: { changes: computeDiff('123', '1234'), path: 'path' },
|
||||
data: { changes: computeDiff('123', '1234'), path: model.path },
|
||||
});
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('IDE store file actions', () => {
|
|||
|
||||
it('closes open files', done => {
|
||||
store
|
||||
.dispatch('closeFile', localFile.path)
|
||||
.dispatch('closeFile', localFile)
|
||||
.then(() => {
|
||||
expect(localFile.opened).toBeFalsy();
|
||||
expect(localFile.active).toBeFalsy();
|
||||
|
@ -44,7 +44,7 @@ describe('IDE store file actions', () => {
|
|||
store.state.changedFiles.push(localFile);
|
||||
|
||||
store
|
||||
.dispatch('closeFile', localFile.path)
|
||||
.dispatch('closeFile', localFile)
|
||||
.then(Vue.nextTick)
|
||||
.then(() => {
|
||||
expect(store.state.openFiles.length).toBe(0);
|
||||
|
@ -65,7 +65,7 @@ describe('IDE store file actions', () => {
|
|||
store.state.entries[f.path] = f;
|
||||
|
||||
store
|
||||
.dispatch('closeFile', localFile.path)
|
||||
.dispatch('closeFile', localFile)
|
||||
.then(Vue.nextTick)
|
||||
.then(() => {
|
||||
expect(router.push).toHaveBeenCalledWith(`/project${f.url}`);
|
||||
|
@ -74,6 +74,22 @@ describe('IDE store file actions', () => {
|
|||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('removes file if it pending', done => {
|
||||
store.state.openFiles.push({
|
||||
...localFile,
|
||||
pending: true,
|
||||
});
|
||||
|
||||
store
|
||||
.dispatch('closeFile', localFile)
|
||||
.then(() => {
|
||||
expect(store.state.openFiles.length).toBe(0);
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setFileActive', () => {
|
||||
|
@ -445,4 +461,113 @@ describe('IDE store file actions', () => {
|
|||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('openPendingTab', () => {
|
||||
let f;
|
||||
|
||||
beforeEach(() => {
|
||||
f = {
|
||||
...file(),
|
||||
projectId: '123',
|
||||
};
|
||||
|
||||
store.state.entries[f.path] = f;
|
||||
});
|
||||
|
||||
it('makes file pending in openFiles', done => {
|
||||
store
|
||||
.dispatch('openPendingTab', f)
|
||||
.then(() => {
|
||||
expect(store.state.openFiles[0].pending).toBe(true);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('returns true when opened', done => {
|
||||
store
|
||||
.dispatch('openPendingTab', f)
|
||||
.then(added => {
|
||||
expect(added).toBe(true);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('pushes router URL when added', done => {
|
||||
store.state.currentBranchId = 'master';
|
||||
|
||||
store
|
||||
.dispatch('openPendingTab', f)
|
||||
.then(() => {
|
||||
expect(router.push).toHaveBeenCalledWith('/project/123/tree/master/');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('calls scrollToTab', done => {
|
||||
const scrollToTabSpy = jasmine.createSpy('scrollToTab');
|
||||
const oldScrollToTab = store._actions.scrollToTab; // eslint-disable-line
|
||||
store._actions.scrollToTab = [scrollToTabSpy]; // eslint-disable-line
|
||||
|
||||
store
|
||||
.dispatch('openPendingTab', f)
|
||||
.then(() => {
|
||||
expect(scrollToTabSpy).toHaveBeenCalled();
|
||||
store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('returns false when passed in file is active & viewer is diff', done => {
|
||||
f.active = true;
|
||||
store.state.openFiles.push(f);
|
||||
store.state.viewer = 'diff';
|
||||
|
||||
store
|
||||
.dispatch('openPendingTab', f)
|
||||
.then(added => {
|
||||
expect(added).toBe(false);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removePendingTab', () => {
|
||||
let f;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(eventHub, '$emit');
|
||||
|
||||
f = {
|
||||
...file('pendingFile'),
|
||||
pending: true,
|
||||
};
|
||||
});
|
||||
|
||||
it('removes pending file from open files', done => {
|
||||
store.state.openFiles.push(f);
|
||||
|
||||
store
|
||||
.dispatch('removePendingTab', f)
|
||||
.then(() => {
|
||||
expect(store.state.openFiles.length).toBe(0);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('emits event to dispose model', done => {
|
||||
store
|
||||
.dispatch('removePendingTab', f)
|
||||
.then(() => {
|
||||
expect(eventHub.$emit).toHaveBeenCalledWith(`editor.update.model.dispose.${f.key}`);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,6 +22,21 @@ describe('IDE store file mutations', () => {
|
|||
|
||||
expect(localFile.active).toBeTruthy();
|
||||
});
|
||||
|
||||
it('sets pending tab as not active', () => {
|
||||
localState.openFiles.push({
|
||||
...localFile,
|
||||
pending: true,
|
||||
active: true,
|
||||
});
|
||||
|
||||
mutations.SET_FILE_ACTIVE(localState, {
|
||||
path: localFile.path,
|
||||
active: true,
|
||||
});
|
||||
|
||||
expect(localState.openFiles[0].active).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TOGGLE_FILE_OPEN', () => {
|
||||
|
@ -178,4 +193,69 @@ describe('IDE store file mutations', () => {
|
|||
expect(localFile.changed).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ADD_PENDING_TAB', () => {
|
||||
beforeEach(() => {
|
||||
const f = {
|
||||
...file('openFile'),
|
||||
path: 'openFile',
|
||||
active: true,
|
||||
opened: true,
|
||||
};
|
||||
|
||||
localState.entries[f.path] = f;
|
||||
localState.openFiles.push(f);
|
||||
});
|
||||
|
||||
it('adds file into openFiles as pending', () => {
|
||||
mutations.ADD_PENDING_TAB(localState, { file: localFile });
|
||||
|
||||
expect(localState.openFiles.length).toBe(2);
|
||||
expect(localState.openFiles[1].pending).toBe(true);
|
||||
expect(localState.openFiles[1].key).toBe(`pending-${localFile.key}`);
|
||||
});
|
||||
|
||||
it('updates open file to pending', () => {
|
||||
mutations.ADD_PENDING_TAB(localState, { file: localState.openFiles[0] });
|
||||
|
||||
expect(localState.openFiles.length).toBe(1);
|
||||
});
|
||||
|
||||
it('updates pending open file to active', () => {
|
||||
localState.openFiles.push({
|
||||
...localFile,
|
||||
pending: true,
|
||||
});
|
||||
|
||||
mutations.ADD_PENDING_TAB(localState, { file: localFile });
|
||||
|
||||
expect(localState.openFiles[1].pending).toBe(true);
|
||||
expect(localState.openFiles[1].active).toBe(true);
|
||||
});
|
||||
|
||||
it('sets all openFiles to not active', () => {
|
||||
mutations.ADD_PENDING_TAB(localState, { file: localFile });
|
||||
|
||||
expect(localState.openFiles.length).toBe(2);
|
||||
|
||||
localState.openFiles.forEach(f => {
|
||||
if (f.pending) {
|
||||
expect(f.active).toBe(true);
|
||||
} else {
|
||||
expect(f.active).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('REMOVE_PENDING_TAB', () => {
|
||||
it('removes pending tab from openFiles', () => {
|
||||
localFile.key = 'testing';
|
||||
localState.openFiles.push(localFile);
|
||||
|
||||
mutations.REMOVE_PENDING_TAB(localState, localFile);
|
||||
|
||||
expect(localState.openFiles.length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue