提交 eaed588b 编写于 作者: T Tim Zallmann

Merge branch 'ide-pending-tab' into 'master'

Added pending tabs to IDE

See merge request gitlab-org/gitlab-ce!17936
<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}`);
});
},
......
<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', 'updateDelayViewerUpdated', 'openPendingTab']),
clickFile(tab) {
this.updateDelayViewerUpdated(true);
methods: {
...mapActions([
'closeFile',
]),
clickFile(tab) {
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];
router.push(`/project${nextFileToOpen.url}`);
const nextFileToOpen = state.openFiles[nextIndexToOpen];
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]);
Object.assign(state, {
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.path !== path),
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),
});
},
};
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: '',
......
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');
instance.addModel(f);
expect(instance.hasCachedModel('path-name')).toBeTruthy();
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);
});
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册