Merge branch 'remove-ide' into 'master'
Remove IDE from CE See merge request gitlab-org/gitlab-ce!17458
This commit is contained in:
commit
67feb7cd42
|
@ -1,65 +0,0 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import icon from '../../../vue_shared/components/icon.vue';
|
||||
import listItem from './list_item.vue';
|
||||
import listCollapsed from './list_collapsed.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
listItem,
|
||||
listCollapsed,
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
fileList: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'currentProjectId',
|
||||
'currentBranchId',
|
||||
'rightPanelCollapsed',
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
toggleCollapsed() {
|
||||
this.$emit('toggleCollapsed');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="multi-file-commit-list">
|
||||
<list-collapsed
|
||||
v-if="rightPanelCollapsed"
|
||||
/>
|
||||
<template v-else>
|
||||
<ul
|
||||
v-if="fileList.length"
|
||||
class="list-unstyled append-bottom-0"
|
||||
>
|
||||
<li
|
||||
v-for="file in fileList"
|
||||
:key="file.key"
|
||||
>
|
||||
<list-item
|
||||
:file="file"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
<div
|
||||
v-else
|
||||
class="help-block prepend-top-0"
|
||||
>
|
||||
No changes
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
|
@ -1,35 +0,0 @@
|
|||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import icon from '../../../vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'addedFiles',
|
||||
'modifiedFiles',
|
||||
]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="multi-file-commit-list-collapsed text-center"
|
||||
>
|
||||
<icon
|
||||
name="file-addition"
|
||||
:size="18"
|
||||
css-classes="multi-file-addition append-bottom-10"
|
||||
/>
|
||||
{{ addedFiles.length }}
|
||||
<icon
|
||||
name="file-modified"
|
||||
:size="18"
|
||||
css-classes="multi-file-modified prepend-top-10 append-bottom-10"
|
||||
/>
|
||||
{{ modifiedFiles.length }}
|
||||
</div>
|
||||
</template>
|
|
@ -1,36 +0,0 @@
|
|||
<script>
|
||||
import icon from '../../../vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
},
|
||||
props: {
|
||||
file: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
iconName() {
|
||||
return this.file.tempFile ? 'file-addition' : 'file-modified';
|
||||
},
|
||||
iconClass() {
|
||||
return `multi-file-${this.file.tempFile ? 'addition' : 'modified'} append-right-8`;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="multi-file-commit-list-item">
|
||||
<icon
|
||||
:name="iconName"
|
||||
:size="16"
|
||||
:css-classes="iconClass"
|
||||
/>
|
||||
<span class="multi-file-commit-list-path">
|
||||
{{ file.path }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
|
@ -1,99 +0,0 @@
|
|||
<script>
|
||||
import { mapState, mapGetters } from 'vuex';
|
||||
import ideSidebar from './ide_side_bar.vue';
|
||||
import ideContextbar from './ide_context_bar.vue';
|
||||
import repoTabs from './repo_tabs.vue';
|
||||
import repoFileButtons from './repo_file_buttons.vue';
|
||||
import ideStatusBar from './ide_status_bar.vue';
|
||||
import repoPreview from './repo_preview.vue';
|
||||
import repoEditor from './repo_editor.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ideSidebar,
|
||||
ideContextbar,
|
||||
repoTabs,
|
||||
repoFileButtons,
|
||||
ideStatusBar,
|
||||
repoEditor,
|
||||
repoPreview,
|
||||
},
|
||||
props: {
|
||||
emptyStateSvgPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'currentBlobView',
|
||||
'selectedFile',
|
||||
]),
|
||||
...mapGetters([
|
||||
'changedFiles',
|
||||
'activeFile',
|
||||
]),
|
||||
},
|
||||
mounted() {
|
||||
const returnValue = 'Are you sure you want to lose unsaved changes?';
|
||||
window.onbeforeunload = (e) => {
|
||||
if (!this.changedFiles.length) return undefined;
|
||||
|
||||
Object.assign(e, {
|
||||
returnValue,
|
||||
});
|
||||
return returnValue;
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="ide-view"
|
||||
>
|
||||
<ide-sidebar />
|
||||
<div
|
||||
class="multi-file-edit-pane"
|
||||
>
|
||||
<template
|
||||
v-if="activeFile"
|
||||
>
|
||||
<repo-tabs/>
|
||||
<component
|
||||
class="multi-file-edit-pane-content"
|
||||
:is="currentBlobView"
|
||||
/>
|
||||
<repo-file-buttons />
|
||||
<ide-status-bar
|
||||
:file="selectedFile"
|
||||
/>
|
||||
</template>
|
||||
<template
|
||||
v-else
|
||||
>
|
||||
<div class="ide-empty-state">
|
||||
<div class="row js-empty-state">
|
||||
<div class="col-xs-12">
|
||||
<div class="svg-content svg-250">
|
||||
<img :src="emptyStateSvgPath" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12">
|
||||
<div class="text-content text-center">
|
||||
<h4>
|
||||
Welcome to the GitLab IDE
|
||||
</h4>
|
||||
<p>
|
||||
You can select a file in the left sidebar to begin
|
||||
editing and use the right sidebar to commit your changes.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<ide-contextbar/>
|
||||
</div>
|
||||
</template>
|
|
@ -1,108 +0,0 @@
|
|||
<script>
|
||||
import { mapGetters, mapState, mapActions } 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';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
repoCommitSection,
|
||||
icon,
|
||||
panelResizer,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
width: 290,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'rightPanelCollapsed',
|
||||
]),
|
||||
...mapGetters([
|
||||
'changedFiles',
|
||||
]),
|
||||
currentIcon() {
|
||||
return this.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
|
||||
},
|
||||
maxSize() {
|
||||
return window.innerWidth / 2;
|
||||
},
|
||||
panelStyle() {
|
||||
if (!this.rightPanelCollapsed) {
|
||||
return { width: `${this.width}px` };
|
||||
}
|
||||
return {};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'setPanelCollapsedStatus',
|
||||
'setResizingStatus',
|
||||
]),
|
||||
toggleCollapsed() {
|
||||
this.setPanelCollapsedStatus({
|
||||
side: 'right',
|
||||
collapsed: !this.rightPanelCollapsed,
|
||||
});
|
||||
},
|
||||
resizingStarted() {
|
||||
this.setResizingStatus(true);
|
||||
},
|
||||
resizingEnded() {
|
||||
this.setResizingStatus(false);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="multi-file-commit-panel"
|
||||
:class="{
|
||||
'is-collapsed': rightPanelCollapsed,
|
||||
}"
|
||||
:style="panelStyle"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<icon
|
||||
name="list-bulleted"
|
||||
:size="18"
|
||||
/>
|
||||
Staged
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-transparent multi-file-commit-panel-collapse-btn"
|
||||
@click="toggleCollapsed"
|
||||
>
|
||||
<icon
|
||||
:name="currentIcon"
|
||||
:size="18"
|
||||
/>
|
||||
</button>
|
||||
</header>
|
||||
<repo-commit-section />
|
||||
</div>
|
||||
<panel-resizer
|
||||
:size.sync="width"
|
||||
:enabled="!rightPanelCollapsed"
|
||||
:start-size="290"
|
||||
:min-size="200"
|
||||
:max-size="maxSize"
|
||||
@resize-start="resizingStarted"
|
||||
@resize-end="resizingEnded"
|
||||
side="left"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
|
@ -1,47 +0,0 @@
|
|||
<script>
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import repoTree from './ide_repo_tree.vue';
|
||||
import newDropdown from './new_dropdown/index.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
repoTree,
|
||||
icon,
|
||||
newDropdown,
|
||||
},
|
||||
props: {
|
||||
projectId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
branch: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="branch-container">
|
||||
<div class="branch-header">
|
||||
<div class="branch-header-title">
|
||||
<icon
|
||||
name="branch"
|
||||
:size="12"
|
||||
/>
|
||||
{{ branch.name }}
|
||||
</div>
|
||||
<div class="branch-header-btns">
|
||||
<new-dropdown
|
||||
:project-id="projectId"
|
||||
:branch="branch.name"
|
||||
path=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<repo-tree :tree-id="branch.treeId" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,49 +0,0 @@
|
|||
<script>
|
||||
import projectAvatarImage from '~/vue_shared/components/project_avatar/image.vue';
|
||||
import branchesTree from './ide_project_branches_tree.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
branchesTree,
|
||||
projectAvatarImage,
|
||||
},
|
||||
props: {
|
||||
project: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="projects-sidebar">
|
||||
<div class="context-header">
|
||||
<a
|
||||
:title="project.name"
|
||||
:href="project.web_url"
|
||||
>
|
||||
<div class="avatar-container s40 project-avatar">
|
||||
<project-avatar-image
|
||||
class="avatar-container project-avatar"
|
||||
:link-href="project.path"
|
||||
:img-src="project.avatar_url"
|
||||
:img-alt="project.name"
|
||||
:img-size="40"
|
||||
/>
|
||||
</div>
|
||||
<div class="sidebar-context-title">
|
||||
{{ project.name }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="multi-file-commit-panel-inner-scroll">
|
||||
<branches-tree
|
||||
v-for="branch in project.branches"
|
||||
:key="branch.name"
|
||||
:project-id="project.path_with_namespace"
|
||||
:branch="branch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,74 +0,0 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
|
||||
import repoPreviousDirectory from './repo_prev_directory.vue';
|
||||
import repoFile from './repo_file.vue';
|
||||
import { treeList } from '../stores/utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
repoPreviousDirectory,
|
||||
repoFile,
|
||||
skeletonLoadingContainer,
|
||||
},
|
||||
props: {
|
||||
treeId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'trees',
|
||||
'isRoot',
|
||||
]),
|
||||
...mapState({
|
||||
projectName(state) {
|
||||
return state.project.name;
|
||||
},
|
||||
}),
|
||||
fetchedList() {
|
||||
return treeList(this.$store.state, this.treeId);
|
||||
},
|
||||
hasPreviousDirectory() {
|
||||
return !this.isRoot && this.fetchedList.length;
|
||||
},
|
||||
showLoading() {
|
||||
if (this.trees[this.treeId]) {
|
||||
return this.trees[this.treeId].loading;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="ide-file-list">
|
||||
<table class="table">
|
||||
<tbody
|
||||
v-if="treeId"
|
||||
>
|
||||
<repo-previous-directory
|
||||
v-if="hasPreviousDirectory"
|
||||
/>
|
||||
<template v-if="showLoading">
|
||||
<div
|
||||
class="multi-file-loading-container"
|
||||
v-for="n in 3"
|
||||
:key="n"
|
||||
>
|
||||
<skeleton-loading-container />
|
||||
</div>
|
||||
</template>
|
||||
<repo-file
|
||||
v-for="file in fetchedList"
|
||||
:key="file.key"
|
||||
:file="file"
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,114 +0,0 @@
|
|||
<script>
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import panelResizer from '~/vue_shared/components/panel_resizer.vue';
|
||||
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
|
||||
import projectTree from './ide_project_tree.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
projectTree,
|
||||
icon,
|
||||
panelResizer,
|
||||
skeletonLoadingContainer,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
width: 290,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'loading',
|
||||
'projects',
|
||||
'leftPanelCollapsed',
|
||||
]),
|
||||
currentIcon() {
|
||||
return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left';
|
||||
},
|
||||
maxSize() {
|
||||
return window.innerWidth / 2;
|
||||
},
|
||||
panelStyle() {
|
||||
if (!this.leftPanelCollapsed) {
|
||||
return { width: `${this.width}px` };
|
||||
}
|
||||
return {};
|
||||
},
|
||||
showLoading() {
|
||||
return this.loading;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'setPanelCollapsedStatus',
|
||||
'setResizingStatus',
|
||||
]),
|
||||
toggleCollapsed() {
|
||||
this.setPanelCollapsedStatus({
|
||||
side: 'left',
|
||||
collapsed: !this.leftPanelCollapsed,
|
||||
});
|
||||
},
|
||||
resizingStarted() {
|
||||
this.setResizingStatus(true);
|
||||
},
|
||||
resizingEnded() {
|
||||
this.setResizingStatus(false);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="multi-file-commit-panel"
|
||||
:class="{
|
||||
'is-collapsed': leftPanelCollapsed,
|
||||
}"
|
||||
:style="panelStyle"
|
||||
>
|
||||
<div class="multi-file-commit-panel-inner">
|
||||
<template v-if="showLoading">
|
||||
<div
|
||||
class="multi-file-loading-container"
|
||||
v-for="n in 3"
|
||||
:key="n"
|
||||
>
|
||||
<skeleton-loading-container />
|
||||
</div>
|
||||
</template>
|
||||
<project-tree
|
||||
v-for="project in projects"
|
||||
:key="project.id"
|
||||
:project="project"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-transparent left-collapse-btn"
|
||||
@click="toggleCollapsed"
|
||||
>
|
||||
<icon
|
||||
:name="currentIcon"
|
||||
:size="18"
|
||||
/>
|
||||
<span
|
||||
v-if="!leftPanelCollapsed"
|
||||
class="collapse-text"
|
||||
>
|
||||
Collapse sidebar
|
||||
</span>
|
||||
</button>
|
||||
<panel-resizer
|
||||
:size.sync="width"
|
||||
:enabled="!leftPanelCollapsed"
|
||||
:start-size="290"
|
||||
:min-size="200"
|
||||
:max-size="maxSize"
|
||||
@resize-start="resizingStarted"
|
||||
@resize-end="resizingEnded"
|
||||
side="right"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
|
@ -1,66 +0,0 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import timeAgoMixin from '~/vue_shared/mixins/timeago';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
mixins: [
|
||||
timeAgoMixin,
|
||||
],
|
||||
props: {
|
||||
file: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'selectedFile',
|
||||
]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ide-status-bar">
|
||||
<div>
|
||||
<icon
|
||||
name="branch"
|
||||
:size="12"
|
||||
/>
|
||||
{{ selectedFile.branchId }}
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="selectedFile.lastCommit && selectedFile.lastCommit.id">
|
||||
Last commit:
|
||||
<a
|
||||
v-tooltip
|
||||
:title="selectedFile.lastCommit.message"
|
||||
:href="selectedFile.lastCommit.url"
|
||||
>
|
||||
{{ timeFormated(selectedFile.lastCommit.updatedAt) }} by
|
||||
{{ selectedFile.lastCommit.author }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
{{ selectedFile.name }}
|
||||
</div>
|
||||
<div class="text-right">
|
||||
{{ selectedFile.eol }}
|
||||
</div>
|
||||
<div class="text-right">
|
||||
{{ file.editorRow }}:{{ file.editorColumn }}
|
||||
</div>
|
||||
<div class="text-right">
|
||||
{{ selectedFile.fileLanguage }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,108 +0,0 @@
|
|||
<script>
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import flash, { hideFlash } from '~/flash';
|
||||
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
loadingIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
branchName: '',
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'currentBranch',
|
||||
]),
|
||||
btnDisabled() {
|
||||
return this.loading || this.branchName === '';
|
||||
},
|
||||
},
|
||||
created() {
|
||||
// Dropdown is outside of Vue instance & is controlled by Bootstrap
|
||||
this.$dropdown = $('.git-revision-dropdown');
|
||||
|
||||
// text element is outside Vue app
|
||||
this.dropdownText = document.querySelector('.project-refs-form .dropdown-toggle-text');
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'createNewBranch',
|
||||
]),
|
||||
toggleDropdown() {
|
||||
this.$dropdown.dropdown('toggle');
|
||||
},
|
||||
submitNewBranch() {
|
||||
// need to query as the element is appended outside of Vue
|
||||
const flashEl = this.$refs.flashContainer.querySelector('.flash-alert');
|
||||
|
||||
this.loading = true;
|
||||
|
||||
if (flashEl) {
|
||||
hideFlash(flashEl, false);
|
||||
}
|
||||
|
||||
this.createNewBranch(this.branchName)
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
this.branchName = '';
|
||||
|
||||
if (this.dropdownText) {
|
||||
this.dropdownText.textContent = this.currentBranchId;
|
||||
}
|
||||
|
||||
this.toggleDropdown();
|
||||
})
|
||||
.catch(res => res.json().then((data) => {
|
||||
this.loading = false;
|
||||
flash(data.message, 'alert', this.$el);
|
||||
}));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="flash-container"
|
||||
ref="flashContainer"
|
||||
>
|
||||
</div>
|
||||
<p>
|
||||
Create from:
|
||||
<code>{{ currentBranch }}</code>
|
||||
</p>
|
||||
<input
|
||||
class="form-control js-new-branch-name"
|
||||
type="text"
|
||||
placeholder="Name new branch"
|
||||
v-model="branchName"
|
||||
@keyup.enter.stop.prevent="submitNewBranch"
|
||||
/>
|
||||
<div class="prepend-top-default clearfix">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary pull-left"
|
||||
:disabled="btnDisabled"
|
||||
@click.stop.prevent="submitNewBranch"
|
||||
>
|
||||
<loading-icon
|
||||
v-if="loading"
|
||||
:inline="true"
|
||||
/>
|
||||
<span>Create</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default pull-right"
|
||||
@click.stop.prevent="toggleDropdown"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,101 +0,0 @@
|
|||
<script>
|
||||
import newModal from './modal.vue';
|
||||
import upload from './upload.vue';
|
||||
import icon from '../../../vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
icon,
|
||||
newModal,
|
||||
upload,
|
||||
},
|
||||
props: {
|
||||
branch: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
parent: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
openModal: false,
|
||||
modalType: '',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
createNewItem(type) {
|
||||
this.modalType = type;
|
||||
this.openModal = true;
|
||||
},
|
||||
hideModal() {
|
||||
this.openModal = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="repo-new-btn pull-right">
|
||||
<div class="dropdown">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-default dropdown-toggle add-to-tree"
|
||||
data-toggle="dropdown"
|
||||
aria-label="Create new file or directory"
|
||||
>
|
||||
<icon
|
||||
name="plus"
|
||||
:size="12"
|
||||
css-classes="pull-left"
|
||||
/>
|
||||
<icon
|
||||
name="arrow-down"
|
||||
:size="12"
|
||||
css-classes="pull-left"
|
||||
/>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
@click.prevent="createNewItem('blob')"
|
||||
>
|
||||
{{ __('New file') }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<upload
|
||||
:branch-id="branch"
|
||||
:path="path"
|
||||
:parent="parent"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
@click.prevent="createNewItem('tree')"
|
||||
>
|
||||
{{ __('New directory') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<new-modal
|
||||
v-if="openModal"
|
||||
:type="modalType"
|
||||
:branch-id="branch"
|
||||
:path="path"
|
||||
:parent="parent"
|
||||
@hide="hideModal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
|
@ -1,112 +0,0 @@
|
|||
<script>
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
import { __ } from '../../../locale';
|
||||
import modal from '../../../vue_shared/components/modal.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
modal,
|
||||
},
|
||||
props: {
|
||||
branchId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
parent: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
path: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
entryName: this.path !== '' ? `${this.path}/` : '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'currentProjectId',
|
||||
]),
|
||||
modalTitle() {
|
||||
if (this.type === 'tree') {
|
||||
return __('Create new directory');
|
||||
}
|
||||
|
||||
return __('Create new file');
|
||||
},
|
||||
buttonLabel() {
|
||||
if (this.type === 'tree') {
|
||||
return __('Create directory');
|
||||
}
|
||||
|
||||
return __('Create file');
|
||||
},
|
||||
formLabelName() {
|
||||
if (this.type === 'tree') {
|
||||
return __('Directory name');
|
||||
}
|
||||
|
||||
return __('File name');
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.fieldName.focus();
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'createTempEntry',
|
||||
]),
|
||||
createEntryInStore() {
|
||||
this.createTempEntry({
|
||||
projectId: this.currentProjectId,
|
||||
branchId: this.branchId,
|
||||
parent: this.parent,
|
||||
name: this.entryName.replace(new RegExp(`^${this.path}/`), ''),
|
||||
type: this.type,
|
||||
});
|
||||
|
||||
this.hideModal();
|
||||
},
|
||||
hideModal() {
|
||||
this.$emit('hide');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<modal
|
||||
:title="modalTitle"
|
||||
:primary-button-label="buttonLabel"
|
||||
kind="success"
|
||||
@cancel="hideModal"
|
||||
@submit="createEntryInStore"
|
||||
>
|
||||
<form
|
||||
class="form-horizontal"
|
||||
slot="body"
|
||||
@submit.prevent="createEntryInStore"
|
||||
>
|
||||
<fieldset class="form-group append-bottom-0">
|
||||
<label class="label-light col-sm-3">
|
||||
{{ formLabelName }}
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
v-model="entryName"
|
||||
ref="fieldName"
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</modal>
|
||||
</template>
|
|
@ -1,87 +0,0 @@
|
|||
<script>
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
branchId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
parent: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'trees',
|
||||
'currentProjectId',
|
||||
]),
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.fileUpload.addEventListener('change', this.openFile);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$refs.fileUpload.removeEventListener('change', this.openFile);
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'createTempEntry',
|
||||
]),
|
||||
createFile(target, file, isText) {
|
||||
const { name } = file;
|
||||
let { result } = target;
|
||||
|
||||
if (!isText) {
|
||||
result = result.split('base64,')[1];
|
||||
}
|
||||
|
||||
this.createTempEntry({
|
||||
name,
|
||||
projectId: this.currentProjectId,
|
||||
branchId: this.branchId,
|
||||
parent: this.parent,
|
||||
type: 'blob',
|
||||
content: result,
|
||||
base64: !isText,
|
||||
});
|
||||
},
|
||||
readFile(file) {
|
||||
const reader = new FileReader();
|
||||
const isText = file.type.match(/text.*/) !== null;
|
||||
|
||||
reader.addEventListener('load', e => this.createFile(e.target, file, isText), { once: true });
|
||||
|
||||
if (isText) {
|
||||
reader.readAsText(file);
|
||||
} else {
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
},
|
||||
openFile() {
|
||||
Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file));
|
||||
},
|
||||
startFileUpload() {
|
||||
this.$refs.fileUpload.click();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
@click.prevent="startFileUpload"
|
||||
>
|
||||
{{ __('Upload file') }}
|
||||
</a>
|
||||
<input
|
||||
id="file-upload"
|
||||
type="file"
|
||||
class="hidden"
|
||||
ref="fileUpload"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
|
@ -1,171 +0,0 @@
|
|||
<script>
|
||||
import { mapGetters, mapState, mapActions } from 'vuex';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import icon from '~/vue_shared/components/icon.vue';
|
||||
import modal from '~/vue_shared/components/modal.vue';
|
||||
import commitFilesList from './commit_sidebar/list.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
modal,
|
||||
icon,
|
||||
commitFilesList,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showNewBranchModal: false,
|
||||
submitCommitsLoading: false,
|
||||
startNewMR: false,
|
||||
commitMessage: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'currentProjectId',
|
||||
'currentBranchId',
|
||||
'rightPanelCollapsed',
|
||||
]),
|
||||
...mapGetters([
|
||||
'changedFiles',
|
||||
]),
|
||||
commitButtonDisabled() {
|
||||
return this.commitMessage === '' || this.submitCommitsLoading || !this.changedFiles.length;
|
||||
},
|
||||
commitMessageCount() {
|
||||
return this.commitMessage.length;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'checkCommitStatus',
|
||||
'commitChanges',
|
||||
'getTreeData',
|
||||
'setPanelCollapsedStatus',
|
||||
]),
|
||||
makeCommit(newBranch = false) {
|
||||
const createNewBranch = newBranch || this.startNewMR;
|
||||
|
||||
const payload = {
|
||||
branch: createNewBranch ?
|
||||
`${this.currentBranchId}-${new Date().getTime().toString()}` :
|
||||
this.currentBranchId,
|
||||
commit_message: this.commitMessage,
|
||||
actions: this.changedFiles.map(f => ({
|
||||
action: f.tempFile ? 'create' : 'update',
|
||||
file_path: f.path,
|
||||
content: f.content,
|
||||
encoding: f.base64 ? 'base64' : 'text',
|
||||
})),
|
||||
start_branch: createNewBranch ? this.currentBranchId : undefined,
|
||||
};
|
||||
|
||||
this.showNewBranchModal = false;
|
||||
this.submitCommitsLoading = true;
|
||||
|
||||
this.commitChanges({ payload, newMr: this.startNewMR })
|
||||
.then(() => {
|
||||
this.submitCommitsLoading = false;
|
||||
this.commitMessage = '';
|
||||
this.startNewMR = false;
|
||||
})
|
||||
.catch(() => {
|
||||
this.submitCommitsLoading = false;
|
||||
});
|
||||
},
|
||||
tryCommit() {
|
||||
this.submitCommitsLoading = true;
|
||||
|
||||
this.checkCommitStatus()
|
||||
.then((branchChanged) => {
|
||||
if (branchChanged) {
|
||||
this.showNewBranchModal = true;
|
||||
} else {
|
||||
this.makeCommit();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.submitCommitsLoading = false;
|
||||
});
|
||||
},
|
||||
toggleCollapsed() {
|
||||
this.setPanelCollapsedStatus({
|
||||
side: 'right',
|
||||
collapsed: !this.rightPanelCollapsed,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="multi-file-commit-panel-section">
|
||||
<modal
|
||||
v-if="showNewBranchModal"
|
||||
:primary-button-label="__('Create new branch')"
|
||||
kind="primary"
|
||||
:title="__('Branch has changed')"
|
||||
:text="__(`This branch has changed since
|
||||
you started editing. Would you like to create a new branch?`)"
|
||||
@cancel="showNewBranchModal = false"
|
||||
@submit="makeCommit(true)"
|
||||
/>
|
||||
<commit-files-list
|
||||
title="Staged"
|
||||
:file-list="changedFiles"
|
||||
:collapsed="rightPanelCollapsed"
|
||||
@toggleCollapsed="toggleCollapsed"
|
||||
/>
|
||||
<form
|
||||
class="form-horizontal multi-file-commit-form"
|
||||
@submit.prevent="tryCommit"
|
||||
v-if="!rightPanelCollapsed"
|
||||
>
|
||||
<div class="multi-file-commit-fieldset">
|
||||
<textarea
|
||||
class="form-control multi-file-commit-message"
|
||||
name="commit-message"
|
||||
v-model="commitMessage"
|
||||
placeholder="Commit message"
|
||||
>
|
||||
</textarea>
|
||||
</div>
|
||||
<div class="multi-file-commit-fieldset">
|
||||
<label
|
||||
v-tooltip
|
||||
title="Create a new merge request with these changes"
|
||||
data-container="body"
|
||||
data-placement="top"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="startNewMR"
|
||||
/>
|
||||
Merge Request
|
||||
</label>
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="commitButtonDisabled"
|
||||
class="btn btn-default btn-sm append-right-10 prepend-left-10"
|
||||
:class="{ disabled: submitCommitsLoading }"
|
||||
>
|
||||
<i
|
||||
v-if="submitCommitsLoading"
|
||||
class="js-commit-loading-icon fa fa-spinner fa-spin"
|
||||
aria-hidden="true"
|
||||
aria-label="loading"
|
||||
>
|
||||
</i>
|
||||
Commit
|
||||
</button>
|
||||
<div
|
||||
class="multi-file-commit-message-count"
|
||||
>
|
||||
{{ commitMessageCount }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
|
@ -1,57 +0,0 @@
|
|||
<script>
|
||||
import { mapGetters, mapActions, mapState } from 'vuex';
|
||||
import modal from '~/vue_shared/components/modal.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
modal,
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'editMode',
|
||||
'discardPopupOpen',
|
||||
]),
|
||||
...mapGetters([
|
||||
'canEditFile',
|
||||
]),
|
||||
buttonLabel() {
|
||||
return this.editMode ? this.__('Cancel edit') : this.__('Edit');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'toggleEditMode',
|
||||
'closeDiscardPopup',
|
||||
]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="editable-mode">
|
||||
<button
|
||||
v-if="canEditFile"
|
||||
class="btn btn-default"
|
||||
type="button"
|
||||
@click.prevent="toggleEditMode()">
|
||||
<i
|
||||
v-if="!editMode"
|
||||
class="fa fa-pencil"
|
||||
aria-hidden="true">
|
||||
</i>
|
||||
<span>
|
||||
{{ buttonLabel }}
|
||||
</span>
|
||||
</button>
|
||||
<modal
|
||||
v-if="discardPopupOpen"
|
||||
class="text-left"
|
||||
:primary-button-label="__('Discard changes')"
|
||||
kind="warning"
|
||||
:title="__('Are you sure?')"
|
||||
:text="__('Are you sure you want to discard your changes?')"
|
||||
@cancel="closeDiscardPopup"
|
||||
@submit="toggleEditMode(true)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
|
@ -1,136 +0,0 @@
|
|||
<script>
|
||||
/* global monaco */
|
||||
import { mapState, mapGetters, mapActions } from 'vuex';
|
||||
import flash from '~/flash';
|
||||
import monacoLoader from '../monaco_loader';
|
||||
import Editor from '../lib/editor';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'activeFile',
|
||||
'activeFileExtension',
|
||||
]),
|
||||
...mapState([
|
||||
'leftPanelCollapsed',
|
||||
'rightPanelCollapsed',
|
||||
'panelResizing',
|
||||
]),
|
||||
shouldHideEditor() {
|
||||
return this.activeFile.binary && !this.activeFile.raw;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
activeFile(oldVal, newVal) {
|
||||
if (newVal && !newVal.active) {
|
||||
this.initMonaco();
|
||||
}
|
||||
},
|
||||
leftPanelCollapsed() {
|
||||
this.editor.updateDimensions();
|
||||
},
|
||||
rightPanelCollapsed() {
|
||||
this.editor.updateDimensions();
|
||||
},
|
||||
panelResizing(isResizing) {
|
||||
if (isResizing === false) {
|
||||
this.editor.updateDimensions();
|
||||
}
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.editor.dispose();
|
||||
},
|
||||
mounted() {
|
||||
if (this.editor && monaco) {
|
||||
this.initMonaco();
|
||||
} else {
|
||||
monacoLoader(['vs/editor/editor.main'], () => {
|
||||
this.editor = Editor.create(monaco);
|
||||
|
||||
this.initMonaco();
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'getRawFileData',
|
||||
'changeFileContent',
|
||||
'setFileLanguage',
|
||||
'setEditorPosition',
|
||||
'setFileEOL',
|
||||
]),
|
||||
initMonaco() {
|
||||
if (this.shouldHideEditor) return;
|
||||
|
||||
this.editor.clearEditor();
|
||||
|
||||
this.getRawFileData(this.activeFile)
|
||||
.then(() => {
|
||||
this.editor.createInstance(this.$refs.editor);
|
||||
})
|
||||
.then(() => this.setupEditor())
|
||||
.catch((err) => {
|
||||
flash('Error setting up monaco. Please try again.', 'alert', document, null, false, true);
|
||||
throw err;
|
||||
});
|
||||
},
|
||||
setupEditor() {
|
||||
if (!this.activeFile) return;
|
||||
|
||||
const model = this.editor.createModel(this.activeFile);
|
||||
|
||||
this.editor.attachModel(model);
|
||||
|
||||
model.onChange((m) => {
|
||||
this.changeFileContent({
|
||||
file: this.activeFile,
|
||||
content: m.getValue(),
|
||||
});
|
||||
});
|
||||
|
||||
// Handle Cursor Position
|
||||
this.editor.onPositionChange((instance, e) => {
|
||||
this.setEditorPosition({
|
||||
editorRow: e.position.lineNumber,
|
||||
editorColumn: e.position.column,
|
||||
});
|
||||
});
|
||||
|
||||
this.editor.setPosition({
|
||||
lineNumber: this.activeFile.editorRow,
|
||||
column: this.activeFile.editorColumn,
|
||||
});
|
||||
|
||||
// Handle File Language
|
||||
this.setFileLanguage({
|
||||
fileLanguage: model.language,
|
||||
});
|
||||
|
||||
// Get File eol
|
||||
this.setFileEOL({
|
||||
eol: model.eol,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
id="ide"
|
||||
class="blob-viewer-container blob-editor-container"
|
||||
>
|
||||
<div
|
||||
v-if="shouldHideEditor"
|
||||
v-html="activeFile.html"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-show="!shouldHideEditor"
|
||||
ref="editor"
|
||||
class="multi-file-editor-holder"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,165 +0,0 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import timeAgoMixin from '~/vue_shared/mixins/timeago';
|
||||
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
|
||||
import fileIcon from '~/vue_shared/components/file_icon.vue';
|
||||
import newDropdown from './new_dropdown/index.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
skeletonLoadingContainer,
|
||||
newDropdown,
|
||||
fileIcon,
|
||||
},
|
||||
mixins: [
|
||||
timeAgoMixin,
|
||||
],
|
||||
props: {
|
||||
file: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
showExtraColumns: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'leftPanelCollapsed',
|
||||
]),
|
||||
isSubmodule() {
|
||||
return this.file.type === 'submodule';
|
||||
},
|
||||
isTree() {
|
||||
return this.file.type === 'tree';
|
||||
},
|
||||
levelIndentation() {
|
||||
if (this.file.level > 0) {
|
||||
return {
|
||||
marginLeft: `${this.file.level * 16}px`,
|
||||
};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
shortId() {
|
||||
return this.file.id.substr(0, 8);
|
||||
},
|
||||
submoduleColSpan() {
|
||||
return !this.leftPanelCollapsed && this.isSubmodule ? 3 : 1;
|
||||
},
|
||||
fileClass() {
|
||||
if (this.file.type === 'blob') {
|
||||
if (this.file.active) {
|
||||
return 'file-open file-active';
|
||||
}
|
||||
return this.file.opened ? 'file-open' : '';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
changedClass() {
|
||||
return {
|
||||
'fa-circle unsaved-icon': this.file.changed || this.file.tempFile,
|
||||
};
|
||||
},
|
||||
},
|
||||
updated() {
|
||||
if (this.file.type === 'blob' && this.file.active) {
|
||||
this.$el.scrollIntoView();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickFile(row) {
|
||||
// Manual Action if a tree is selected/opened
|
||||
if (this.file.type === 'tree' && this.$router.currentRoute.path === `/project${row.url}`) {
|
||||
this.$store.dispatch('toggleTreeOpen', {
|
||||
endpoint: this.file.url,
|
||||
tree: this.file,
|
||||
});
|
||||
}
|
||||
this.$router.push(`/project${row.url}`);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr
|
||||
class="file"
|
||||
:class="fileClass"
|
||||
@click="clickFile(file)">
|
||||
<td
|
||||
class="multi-file-table-name"
|
||||
:colspan="submoduleColSpan"
|
||||
>
|
||||
<a
|
||||
class="repo-file-name"
|
||||
>
|
||||
<file-icon
|
||||
:file-name="file.name"
|
||||
:loading="file.loading"
|
||||
:folder="file.type === 'tree'"
|
||||
:opened="file.opened"
|
||||
:style="levelIndentation"
|
||||
:size="16"
|
||||
/>
|
||||
{{ file.name }}
|
||||
</a>
|
||||
<new-dropdown
|
||||
v-if="isTree"
|
||||
:project-id="file.projectId"
|
||||
:branch="file.branchId"
|
||||
:path="file.path"
|
||||
:parent="file"
|
||||
/>
|
||||
<i
|
||||
class="fa"
|
||||
v-if="file.changed || file.tempFile"
|
||||
:class="changedClass"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
<template v-if="isSubmodule && file.id">
|
||||
@
|
||||
<span class="commit-sha">
|
||||
<a
|
||||
@click.stop
|
||||
:href="file.tree_url"
|
||||
>
|
||||
{{ shortId }}
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
</td>
|
||||
|
||||
<template v-if="showExtraColumns && !isSubmodule">
|
||||
<td class="multi-file-table-col-commit-message hidden-sm hidden-xs">
|
||||
<a
|
||||
v-if="file.lastCommit.message"
|
||||
@click.stop
|
||||
:href="file.lastCommit.url"
|
||||
>
|
||||
{{ file.lastCommit.message }}
|
||||
</a>
|
||||
<skeleton-loading-container
|
||||
v-else
|
||||
:small="true"
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td class="commit-update hidden-xs text-right">
|
||||
<span
|
||||
v-if="file.lastCommit.updatedAt"
|
||||
:title="tooltipTitle(file.lastCommit.updatedAt)"
|
||||
>
|
||||
{{ timeFormated(file.lastCommit.updatedAt) }}
|
||||
</span>
|
||||
<skeleton-loading-container
|
||||
v-else
|
||||
class="animation-container-right"
|
||||
:small="true"
|
||||
/>
|
||||
</td>
|
||||
</template>
|
||||
</tr>
|
||||
</template>
|
|
@ -1,60 +0,0 @@
|
|||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'activeFile',
|
||||
]),
|
||||
showButtons() {
|
||||
return this.activeFile.rawPath ||
|
||||
this.activeFile.blamePath ||
|
||||
this.activeFile.commitsPath ||
|
||||
this.activeFile.permalink;
|
||||
},
|
||||
rawDownloadButtonLabel() {
|
||||
return this.activeFile.binary ? 'Download' : 'Raw';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="showButtons"
|
||||
class="multi-file-editor-btn-group"
|
||||
>
|
||||
<a
|
||||
:href="activeFile.rawPath"
|
||||
target="_blank"
|
||||
class="btn btn-default btn-sm raw"
|
||||
rel="noopener noreferrer">
|
||||
{{ rawDownloadButtonLabel }}
|
||||
</a>
|
||||
|
||||
<div
|
||||
class="btn-group"
|
||||
role="group"
|
||||
aria-label="File actions"
|
||||
>
|
||||
<a
|
||||
:href="activeFile.blamePath"
|
||||
class="btn btn-default btn-sm blame"
|
||||
>
|
||||
Blame
|
||||
</a>
|
||||
<a
|
||||
:href="activeFile.commitsPath"
|
||||
class="btn btn-default btn-sm history"
|
||||
>
|
||||
History
|
||||
</a>
|
||||
<a
|
||||
:href="activeFile.permalink"
|
||||
class="btn btn-default btn-sm permalink"
|
||||
>
|
||||
Permalink
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,42 +0,0 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
skeletonLoadingContainer,
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'leftPanelCollapsed',
|
||||
]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr
|
||||
class="loading-file"
|
||||
aria-label="Loading files"
|
||||
>
|
||||
<td class="multi-file-table-col-name">
|
||||
<skeleton-loading-container
|
||||
:small="true"
|
||||
/>
|
||||
</td>
|
||||
<template v-if="!leftPanelCollapsed">
|
||||
<td class="hidden-sm hidden-xs">
|
||||
<skeleton-loading-container
|
||||
:small="true"
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td class="hidden-xs">
|
||||
<skeleton-loading-container
|
||||
class="animation-container-right"
|
||||
:small="true"
|
||||
/>
|
||||
</td>
|
||||
</template>
|
||||
</tr>
|
||||
</template>
|
|
@ -1,32 +0,0 @@
|
|||
<script>
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapState([
|
||||
'parentTreeUrl',
|
||||
'leftPanelCollapsed',
|
||||
]),
|
||||
colSpanCondition() {
|
||||
return this.leftPanelCollapsed ? undefined : 3;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'getTreeData',
|
||||
]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr class="file prev-directory">
|
||||
<td
|
||||
:colspan="colSpanCondition"
|
||||
class="table-cell"
|
||||
@click.prevent="getTreeData({ endpoint: parentTreeUrl })"
|
||||
>
|
||||
<a :href="parentTreeUrl">...</a>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
|
@ -1,71 +0,0 @@
|
|||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import LineHighlighter from '~/line_highlighter';
|
||||
import syntaxHighlight from '~/syntax_highlight';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'activeFile',
|
||||
]),
|
||||
renderErrorTooLarge() {
|
||||
return this.activeFile.renderError === 'too_large';
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.highlightFile();
|
||||
this.lineHighlighter = new LineHighlighter({
|
||||
fileHolderSelector: '.blob-viewer-container',
|
||||
scrollFileHolder: true,
|
||||
});
|
||||
},
|
||||
updated() {
|
||||
this.$nextTick(() => {
|
||||
this.highlightFile();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
highlightFile() {
|
||||
syntaxHighlight($(this.$el).find('.file-content'));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
v-if="!activeFile.renderError"
|
||||
v-html="activeFile.html"
|
||||
class="multi-file-preview-holder"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="activeFile.tempFile"
|
||||
class="vertical-center render-error">
|
||||
<p class="text-center">
|
||||
The source could not be displayed for this temporary file.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="renderErrorTooLarge"
|
||||
class="vertical-center render-error">
|
||||
<p class="text-center">
|
||||
The source could not be displayed because it is too large.
|
||||
You can <a
|
||||
:href="activeFile.rawPath"
|
||||
download>download</a> it instead.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="vertical-center render-error">
|
||||
<p class="text-center">
|
||||
The source could not be displayed because a rendering error occurred.
|
||||
You can <a
|
||||
:href="activeFile.rawPath"
|
||||
download>download</a> it instead.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,74 +0,0 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import fileIcon from '~/vue_shared/components/file_icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
fileIcon,
|
||||
},
|
||||
props: {
|
||||
tab: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
closeLabel() {
|
||||
if (this.tab.changed || this.tab.tempFile) {
|
||||
return `${this.tab.name} changed`;
|
||||
}
|
||||
return `Close ${this.tab.name}`;
|
||||
},
|
||||
changedClass() {
|
||||
const tabChangedObj = {
|
||||
'fa-times close-icon': !this.tab.changed && !this.tab.tempFile,
|
||||
'fa-circle unsaved-icon': this.tab.changed || this.tab.tempFile,
|
||||
};
|
||||
return tabChangedObj;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions([
|
||||
'closeFile',
|
||||
]),
|
||||
clickFile(tab) {
|
||||
this.$router.push(`/project${tab.url}`);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li @click="clickFile(tab)">
|
||||
<button
|
||||
type="button"
|
||||
class="multi-file-tab-close"
|
||||
@click.stop.prevent="closeFile({ file: tab })"
|
||||
:aria-label="closeLabel"
|
||||
:class="{
|
||||
'modified': tab.changed,
|
||||
}"
|
||||
:disabled="tab.changed"
|
||||
>
|
||||
<i
|
||||
class="fa"
|
||||
:class="changedClass"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="multi-file-tab"
|
||||
:class="{active : tab.active }"
|
||||
:title="tab.url"
|
||||
>
|
||||
<file-icon
|
||||
:file-name="tab.name"
|
||||
:size="16"
|
||||
/>
|
||||
{{ tab.name }}
|
||||
</div>
|
||||
</li>
|
||||
</template>
|
|
@ -1,27 +0,0 @@
|
|||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import RepoTab from './repo_tab.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
'repo-tab': RepoTab,
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'openFiles',
|
||||
]),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul
|
||||
class="multi-file-tabs list-unstyled append-bottom-0"
|
||||
>
|
||||
<repo-tab
|
||||
v-for="tab in openFiles"
|
||||
:key="tab.key"
|
||||
:tab="tab"
|
||||
/>
|
||||
</ul>
|
||||
</template>
|
|
@ -1,101 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import store from './stores';
|
||||
import flash from '../flash';
|
||||
import {
|
||||
getTreeEntry,
|
||||
} from './stores/utils';
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
/**
|
||||
* Routes below /-/ide/:
|
||||
|
||||
/project/h5bp/html5-boilerplate/blob/master
|
||||
/project/h5bp/html5-boilerplate/blob/master/app/js/test.js
|
||||
|
||||
/project/h5bp/html5-boilerplate/mr/123
|
||||
/project/h5bp/html5-boilerplate/mr/123/app/js/test.js
|
||||
|
||||
/workspace/123
|
||||
/workspace/project/h5bp/html5-boilerplate/blob/my-special-branch
|
||||
/workspace/project/h5bp/html5-boilerplate/mr/123
|
||||
|
||||
/ = /workspace
|
||||
|
||||
/settings
|
||||
*/
|
||||
|
||||
// Unfortunately Vue Router doesn't work without at least a fake component
|
||||
// If you do only data handling
|
||||
const EmptyRouterComponent = {
|
||||
render(createElement) {
|
||||
return createElement('div');
|
||||
},
|
||||
};
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
base: `${gon.relative_url_root}/-/ide/`,
|
||||
routes: [
|
||||
{
|
||||
path: '/project/:namespace/:project',
|
||||
component: EmptyRouterComponent,
|
||||
children: [
|
||||
{
|
||||
path: ':targetmode/:branch/*',
|
||||
component: EmptyRouterComponent,
|
||||
},
|
||||
{
|
||||
path: 'mr/:mrid',
|
||||
component: EmptyRouterComponent,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.params.namespace && to.params.project) {
|
||||
store.dispatch('getProjectData', {
|
||||
namespace: to.params.namespace,
|
||||
projectId: to.params.project,
|
||||
})
|
||||
.then(() => {
|
||||
const fullProjectId = `${to.params.namespace}/${to.params.project}`;
|
||||
|
||||
if (to.params.branch) {
|
||||
store.dispatch('getBranchData', {
|
||||
projectId: fullProjectId,
|
||||
branchId: to.params.branch,
|
||||
});
|
||||
|
||||
store.dispatch('getTreeData', {
|
||||
projectId: fullProjectId,
|
||||
branch: to.params.branch,
|
||||
endpoint: `/tree/${to.params.branch}`,
|
||||
})
|
||||
.then(() => {
|
||||
if (to.params[0]) {
|
||||
const treeEntry = getTreeEntry(store, `${to.params.namespace}/${to.params.project}/${to.params.branch}`, to.params[0]);
|
||||
if (treeEntry) {
|
||||
store.dispatch('handleTreeEntryAction', treeEntry);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
flash('Error while loading the branch files. Please try again.', 'alert', document, null, false, true);
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
flash('Error while loading the project data. Please try again.', 'alert', document, null, false, true);
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
export default router;
|
|
@ -1,31 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import ide from './components/ide.vue';
|
||||
import store from './stores';
|
||||
import router from './ide_router';
|
||||
import Translate from '../vue_shared/translate';
|
||||
|
||||
function initIde(el) {
|
||||
if (!el) return null;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
store,
|
||||
router,
|
||||
components: {
|
||||
ide,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('ide', {
|
||||
props: {
|
||||
emptyStateSvgPath: el.dataset.emptyStateSvgPath,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const ideElement = document.getElementById('ide');
|
||||
|
||||
Vue.use(Translate);
|
||||
|
||||
initIde(ideElement);
|
|
@ -1,14 +0,0 @@
|
|||
export default class Disposable {
|
||||
constructor() {
|
||||
this.disposers = new Set();
|
||||
}
|
||||
|
||||
add(...disposers) {
|
||||
disposers.forEach(disposer => this.disposers.add(disposer));
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposers.forEach(disposer => disposer.dispose());
|
||||
this.disposers.clear();
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
/* global monaco */
|
||||
import Disposable from './disposable';
|
||||
|
||||
export default class Model {
|
||||
constructor(monaco, file) {
|
||||
this.monaco = monaco;
|
||||
this.disposable = new Disposable();
|
||||
this.file = file;
|
||||
this.content = file.content !== '' ? file.content : file.raw;
|
||||
|
||||
this.disposable.add(
|
||||
this.originalModel = this.monaco.editor.createModel(
|
||||
this.file.raw,
|
||||
undefined,
|
||||
new this.monaco.Uri(null, null, `original/${this.file.path}`),
|
||||
),
|
||||
this.model = this.monaco.editor.createModel(
|
||||
this.content,
|
||||
undefined,
|
||||
new this.monaco.Uri(null, null, this.file.path),
|
||||
),
|
||||
);
|
||||
|
||||
this.events = new Map();
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this.model.uri.toString();
|
||||
}
|
||||
|
||||
get language() {
|
||||
return this.model.getModeId();
|
||||
}
|
||||
|
||||
get eol() {
|
||||
return this.model.getEOL() === '\n' ? 'LF' : 'CRLF';
|
||||
}
|
||||
|
||||
get path() {
|
||||
return this.file.path;
|
||||
}
|
||||
|
||||
getModel() {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
getOriginalModel() {
|
||||
return this.originalModel;
|
||||
}
|
||||
|
||||
onChange(cb) {
|
||||
this.events.set(
|
||||
this.path,
|
||||
this.disposable.add(
|
||||
this.model.onDidChangeContent(e => cb(this.model, e)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposable.dispose();
|
||||
this.events.clear();
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
import Disposable from './disposable';
|
||||
import Model from './model';
|
||||
|
||||
export default class ModelManager {
|
||||
constructor(monaco) {
|
||||
this.monaco = monaco;
|
||||
this.disposable = new Disposable();
|
||||
this.models = new Map();
|
||||
}
|
||||
|
||||
hasCachedModel(path) {
|
||||
return this.models.has(path);
|
||||
}
|
||||
|
||||
addModel(file) {
|
||||
if (this.hasCachedModel(file.path)) {
|
||||
return this.models.get(file.path);
|
||||
}
|
||||
|
||||
const model = new Model(this.monaco, file);
|
||||
this.models.set(model.path, model);
|
||||
this.disposable.add(model);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
// dispose of all the models
|
||||
this.disposable.dispose();
|
||||
this.models.clear();
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
export default class DecorationsController {
|
||||
constructor(editor) {
|
||||
this.editor = editor;
|
||||
this.decorations = new Map();
|
||||
this.editorDecorations = new Map();
|
||||
}
|
||||
|
||||
getAllDecorationsForModel(model) {
|
||||
if (!this.decorations.has(model.url)) return [];
|
||||
|
||||
const modelDecorations = this.decorations.get(model.url);
|
||||
const decorations = [];
|
||||
|
||||
modelDecorations.forEach(val => decorations.push(...val));
|
||||
|
||||
return decorations;
|
||||
}
|
||||
|
||||
addDecorations(model, decorationsKey, decorations) {
|
||||
const decorationMap = this.decorations.get(model.url) || new Map();
|
||||
|
||||
decorationMap.set(decorationsKey, decorations);
|
||||
|
||||
this.decorations.set(model.url, decorationMap);
|
||||
|
||||
this.decorate(model);
|
||||
}
|
||||
|
||||
decorate(model) {
|
||||
const decorations = this.getAllDecorationsForModel(model);
|
||||
const oldDecorations = this.editorDecorations.get(model.url) || [];
|
||||
|
||||
this.editorDecorations.set(
|
||||
model.url,
|
||||
this.editor.instance.deltaDecorations(oldDecorations, decorations),
|
||||
);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.decorations.clear();
|
||||
this.editorDecorations.clear();
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
/* global monaco */
|
||||
import { throttle } from 'underscore';
|
||||
import DirtyDiffWorker from './diff_worker';
|
||||
import Disposable from '../common/disposable';
|
||||
|
||||
export const getDiffChangeType = (change) => {
|
||||
if (change.modified) {
|
||||
return 'modified';
|
||||
} else if (change.added) {
|
||||
return 'added';
|
||||
} else if (change.removed) {
|
||||
return 'removed';
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
export const getDecorator = change => ({
|
||||
range: new monaco.Range(
|
||||
change.lineNumber,
|
||||
1,
|
||||
change.endLineNumber,
|
||||
1,
|
||||
),
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
linesDecorationsClassName: `dirty-diff dirty-diff-${getDiffChangeType(change)}`,
|
||||
},
|
||||
});
|
||||
|
||||
export default class DirtyDiffController {
|
||||
constructor(modelManager, decorationsController) {
|
||||
this.disposable = new Disposable();
|
||||
this.editorSimpleWorker = null;
|
||||
this.modelManager = modelManager;
|
||||
this.decorationsController = decorationsController;
|
||||
this.dirtyDiffWorker = new DirtyDiffWorker();
|
||||
this.throttledComputeDiff = throttle(this.computeDiff, 250);
|
||||
this.decorate = this.decorate.bind(this);
|
||||
|
||||
this.dirtyDiffWorker.addEventListener('message', this.decorate);
|
||||
}
|
||||
|
||||
attachModel(model) {
|
||||
model.onChange(() => this.throttledComputeDiff(model));
|
||||
}
|
||||
|
||||
computeDiff(model) {
|
||||
this.dirtyDiffWorker.postMessage({
|
||||
path: model.path,
|
||||
originalContent: model.getOriginalModel().getValue(),
|
||||
newContent: model.getModel().getValue(),
|
||||
});
|
||||
}
|
||||
|
||||
reDecorate(model) {
|
||||
this.decorationsController.decorate(model);
|
||||
}
|
||||
|
||||
decorate({ data }) {
|
||||
const decorations = data.changes.map(change => getDecorator(change));
|
||||
this.decorationsController.addDecorations(data.path, 'dirtyDiff', decorations);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposable.dispose();
|
||||
|
||||
this.dirtyDiffWorker.removeEventListener('message', this.decorate);
|
||||
this.dirtyDiffWorker.terminate();
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import { diffLines } from 'diff';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const computeDiff = (originalContent, newContent) => {
|
||||
const changes = diffLines(originalContent, newContent);
|
||||
|
||||
let lineNumber = 1;
|
||||
return changes.reduce((acc, change) => {
|
||||
const findOnLine = acc.find(c => c.lineNumber === lineNumber);
|
||||
|
||||
if (findOnLine) {
|
||||
Object.assign(findOnLine, change, {
|
||||
modified: true,
|
||||
endLineNumber: (lineNumber + change.count) - 1,
|
||||
});
|
||||
} else if ('added' in change || 'removed' in change) {
|
||||
acc.push(Object.assign({}, change, {
|
||||
lineNumber,
|
||||
modified: undefined,
|
||||
endLineNumber: (lineNumber + change.count) - 1,
|
||||
}));
|
||||
}
|
||||
|
||||
if (!change.removed) {
|
||||
lineNumber += change.count;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
import { computeDiff } from './diff';
|
||||
|
||||
self.addEventListener('message', (e) => {
|
||||
const data = e.data;
|
||||
|
||||
self.postMessage({
|
||||
path: data.path,
|
||||
changes: computeDiff(data.originalContent, data.newContent),
|
||||
});
|
||||
});
|
|
@ -1,110 +0,0 @@
|
|||
import _ from 'underscore';
|
||||
import DecorationsController from './decorations/controller';
|
||||
import DirtyDiffController from './diff/controller';
|
||||
import Disposable from './common/disposable';
|
||||
import ModelManager from './common/model_manager';
|
||||
import editorOptions from './editor_options';
|
||||
|
||||
export default class Editor {
|
||||
static create(monaco) {
|
||||
this.editorInstance = new Editor(monaco);
|
||||
|
||||
return this.editorInstance;
|
||||
}
|
||||
|
||||
constructor(monaco) {
|
||||
this.monaco = monaco;
|
||||
this.currentModel = null;
|
||||
this.instance = null;
|
||||
this.dirtyDiffController = null;
|
||||
this.disposable = new Disposable();
|
||||
|
||||
this.disposable.add(
|
||||
this.modelManager = new ModelManager(this.monaco),
|
||||
this.decorationsController = new DecorationsController(this),
|
||||
);
|
||||
|
||||
this.debouncedUpdate = _.debounce(() => {
|
||||
this.updateDimensions();
|
||||
}, 200);
|
||||
window.addEventListener('resize', this.debouncedUpdate, false);
|
||||
}
|
||||
|
||||
createInstance(domElement) {
|
||||
if (!this.instance) {
|
||||
this.disposable.add(
|
||||
this.instance = this.monaco.editor.create(domElement, {
|
||||
model: null,
|
||||
readOnly: false,
|
||||
contextmenu: true,
|
||||
scrollBeyondLastLine: false,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
}),
|
||||
this.dirtyDiffController = new DirtyDiffController(
|
||||
this.modelManager, this.decorationsController,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
createModel(file) {
|
||||
return this.modelManager.addModel(file);
|
||||
}
|
||||
|
||||
attachModel(model) {
|
||||
this.instance.setModel(model.getModel());
|
||||
if (this.dirtyDiffController) this.dirtyDiffController.attachModel(model);
|
||||
|
||||
this.currentModel = model;
|
||||
|
||||
this.instance.updateOptions(editorOptions.reduce((acc, obj) => {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
Object.assign(acc, {
|
||||
[key]: obj[key](model),
|
||||
});
|
||||
});
|
||||
return acc;
|
||||
}, {}));
|
||||
|
||||
if (this.dirtyDiffController) this.dirtyDiffController.reDecorate(model);
|
||||
}
|
||||
|
||||
clearEditor() {
|
||||
if (this.instance) {
|
||||
this.instance.setModel(null);
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposable.dispose();
|
||||
window.removeEventListener('resize', this.debouncedUpdate);
|
||||
|
||||
// dispose main monaco instance
|
||||
if (this.instance) {
|
||||
this.instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
updateDimensions() {
|
||||
this.instance.layout();
|
||||
}
|
||||
|
||||
setPosition({ lineNumber, column }) {
|
||||
this.instance.revealPositionInCenter({
|
||||
lineNumber,
|
||||
column,
|
||||
});
|
||||
this.instance.setPosition({
|
||||
lineNumber,
|
||||
column,
|
||||
});
|
||||
}
|
||||
|
||||
onPositionChange(cb) {
|
||||
this.disposable.add(
|
||||
this.instance.onDidChangeCursorPosition(e => cb(this.instance, e)),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
export default [{
|
||||
}];
|
|
@ -1,16 +0,0 @@
|
|||
import monacoContext from 'monaco-editor/dev/vs/loader';
|
||||
|
||||
monacoContext.require.config({
|
||||
paths: {
|
||||
vs: `${__webpack_public_path__}monaco-editor/vs`, // eslint-disable-line camelcase
|
||||
},
|
||||
});
|
||||
|
||||
// ignore CDN config and use local assets path for service worker which cannot be cross-domain
|
||||
const relativeRootPath = (gon && gon.relative_url_root) || '';
|
||||
const monacoPath = `${relativeRootPath}/assets/webpack/monaco-editor/vs`;
|
||||
window.MonacoEnvironment = { getWorkerUrl: () => `${monacoPath}/base/worker/workerMain.js` };
|
||||
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
window.__monaco_context__ = monacoContext;
|
||||
export default monacoContext.require;
|
|
@ -1,47 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import VueResource from 'vue-resource';
|
||||
import Api from '../../api';
|
||||
|
||||
Vue.use(VueResource);
|
||||
|
||||
export default {
|
||||
getTreeData(endpoint) {
|
||||
return Vue.http.get(endpoint, { params: { format: 'json' } });
|
||||
},
|
||||
getFileData(endpoint) {
|
||||
return Vue.http.get(endpoint, { params: { format: 'json' } });
|
||||
},
|
||||
getRawFileData(file) {
|
||||
if (file.tempFile) {
|
||||
return Promise.resolve(file.content);
|
||||
}
|
||||
|
||||
if (file.raw) {
|
||||
return Promise.resolve(file.raw);
|
||||
}
|
||||
|
||||
return Vue.http.get(file.rawPath, { params: { format: 'json' } })
|
||||
.then(res => res.text());
|
||||
},
|
||||
getProjectData(namespace, project) {
|
||||
return Api.project(`${namespace}/${project}`);
|
||||
},
|
||||
getBranchData(projectId, currentBranchId) {
|
||||
return Api.branchSingle(projectId, currentBranchId);
|
||||
},
|
||||
createBranch(projectId, payload) {
|
||||
const url = Api.buildUrl(Api.createBranchPath).replace(':id', projectId);
|
||||
|
||||
return Vue.http.post(url, payload);
|
||||
},
|
||||
commit(projectId, payload) {
|
||||
return Api.commitMultiple(projectId, payload);
|
||||
},
|
||||
getTreeLastCommit(endpoint) {
|
||||
return Vue.http.get(endpoint, {
|
||||
params: {
|
||||
format: 'json',
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
|
@ -1,196 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import { visitUrl } from '~/lib/utils/url_utility';
|
||||
import flash from '~/flash';
|
||||
import service from '../services';
|
||||
import * as types from './mutation_types';
|
||||
import { stripHtml } from '../../lib/utils/text_utility';
|
||||
|
||||
export const redirectToUrl = (_, url) => visitUrl(url);
|
||||
|
||||
export const setInitialData = ({ commit }, data) =>
|
||||
commit(types.SET_INITIAL_DATA, data);
|
||||
|
||||
export const closeDiscardPopup = ({ commit }) =>
|
||||
commit(types.TOGGLE_DISCARD_POPUP, false);
|
||||
|
||||
export const discardAllChanges = ({ commit, getters, dispatch }) => {
|
||||
const changedFiles = getters.changedFiles;
|
||||
|
||||
changedFiles.forEach((file) => {
|
||||
commit(types.DISCARD_FILE_CHANGES, file);
|
||||
|
||||
if (file.tempFile) {
|
||||
dispatch('closeFile', { file, force: true });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const closeAllFiles = ({ state, dispatch }) => {
|
||||
state.openFiles.forEach(file => dispatch('closeFile', { file }));
|
||||
};
|
||||
|
||||
export const toggleEditMode = (
|
||||
{ state, commit, getters, dispatch },
|
||||
force = false,
|
||||
) => {
|
||||
const changedFiles = getters.changedFiles;
|
||||
|
||||
if (changedFiles.length && !force) {
|
||||
commit(types.TOGGLE_DISCARD_POPUP, true);
|
||||
} else {
|
||||
commit(types.TOGGLE_EDIT_MODE);
|
||||
commit(types.TOGGLE_DISCARD_POPUP, false);
|
||||
dispatch('toggleBlobView');
|
||||
|
||||
if (!state.editMode) {
|
||||
dispatch('discardAllChanges');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleBlobView = ({ commit, state }) => {
|
||||
if (state.editMode) {
|
||||
commit(types.SET_EDIT_MODE);
|
||||
} else {
|
||||
commit(types.SET_PREVIEW_MODE);
|
||||
}
|
||||
};
|
||||
|
||||
export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
|
||||
if (side === 'left') {
|
||||
commit(types.SET_LEFT_PANEL_COLLAPSED, collapsed);
|
||||
} else {
|
||||
commit(types.SET_RIGHT_PANEL_COLLAPSED, collapsed);
|
||||
}
|
||||
};
|
||||
|
||||
export const setResizingStatus = ({ commit }, resizing) => {
|
||||
commit(types.SET_RESIZING_STATUS, resizing);
|
||||
};
|
||||
|
||||
export const checkCommitStatus = ({ state }) =>
|
||||
service
|
||||
.getBranchData(state.currentProjectId, state.currentBranchId)
|
||||
.then(({ data }) => {
|
||||
const { id } = data.commit;
|
||||
const selectedBranch =
|
||||
state.projects[state.currentProjectId].branches[state.currentBranchId];
|
||||
|
||||
if (selectedBranch.workingReference !== id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
.catch(() => flash('Error checking branch data. Please try again.', 'alert', document, null, false, true));
|
||||
|
||||
export const commitChanges = (
|
||||
{ commit, state, dispatch, getters },
|
||||
{ payload, newMr },
|
||||
) =>
|
||||
service
|
||||
.commit(state.currentProjectId, payload)
|
||||
.then(({ data }) => {
|
||||
const { branch } = payload;
|
||||
if (!data.short_id) {
|
||||
flash(data.message, 'alert', document, null, false, true);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedProject = state.projects[state.currentProjectId];
|
||||
const lastCommit = {
|
||||
commit_path: `${selectedProject.web_url}/commit/${data.id}`,
|
||||
commit: {
|
||||
message: data.message,
|
||||
authored_date: data.committed_date,
|
||||
},
|
||||
};
|
||||
|
||||
let commitMsg = `Your changes have been committed. Commit ${data.short_id}`;
|
||||
if (data.stats) {
|
||||
commitMsg += ` with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`;
|
||||
}
|
||||
|
||||
flash(
|
||||
commitMsg,
|
||||
'notice',
|
||||
document,
|
||||
null,
|
||||
false,
|
||||
true);
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
|
||||
if (newMr) {
|
||||
dispatch('discardAllChanges');
|
||||
dispatch(
|
||||
'redirectToUrl',
|
||||
`${selectedProject.web_url}/merge_requests/new?merge_request%5Bsource_branch%5D=${branch}`,
|
||||
);
|
||||
} else {
|
||||
commit(types.SET_BRANCH_WORKING_REFERENCE, {
|
||||
projectId: state.currentProjectId,
|
||||
branchId: state.currentBranchId,
|
||||
reference: data.id,
|
||||
});
|
||||
|
||||
getters.changedFiles.forEach((entry) => {
|
||||
commit(types.SET_LAST_COMMIT_DATA, {
|
||||
entry,
|
||||
lastCommit,
|
||||
});
|
||||
});
|
||||
|
||||
dispatch('discardAllChanges');
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
let errMsg = 'Error committing changes. Please try again.';
|
||||
if (err.response.data && err.response.data.message) {
|
||||
errMsg += ` (${stripHtml(err.response.data.message)})`;
|
||||
}
|
||||
flash(errMsg, 'alert', document, null, false, true);
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
});
|
||||
|
||||
export const createTempEntry = (
|
||||
{ state, dispatch },
|
||||
{ projectId, branchId, parent, name, type, content = '', base64 = false },
|
||||
) => {
|
||||
const selectedParent = parent || state.trees[`${projectId}/${branchId}`];
|
||||
if (type === 'tree') {
|
||||
dispatch('createTempTree', {
|
||||
projectId,
|
||||
branchId,
|
||||
parent: selectedParent,
|
||||
name,
|
||||
});
|
||||
} else if (type === 'blob') {
|
||||
dispatch('createTempFile', {
|
||||
projectId,
|
||||
branchId,
|
||||
parent: selectedParent,
|
||||
name,
|
||||
base64,
|
||||
content,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const scrollToTab = () => {
|
||||
Vue.nextTick(() => {
|
||||
const tabs = document.getElementById('tabs');
|
||||
|
||||
if (tabs) {
|
||||
const tabEl = tabs.querySelector('.active .repo-tab');
|
||||
|
||||
tabEl.focus();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export * from './actions/tree';
|
||||
export * from './actions/file';
|
||||
export * from './actions/project';
|
||||
export * from './actions/branch';
|
|
@ -1,43 +0,0 @@
|
|||
import service from '../../services';
|
||||
import flash from '../../../flash';
|
||||
import * as types from '../mutation_types';
|
||||
|
||||
export const getBranchData = (
|
||||
{ commit, state, dispatch },
|
||||
{ projectId, branchId, force = false } = {},
|
||||
) => new Promise((resolve, reject) => {
|
||||
if ((typeof state.projects[`${projectId}`] === 'undefined' ||
|
||||
!state.projects[`${projectId}`].branches[branchId])
|
||||
|| force) {
|
||||
service.getBranchData(`${projectId}`, branchId)
|
||||
.then(({ data }) => {
|
||||
const { id } = data.commit;
|
||||
commit(types.SET_BRANCH, { projectPath: `${projectId}`, branchName: branchId, branch: data });
|
||||
commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
|
||||
resolve(data);
|
||||
})
|
||||
.catch(() => {
|
||||
flash('Error loading branch data. Please try again.', 'alert', document, null, false, true);
|
||||
reject(new Error(`Branch not loaded - ${projectId}/${branchId}`));
|
||||
});
|
||||
} else {
|
||||
resolve(state.projects[`${projectId}`].branches[branchId]);
|
||||
}
|
||||
});
|
||||
|
||||
export const createNewBranch = ({ state, commit }, branch) => service.createBranch(
|
||||
state.currentProjectId,
|
||||
{
|
||||
branch,
|
||||
ref: state.currentBranchId,
|
||||
},
|
||||
)
|
||||
.then(res => res.json())
|
||||
.then((data) => {
|
||||
const branchName = data.name;
|
||||
const url = location.href.replace(state.currentBranchId, branchName);
|
||||
|
||||
if (this.$router) this.$router.push(url);
|
||||
|
||||
commit(types.SET_CURRENT_BRANCH, branchName);
|
||||
});
|
|
@ -1,137 +0,0 @@
|
|||
import { normalizeHeaders } from '../../../lib/utils/common_utils';
|
||||
import flash from '../../../flash';
|
||||
import service from '../../services';
|
||||
import * as types from '../mutation_types';
|
||||
import router from '../../ide_router';
|
||||
import {
|
||||
findEntry,
|
||||
setPageTitle,
|
||||
createTemp,
|
||||
findIndexOfFile,
|
||||
} from '../utils';
|
||||
|
||||
export const closeFile = ({ commit, state, dispatch }, { file, force = false }) => {
|
||||
if ((file.changed || file.tempFile) && !force) return;
|
||||
|
||||
const indexOfClosedFile = findIndexOfFile(state.openFiles, file);
|
||||
const fileWasActive = file.active;
|
||||
|
||||
commit(types.TOGGLE_FILE_OPEN, file);
|
||||
commit(types.SET_FILE_ACTIVE, { file, active: false });
|
||||
|
||||
if (state.openFiles.length > 0 && fileWasActive) {
|
||||
const nextIndexToOpen = indexOfClosedFile === 0 ? 0 : indexOfClosedFile - 1;
|
||||
const nextFileToOpen = state.openFiles[nextIndexToOpen];
|
||||
|
||||
dispatch('setFileActive', nextFileToOpen);
|
||||
} else if (!state.openFiles.length) {
|
||||
router.push(`/project/${file.projectId}/tree/${file.branchId}/`);
|
||||
}
|
||||
|
||||
dispatch('getLastCommitData');
|
||||
};
|
||||
|
||||
export const setFileActive = ({ commit, state, getters, dispatch }, file) => {
|
||||
const currentActiveFile = getters.activeFile;
|
||||
|
||||
if (file.active) return;
|
||||
|
||||
if (currentActiveFile) {
|
||||
commit(types.SET_FILE_ACTIVE, { file: currentActiveFile, active: false });
|
||||
}
|
||||
|
||||
commit(types.SET_FILE_ACTIVE, { file, active: true });
|
||||
dispatch('scrollToTab');
|
||||
|
||||
// reset hash for line highlighting
|
||||
location.hash = '';
|
||||
|
||||
commit(types.SET_CURRENT_PROJECT, file.projectId);
|
||||
commit(types.SET_CURRENT_BRANCH, file.branchId);
|
||||
};
|
||||
|
||||
export const getFileData = ({ state, commit, dispatch }, file) => {
|
||||
commit(types.TOGGLE_LOADING, file);
|
||||
|
||||
service.getFileData(file.url)
|
||||
.then((res) => {
|
||||
const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']);
|
||||
|
||||
setPageTitle(pageTitle);
|
||||
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => {
|
||||
commit(types.SET_FILE_DATA, { data, file });
|
||||
commit(types.TOGGLE_FILE_OPEN, file);
|
||||
dispatch('setFileActive', file);
|
||||
commit(types.TOGGLE_LOADING, file);
|
||||
})
|
||||
.catch(() => {
|
||||
commit(types.TOGGLE_LOADING, file);
|
||||
flash('Error loading file data. Please try again.', 'alert', document, null, false, true);
|
||||
});
|
||||
};
|
||||
|
||||
export const getRawFileData = ({ commit, dispatch }, file) => service.getRawFileData(file)
|
||||
.then((raw) => {
|
||||
commit(types.SET_FILE_RAW_DATA, { file, raw });
|
||||
})
|
||||
.catch(() => flash('Error loading file content. Please try again.', 'alert', document, null, false, true));
|
||||
|
||||
export const changeFileContent = ({ commit }, { file, content }) => {
|
||||
commit(types.UPDATE_FILE_CONTENT, { file, content });
|
||||
};
|
||||
|
||||
export const setFileLanguage = ({ state, commit }, { fileLanguage }) => {
|
||||
if (state.selectedFile) {
|
||||
commit(types.SET_FILE_LANGUAGE, { file: state.selectedFile, fileLanguage });
|
||||
}
|
||||
};
|
||||
|
||||
export const setFileEOL = ({ state, commit }, { eol }) => {
|
||||
if (state.selectedFile) {
|
||||
commit(types.SET_FILE_EOL, { file: state.selectedFile, eol });
|
||||
}
|
||||
};
|
||||
|
||||
export const setEditorPosition = ({ state, commit }, { editorRow, editorColumn }) => {
|
||||
if (state.selectedFile) {
|
||||
commit(types.SET_FILE_POSITION, { file: state.selectedFile, editorRow, editorColumn });
|
||||
}
|
||||
};
|
||||
|
||||
export const createTempFile = ({ state, commit, dispatch }, { projectId, branchId, parent, name, content = '', base64 = '' }) => {
|
||||
const path = parent.path !== undefined ? parent.path : '';
|
||||
// We need to do the replacement otherwise the web_url + file.url duplicate
|
||||
const newUrl = `/${projectId}/blob/${branchId}/${path}${path ? '/' : ''}${name}`;
|
||||
const file = createTemp({
|
||||
projectId,
|
||||
branchId,
|
||||
name: name.replace(`${path}/`, ''),
|
||||
path,
|
||||
type: 'blob',
|
||||
level: parent.level !== undefined ? parent.level + 1 : 0,
|
||||
changed: true,
|
||||
content,
|
||||
base64,
|
||||
url: newUrl,
|
||||
});
|
||||
|
||||
if (findEntry(parent.tree, 'blob', file.name)) return flash(`The name "${file.name}" is already taken in this directory.`, 'alert', document, null, false, true);
|
||||
|
||||
commit(types.CREATE_TMP_FILE, {
|
||||
parent,
|
||||
file,
|
||||
});
|
||||
commit(types.TOGGLE_FILE_OPEN, file);
|
||||
dispatch('setFileActive', file);
|
||||
|
||||
if (!state.editMode && !file.base64) {
|
||||
dispatch('toggleEditMode', true);
|
||||
}
|
||||
|
||||
router.push(`/project${file.url}`);
|
||||
|
||||
return Promise.resolve(file);
|
||||
};
|
|
@ -1,27 +0,0 @@
|
|||
import service from '../../services';
|
||||
import flash from '../../../flash';
|
||||
import * as types from '../mutation_types';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const getProjectData = (
|
||||
{ commit, state, dispatch },
|
||||
{ namespace, projectId, force = false } = {},
|
||||
) => new Promise((resolve, reject) => {
|
||||
if (!state.projects[`${namespace}/${projectId}`] || force) {
|
||||
commit(types.TOGGLE_LOADING, state);
|
||||
service.getProjectData(namespace, projectId)
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
commit(types.TOGGLE_LOADING, state);
|
||||
commit(types.SET_PROJECT, { projectPath: `${namespace}/${projectId}`, project: data });
|
||||
if (!state.currentProjectId) commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`);
|
||||
resolve(data);
|
||||
})
|
||||
.catch(() => {
|
||||
flash('Error loading project data. Please try again.', 'alert', document, null, false, true);
|
||||
reject(new Error(`Project not loaded ${namespace}/${projectId}`));
|
||||
});
|
||||
} else {
|
||||
resolve(state.projects[`${namespace}/${projectId}`]);
|
||||
}
|
||||
});
|
|
@ -1,188 +0,0 @@
|
|||
import { visitUrl } from '../../../lib/utils/url_utility';
|
||||
import { normalizeHeaders } from '../../../lib/utils/common_utils';
|
||||
import flash from '../../../flash';
|
||||
import service from '../../services';
|
||||
import * as types from '../mutation_types';
|
||||
import router from '../../ide_router';
|
||||
import {
|
||||
setPageTitle,
|
||||
findEntry,
|
||||
createTemp,
|
||||
createOrMergeEntry,
|
||||
} from '../utils';
|
||||
|
||||
export const getTreeData = (
|
||||
{ commit, state, dispatch },
|
||||
{ endpoint, tree = null, projectId, branch, force = false } = {},
|
||||
) => new Promise((resolve, reject) => {
|
||||
// We already have the base tree so we resolve immediately
|
||||
if (!tree && state.trees[`${projectId}/${branch}`] && !force) {
|
||||
resolve();
|
||||
} else {
|
||||
if (tree) commit(types.TOGGLE_LOADING, tree);
|
||||
const selectedProject = state.projects[projectId];
|
||||
// We are merging the web_url that we got on the project info with the endpoint
|
||||
// we got on the tree entry, as both contain the projectId, we replace it in the tree endpoint
|
||||
const completeEndpoint = selectedProject.web_url + (endpoint).replace(projectId, '');
|
||||
if (completeEndpoint && (!tree || !tree.tempFile)) {
|
||||
service.getTreeData(completeEndpoint)
|
||||
.then((res) => {
|
||||
const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']);
|
||||
|
||||
setPageTitle(pageTitle);
|
||||
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => {
|
||||
if (!state.isInitialRoot) {
|
||||
commit(types.SET_ROOT, data.path === '/');
|
||||
}
|
||||
|
||||
dispatch('updateDirectoryData', { data, tree, projectId, branch });
|
||||
const selectedTree = tree || state.trees[`${projectId}/${branch}`];
|
||||
|
||||
commit(types.SET_PARENT_TREE_URL, data.parent_tree_url);
|
||||
commit(types.SET_LAST_COMMIT_URL, { tree: selectedTree, url: data.last_commit_path });
|
||||
if (tree) commit(types.TOGGLE_LOADING, selectedTree);
|
||||
|
||||
const prevLastCommitPath = selectedTree.lastCommitPath;
|
||||
if (prevLastCommitPath !== null) {
|
||||
dispatch('getLastCommitData', selectedTree);
|
||||
}
|
||||
resolve(data);
|
||||
})
|
||||
.catch((e) => {
|
||||
flash('Error loading tree data. Please try again.', 'alert', document, null, false, true);
|
||||
if (tree) commit(types.TOGGLE_LOADING, tree);
|
||||
reject(e);
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const toggleTreeOpen = ({ commit, dispatch }, { endpoint, tree }) => {
|
||||
if (tree.opened) {
|
||||
// send empty data to clear the tree
|
||||
const data = { trees: [], blobs: [], submodules: [] };
|
||||
|
||||
dispatch('updateDirectoryData', { data, tree, projectId: tree.projectId, branchId: tree.branchId });
|
||||
} else {
|
||||
dispatch('getTreeData', { endpoint, tree, projectId: tree.projectId, branch: tree.branchId });
|
||||
}
|
||||
|
||||
commit(types.TOGGLE_TREE_OPEN, tree);
|
||||
};
|
||||
|
||||
export const handleTreeEntryAction = ({ commit, dispatch }, row) => {
|
||||
if (row.type === 'tree') {
|
||||
dispatch('toggleTreeOpen', {
|
||||
endpoint: row.url,
|
||||
tree: row,
|
||||
});
|
||||
} else if (row.type === 'submodule') {
|
||||
commit(types.TOGGLE_LOADING, row);
|
||||
visitUrl(row.url);
|
||||
} else if (row.type === 'blob' && row.opened) {
|
||||
dispatch('setFileActive', row);
|
||||
} else {
|
||||
dispatch('getFileData', row);
|
||||
}
|
||||
};
|
||||
|
||||
export const createTempTree = (
|
||||
{ state, commit, dispatch },
|
||||
{ projectId, branchId, parent, name },
|
||||
) => {
|
||||
let selectedTree = parent;
|
||||
const dirNames = name.replace(new RegExp(`^${state.path}/`), '').split('/');
|
||||
|
||||
dirNames.forEach((dirName) => {
|
||||
const foundEntry = findEntry(selectedTree.tree, 'tree', dirName);
|
||||
|
||||
if (!foundEntry) {
|
||||
const path = selectedTree.path !== undefined ? selectedTree.path : '';
|
||||
const tmpEntry = createTemp({
|
||||
projectId,
|
||||
branchId,
|
||||
name: dirName,
|
||||
path,
|
||||
type: 'tree',
|
||||
level: selectedTree.level !== undefined ? selectedTree.level + 1 : 0,
|
||||
tree: [],
|
||||
url: `/${projectId}/blob/${branchId}/${path}${path ? '/' : ''}${dirName}`,
|
||||
});
|
||||
|
||||
commit(types.CREATE_TMP_TREE, {
|
||||
parent: selectedTree,
|
||||
tmpEntry,
|
||||
});
|
||||
commit(types.TOGGLE_TREE_OPEN, tmpEntry);
|
||||
|
||||
router.push(`/project${tmpEntry.url}`);
|
||||
|
||||
selectedTree = tmpEntry;
|
||||
} else {
|
||||
selectedTree = foundEntry;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => {
|
||||
if (!tree || tree.lastCommitPath === null || !tree.lastCommitPath) return;
|
||||
|
||||
service.getTreeLastCommit(tree.lastCommitPath)
|
||||
.then((res) => {
|
||||
const lastCommitPath = normalizeHeaders(res.headers)['MORE-LOGS-URL'] || null;
|
||||
|
||||
commit(types.SET_LAST_COMMIT_URL, { tree, url: lastCommitPath });
|
||||
|
||||
return res.json();
|
||||
})
|
||||
.then((data) => {
|
||||
data.forEach((lastCommit) => {
|
||||
const entry = findEntry(tree.tree, lastCommit.type, lastCommit.file_name);
|
||||
|
||||
if (entry) {
|
||||
commit(types.SET_LAST_COMMIT_DATA, { entry, lastCommit });
|
||||
}
|
||||
});
|
||||
|
||||
dispatch('getLastCommitData', tree);
|
||||
})
|
||||
.catch(() => flash('Error fetching log data.', 'alert', document, null, false, true));
|
||||
};
|
||||
|
||||
export const updateDirectoryData = (
|
||||
{ commit, state },
|
||||
{ data, tree, projectId, branch },
|
||||
) => {
|
||||
if (!tree) {
|
||||
const existingTree = state.trees[`${projectId}/${branch}`];
|
||||
if (!existingTree) {
|
||||
commit(types.CREATE_TREE, { treePath: `${projectId}/${branch}` });
|
||||
}
|
||||
}
|
||||
|
||||
const selectedTree = tree || state.trees[`${projectId}/${branch}`];
|
||||
const level = selectedTree.level !== undefined ? selectedTree.level + 1 : 0;
|
||||
const parentTreeUrl = data.parent_tree_url ? `${data.parent_tree_url}${data.path}` : state.endpoints.rootUrl;
|
||||
const createEntry = (entry, type) => createOrMergeEntry({
|
||||
tree: selectedTree,
|
||||
projectId: `${projectId}`,
|
||||
branchId: branch,
|
||||
entry,
|
||||
level,
|
||||
type,
|
||||
parentTreeUrl,
|
||||
});
|
||||
|
||||
const formattedData = [
|
||||
...data.trees.map(t => createEntry(t, 'tree')),
|
||||
...data.submodules.map(m => createEntry(m, 'submodule')),
|
||||
...data.blobs.map(b => createEntry(b, 'blob')),
|
||||
];
|
||||
|
||||
commit(types.SET_DIRECTORY_DATA, { tree: selectedTree, data: formattedData });
|
||||
};
|
|
@ -1,19 +0,0 @@
|
|||
export const changedFiles = state => state.openFiles.filter(file => file.changed);
|
||||
|
||||
export const activeFile = state => state.openFiles.find(file => file.active) || null;
|
||||
|
||||
export const activeFileExtension = (state) => {
|
||||
const file = activeFile(state);
|
||||
return file ? `.${file.path.split('.').pop()}` : '';
|
||||
};
|
||||
|
||||
export const canEditFile = (state) => {
|
||||
const currentActiveFile = activeFile(state);
|
||||
|
||||
return state.canCommit &&
|
||||
(currentActiveFile && !currentActiveFile.renderError && !currentActiveFile.binary);
|
||||
};
|
||||
|
||||
export const addedFiles = state => changedFiles(state).filter(f => f.tempFile);
|
||||
|
||||
export const modifiedFiles = state => changedFiles(state).filter(f => !f.tempFile);
|
|
@ -1,15 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import state from './state';
|
||||
import * as actions from './actions';
|
||||
import * as getters from './getters';
|
||||
import mutations from './mutations';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: state(),
|
||||
actions,
|
||||
mutations,
|
||||
getters,
|
||||
});
|
|
@ -1,46 +0,0 @@
|
|||
export const SET_INITIAL_DATA = 'SET_INITIAL_DATA';
|
||||
export const TOGGLE_LOADING = 'TOGGLE_LOADING';
|
||||
export const SET_PARENT_TREE_URL = 'SET_PARENT_TREE_URL';
|
||||
export const SET_ROOT = 'SET_ROOT';
|
||||
export const SET_LAST_COMMIT_DATA = 'SET_LAST_COMMIT_DATA';
|
||||
export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED';
|
||||
export const SET_RIGHT_PANEL_COLLAPSED = 'SET_RIGHT_PANEL_COLLAPSED';
|
||||
export const SET_RESIZING_STATUS = 'SET_RESIZING_STATUS';
|
||||
|
||||
// Project Mutation Types
|
||||
export const SET_PROJECT = 'SET_PROJECT';
|
||||
export const SET_CURRENT_PROJECT = 'SET_CURRENT_PROJECT';
|
||||
export const TOGGLE_PROJECT_OPEN = 'TOGGLE_PROJECT_OPEN';
|
||||
|
||||
// Branch Mutation Types
|
||||
export const SET_BRANCH = 'SET_BRANCH';
|
||||
export const SET_BRANCH_WORKING_REFERENCE = 'SET_BRANCH_WORKING_REFERENCE';
|
||||
export const TOGGLE_BRANCH_OPEN = 'TOGGLE_BRANCH_OPEN';
|
||||
|
||||
// Tree mutation types
|
||||
export const SET_DIRECTORY_DATA = 'SET_DIRECTORY_DATA';
|
||||
export const TOGGLE_TREE_OPEN = 'TOGGLE_TREE_OPEN';
|
||||
export const CREATE_TMP_TREE = 'CREATE_TMP_TREE';
|
||||
export const SET_LAST_COMMIT_URL = 'SET_LAST_COMMIT_URL';
|
||||
export const CREATE_TREE = 'CREATE_TREE';
|
||||
|
||||
// File mutation types
|
||||
export const SET_FILE_DATA = 'SET_FILE_DATA';
|
||||
export const TOGGLE_FILE_OPEN = 'TOGGLE_FILE_OPEN';
|
||||
export const SET_FILE_ACTIVE = 'SET_FILE_ACTIVE';
|
||||
export const SET_FILE_RAW_DATA = 'SET_FILE_RAW_DATA';
|
||||
export const UPDATE_FILE_CONTENT = 'UPDATE_FILE_CONTENT';
|
||||
export const SET_FILE_LANGUAGE = 'SET_FILE_LANGUAGE';
|
||||
export const SET_FILE_POSITION = 'SET_FILE_POSITION';
|
||||
export const SET_FILE_EOL = 'SET_FILE_EOL';
|
||||
export const DISCARD_FILE_CHANGES = 'DISCARD_FILE_CHANGES';
|
||||
export const CREATE_TMP_FILE = 'CREATE_TMP_FILE';
|
||||
|
||||
// Viewer mutation types
|
||||
export const SET_PREVIEW_MODE = 'SET_PREVIEW_MODE';
|
||||
export const SET_EDIT_MODE = 'SET_EDIT_MODE';
|
||||
export const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';
|
||||
export const TOGGLE_DISCARD_POPUP = 'TOGGLE_DISCARD_POPUP';
|
||||
|
||||
export const SET_CURRENT_BRANCH = 'SET_CURRENT_BRANCH';
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
import * as types from './mutation_types';
|
||||
import projectMutations from './mutations/project';
|
||||
import fileMutations from './mutations/file';
|
||||
import treeMutations from './mutations/tree';
|
||||
import branchMutations from './mutations/branch';
|
||||
|
||||
export default {
|
||||
[types.SET_INITIAL_DATA](state, data) {
|
||||
Object.assign(state, data);
|
||||
},
|
||||
[types.SET_PREVIEW_MODE](state) {
|
||||
Object.assign(state, {
|
||||
currentBlobView: 'repo-preview',
|
||||
});
|
||||
},
|
||||
[types.SET_EDIT_MODE](state) {
|
||||
Object.assign(state, {
|
||||
currentBlobView: 'repo-editor',
|
||||
});
|
||||
},
|
||||
[types.TOGGLE_LOADING](state, entry) {
|
||||
Object.assign(entry, {
|
||||
loading: !entry.loading,
|
||||
});
|
||||
},
|
||||
[types.TOGGLE_EDIT_MODE](state) {
|
||||
Object.assign(state, {
|
||||
editMode: !state.editMode,
|
||||
});
|
||||
},
|
||||
[types.TOGGLE_DISCARD_POPUP](state, discardPopupOpen) {
|
||||
Object.assign(state, {
|
||||
discardPopupOpen,
|
||||
});
|
||||
},
|
||||
[types.SET_ROOT](state, isRoot) {
|
||||
Object.assign(state, {
|
||||
isRoot,
|
||||
isInitialRoot: isRoot,
|
||||
});
|
||||
},
|
||||
[types.SET_LEFT_PANEL_COLLAPSED](state, collapsed) {
|
||||
Object.assign(state, {
|
||||
leftPanelCollapsed: collapsed,
|
||||
});
|
||||
},
|
||||
[types.SET_RIGHT_PANEL_COLLAPSED](state, collapsed) {
|
||||
Object.assign(state, {
|
||||
rightPanelCollapsed: collapsed,
|
||||
});
|
||||
},
|
||||
[types.SET_RESIZING_STATUS](state, resizing) {
|
||||
Object.assign(state, {
|
||||
panelResizing: resizing,
|
||||
});
|
||||
},
|
||||
[types.SET_LAST_COMMIT_DATA](state, { entry, lastCommit }) {
|
||||
Object.assign(entry.lastCommit, {
|
||||
id: lastCommit.commit.id,
|
||||
url: lastCommit.commit_path,
|
||||
message: lastCommit.commit.message,
|
||||
author: lastCommit.commit.author_name,
|
||||
updatedAt: lastCommit.commit.authored_date,
|
||||
});
|
||||
},
|
||||
...projectMutations,
|
||||
...fileMutations,
|
||||
...treeMutations,
|
||||
...branchMutations,
|
||||
};
|
|
@ -1,28 +0,0 @@
|
|||
import * as types from '../mutation_types';
|
||||
|
||||
export default {
|
||||
[types.SET_CURRENT_BRANCH](state, currentBranchId) {
|
||||
Object.assign(state, {
|
||||
currentBranchId,
|
||||
});
|
||||
},
|
||||
[types.SET_BRANCH](state, { projectPath, branchName, branch }) {
|
||||
// Add client side properties
|
||||
Object.assign(branch, {
|
||||
treeId: `${projectPath}/${branchName}`,
|
||||
active: true,
|
||||
workingReference: '',
|
||||
});
|
||||
|
||||
Object.assign(state.projects[projectPath], {
|
||||
branches: {
|
||||
[branchName]: branch,
|
||||
},
|
||||
});
|
||||
},
|
||||
[types.SET_BRANCH_WORKING_REFERENCE](state, { projectId, branchId, reference }) {
|
||||
Object.assign(state.projects[projectId].branches[branchId], {
|
||||
workingReference: reference,
|
||||
});
|
||||
},
|
||||
};
|
|
@ -1,74 +0,0 @@
|
|||
import * as types from '../mutation_types';
|
||||
import { findIndexOfFile } from '../utils';
|
||||
|
||||
export default {
|
||||
[types.SET_FILE_ACTIVE](state, { file, active }) {
|
||||
Object.assign(file, {
|
||||
active,
|
||||
});
|
||||
|
||||
Object.assign(state, {
|
||||
selectedFile: file,
|
||||
});
|
||||
},
|
||||
[types.TOGGLE_FILE_OPEN](state, file) {
|
||||
Object.assign(file, {
|
||||
opened: !file.opened,
|
||||
});
|
||||
|
||||
if (file.opened) {
|
||||
state.openFiles.push(file);
|
||||
} else {
|
||||
state.openFiles.splice(findIndexOfFile(state.openFiles, file), 1);
|
||||
}
|
||||
},
|
||||
[types.SET_FILE_DATA](state, { data, file }) {
|
||||
Object.assign(file, {
|
||||
blamePath: data.blame_path,
|
||||
commitsPath: data.commits_path,
|
||||
permalink: data.permalink,
|
||||
rawPath: data.raw_path,
|
||||
binary: data.binary,
|
||||
html: data.html,
|
||||
renderError: data.render_error,
|
||||
});
|
||||
},
|
||||
[types.SET_FILE_RAW_DATA](state, { file, raw }) {
|
||||
Object.assign(file, {
|
||||
raw,
|
||||
});
|
||||
},
|
||||
[types.UPDATE_FILE_CONTENT](state, { file, content }) {
|
||||
const changed = content !== file.raw;
|
||||
|
||||
Object.assign(file, {
|
||||
content,
|
||||
changed,
|
||||
});
|
||||
},
|
||||
[types.SET_FILE_LANGUAGE](state, { file, fileLanguage }) {
|
||||
Object.assign(file, {
|
||||
fileLanguage,
|
||||
});
|
||||
},
|
||||
[types.SET_FILE_EOL](state, { file, eol }) {
|
||||
Object.assign(file, {
|
||||
eol,
|
||||
});
|
||||
},
|
||||
[types.SET_FILE_POSITION](state, { file, editorRow, editorColumn }) {
|
||||
Object.assign(file, {
|
||||
editorRow,
|
||||
editorColumn,
|
||||
});
|
||||
},
|
||||
[types.DISCARD_FILE_CHANGES](state, file) {
|
||||
Object.assign(file, {
|
||||
content: file.raw,
|
||||
changed: false,
|
||||
});
|
||||
},
|
||||
[types.CREATE_TMP_FILE](state, { file, parent }) {
|
||||
parent.tree.push(file);
|
||||
},
|
||||
};
|
|
@ -1,23 +0,0 @@
|
|||
import * as types from '../mutation_types';
|
||||
|
||||
export default {
|
||||
[types.SET_CURRENT_PROJECT](state, currentProjectId) {
|
||||
Object.assign(state, {
|
||||
currentProjectId,
|
||||
});
|
||||
},
|
||||
[types.SET_PROJECT](state, { projectPath, project }) {
|
||||
// Add client side properties
|
||||
Object.assign(project, {
|
||||
tree: [],
|
||||
branches: {},
|
||||
active: true,
|
||||
});
|
||||
|
||||
Object.assign(state, {
|
||||
projects: Object.assign({}, state.projects, {
|
||||
[projectPath]: project,
|
||||
}),
|
||||
});
|
||||
},
|
||||
};
|
|
@ -1,36 +0,0 @@
|
|||
import * as types from '../mutation_types';
|
||||
|
||||
export default {
|
||||
[types.TOGGLE_TREE_OPEN](state, tree) {
|
||||
Object.assign(tree, {
|
||||
opened: !tree.opened,
|
||||
});
|
||||
},
|
||||
[types.CREATE_TREE](state, { treePath }) {
|
||||
Object.assign(state, {
|
||||
trees: Object.assign({}, state.trees, {
|
||||
[treePath]: {
|
||||
tree: [],
|
||||
},
|
||||
}),
|
||||
});
|
||||
},
|
||||
[types.SET_DIRECTORY_DATA](state, { data, tree }) {
|
||||
Object.assign(tree, {
|
||||
tree: data,
|
||||
});
|
||||
},
|
||||
[types.SET_PARENT_TREE_URL](state, url) {
|
||||
Object.assign(state, {
|
||||
parentTreeUrl: url,
|
||||
});
|
||||
},
|
||||
[types.SET_LAST_COMMIT_URL](state, { tree = state, url }) {
|
||||
Object.assign(tree, {
|
||||
lastCommitPath: url,
|
||||
});
|
||||
},
|
||||
[types.CREATE_TMP_TREE](state, { parent, tmpEntry }) {
|
||||
parent.tree.push(tmpEntry);
|
||||
},
|
||||
};
|
|
@ -1,23 +0,0 @@
|
|||
export default () => ({
|
||||
canCommit: false,
|
||||
currentProjectId: '',
|
||||
currentBranchId: '',
|
||||
currentBlobView: 'repo-editor',
|
||||
discardPopupOpen: false,
|
||||
editMode: true,
|
||||
endpoints: {},
|
||||
isRoot: false,
|
||||
isInitialRoot: false,
|
||||
lastCommitPath: '',
|
||||
loading: false,
|
||||
onTopOfBranch: false,
|
||||
openFiles: [],
|
||||
selectedFile: null,
|
||||
path: '',
|
||||
parentTreeUrl: '',
|
||||
trees: {},
|
||||
projects: {},
|
||||
leftPanelCollapsed: false,
|
||||
rightPanelCollapsed: true,
|
||||
panelResizing: false,
|
||||
});
|
|
@ -1,177 +0,0 @@
|
|||
import _ from 'underscore';
|
||||
|
||||
export const dataStructure = () => ({
|
||||
id: '',
|
||||
key: '',
|
||||
type: '',
|
||||
projectId: '',
|
||||
branchId: '',
|
||||
name: '',
|
||||
url: '',
|
||||
path: '',
|
||||
level: 0,
|
||||
tempFile: false,
|
||||
icon: '',
|
||||
tree: [],
|
||||
loading: false,
|
||||
opened: false,
|
||||
active: false,
|
||||
changed: false,
|
||||
lastCommitPath: '',
|
||||
lastCommit: {
|
||||
id: '',
|
||||
url: '',
|
||||
message: '',
|
||||
updatedAt: '',
|
||||
author: '',
|
||||
},
|
||||
tree_url: '',
|
||||
blamePath: '',
|
||||
commitsPath: '',
|
||||
permalink: '',
|
||||
rawPath: '',
|
||||
binary: false,
|
||||
html: '',
|
||||
raw: '',
|
||||
content: '',
|
||||
parentTreeUrl: '',
|
||||
renderError: false,
|
||||
base64: false,
|
||||
editorRow: 1,
|
||||
editorColumn: 1,
|
||||
fileLanguage: '',
|
||||
eol: '',
|
||||
});
|
||||
|
||||
export const decorateData = (entity) => {
|
||||
const {
|
||||
id,
|
||||
projectId,
|
||||
branchId,
|
||||
type,
|
||||
url,
|
||||
name,
|
||||
icon,
|
||||
tree_url,
|
||||
path,
|
||||
renderError,
|
||||
content = '',
|
||||
tempFile = false,
|
||||
active = false,
|
||||
opened = false,
|
||||
changed = false,
|
||||
parentTreeUrl = '',
|
||||
level = 0,
|
||||
base64 = false,
|
||||
} = entity;
|
||||
|
||||
return {
|
||||
...dataStructure(),
|
||||
id,
|
||||
projectId,
|
||||
branchId,
|
||||
key: `${name}-${type}-${id}`,
|
||||
type,
|
||||
name,
|
||||
url,
|
||||
tree_url,
|
||||
path,
|
||||
level,
|
||||
tempFile,
|
||||
icon: `fa-${icon}`,
|
||||
opened,
|
||||
active,
|
||||
parentTreeUrl,
|
||||
changed,
|
||||
renderError,
|
||||
content,
|
||||
base64,
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
Takes the multi-dimensional tree and returns a flattened array.
|
||||
This allows for the table to recursively render the table rows but keeps the data
|
||||
structure nested to make it easier to add new files/directories.
|
||||
*/
|
||||
export const treeList = (state, treeId) => {
|
||||
const baseTree = state.trees[treeId];
|
||||
if (baseTree) {
|
||||
const mapTree = arr => (!arr.tree || !arr.tree.length ?
|
||||
[] : _.map(arr.tree, a => [a, mapTree(a)]));
|
||||
|
||||
return _.chain(baseTree.tree)
|
||||
.map(arr => [arr, mapTree(arr)])
|
||||
.flatten()
|
||||
.value();
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
export const getTree = state => (namespace, projectId, branch) => state.trees[`${namespace}/${projectId}/${branch}`];
|
||||
|
||||
export const getTreeEntry = (store, treeId, path) => {
|
||||
const fileList = treeList(store.state, treeId);
|
||||
return fileList ? fileList.find(file => file.path === path) : null;
|
||||
};
|
||||
|
||||
export const findEntry = (tree, type, name) => tree.find(
|
||||
f => f.type === type && f.name === name,
|
||||
);
|
||||
|
||||
export const findIndexOfFile = (state, file) => state.findIndex(f => f.path === file.path);
|
||||
|
||||
export const setPageTitle = (title) => {
|
||||
document.title = title;
|
||||
};
|
||||
|
||||
export const createTemp = ({
|
||||
projectId, branchId, name, path, type, level, changed, content, base64, url,
|
||||
}) => {
|
||||
const treePath = path ? `${path}/${name}` : name;
|
||||
|
||||
return decorateData({
|
||||
id: new Date().getTime().toString(),
|
||||
projectId,
|
||||
branchId,
|
||||
name,
|
||||
type,
|
||||
tempFile: true,
|
||||
path: treePath,
|
||||
icon: type === 'tree' ? 'folder' : 'file-text-o',
|
||||
changed,
|
||||
content,
|
||||
parentTreeUrl: '',
|
||||
level,
|
||||
base64,
|
||||
renderError: base64,
|
||||
url,
|
||||
});
|
||||
};
|
||||
|
||||
export const createOrMergeEntry = ({ tree,
|
||||
projectId,
|
||||
branchId,
|
||||
entry,
|
||||
type,
|
||||
parentTreeUrl,
|
||||
level }) => {
|
||||
const found = findEntry(tree.tree || tree, type, entry.name);
|
||||
|
||||
if (found) {
|
||||
return Object.assign({}, found, {
|
||||
id: entry.id,
|
||||
url: entry.url,
|
||||
tempFile: false,
|
||||
});
|
||||
}
|
||||
|
||||
return decorateData({
|
||||
...entry,
|
||||
projectId,
|
||||
branchId,
|
||||
type,
|
||||
parentTreeUrl,
|
||||
level,
|
||||
});
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
class IdeController < ApplicationController
|
||||
layout 'nav_only'
|
||||
|
||||
def index
|
||||
end
|
||||
end
|
|
@ -320,10 +320,6 @@ module ApplicationHelper
|
|||
cookies["sidebar_collapsed"] == "true"
|
||||
end
|
||||
|
||||
def show_new_ide?
|
||||
cookies["new_repo"] == "true" && body_data_page != 'projects:show'
|
||||
end
|
||||
|
||||
def locale_path
|
||||
asset_path("locale/#{Gitlab::I18n.locale}/app.js")
|
||||
end
|
||||
|
|
|
@ -33,20 +33,6 @@ module BlobHelper
|
|||
ref)
|
||||
end
|
||||
|
||||
def ide_edit_button(project = @project, ref = @ref, path = @path, options = {})
|
||||
return unless show_new_ide?
|
||||
return unless blob = readable_blob(options, path, project, ref)
|
||||
|
||||
common_classes = "btn js-edit-ide #{options[:extra_class]}"
|
||||
|
||||
edit_button_tag(blob,
|
||||
common_classes,
|
||||
_('Web IDE'),
|
||||
ide_edit_path(project, ref, path, options),
|
||||
project,
|
||||
ref)
|
||||
end
|
||||
|
||||
def modify_file_button(project = @project, ref = @ref, path = @path, label:, action:, btn_class:, modal_type:)
|
||||
return unless current_user
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
- @body_class = 'ide'
|
||||
- page_title 'IDE'
|
||||
|
||||
- content_for :page_specific_javascripts do
|
||||
= webpack_bundle_tag 'common_vue'
|
||||
= webpack_bundle_tag 'ide', force_same_domain: true
|
||||
|
||||
#ide.ide-loading{ data: {"empty-state-svg-path" => image_path('illustrations/multi_file_editor_empty.svg')} }
|
||||
.text-center
|
||||
= icon('spinner spin 2x')
|
||||
%h2.clgray= _('Loading the GitLab IDE...')
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
.btn-group{ role: "group" }<
|
||||
= edit_blob_button
|
||||
= ide_edit_button
|
||||
- if current_user
|
||||
= replace_blob_link
|
||||
= delete_blob_link
|
||||
|
|
|
@ -72,11 +72,6 @@
|
|||
#{ _('New tag') }
|
||||
|
||||
.tree-controls
|
||||
- if show_new_ide?
|
||||
= succeed " " do
|
||||
= link_to ide_edit_path(@project, @id), class: 'btn btn-default' do
|
||||
= _('Web IDE')
|
||||
|
||||
= link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
|
||||
|
||||
= render 'projects/find_file_link'
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
- show_create = local_assigns.fetch(:show_create, false)
|
||||
|
||||
- show_new_branch_form = show_new_ide? && show_create && can?(current_user, :push_code, @project)
|
||||
- dropdown_toggle_text = @ref || @project.default_branch
|
||||
= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
|
||||
= hidden_field_tag :destination, destination
|
||||
|
@ -16,14 +15,3 @@
|
|||
= dropdown_filter _("Search branches and tags")
|
||||
= dropdown_content
|
||||
= dropdown_loading
|
||||
- if show_new_branch_form
|
||||
= dropdown_footer do
|
||||
%ul.dropdown-footer-list
|
||||
%li
|
||||
%a.dropdown-toggle-page{ href: "#" }
|
||||
Create new branch
|
||||
- if show_new_branch_form
|
||||
.dropdown-page-two
|
||||
= dropdown_title("Create new branch", options: { back: true })
|
||||
= dropdown_content do
|
||||
.js-new-branch-dropdown
|
||||
|
|
|
@ -43,8 +43,6 @@ Rails.application.routes.draw do
|
|||
get 'liveness' => 'health#liveness'
|
||||
get 'readiness' => 'health#readiness'
|
||||
post 'storage_check' => 'health#storage_check'
|
||||
get 'ide' => 'ide#index'
|
||||
get 'ide/*vueroute' => 'ide#index', format: false
|
||||
resources :metrics, only: [:index]
|
||||
mount Peek::Railtie => '/peek'
|
||||
|
||||
|
|
|
@ -47,7 +47,6 @@ function generateEntries() {
|
|||
common_vue: './vue_shared/vue_resource_interceptor.js',
|
||||
locale: './locale/index.js',
|
||||
main: './main.js',
|
||||
ide: './ide/index.js',
|
||||
raven: './raven/index.js',
|
||||
webpack_runtime: './webpack.js',
|
||||
};
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'Multi-file editor new directory', :js do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, :repository) }
|
||||
|
||||
before do
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
|
||||
set_cookie('new_repo', 'true')
|
||||
|
||||
visit project_tree_path(project, :master)
|
||||
|
||||
wait_for_requests
|
||||
|
||||
click_link('Web IDE')
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
after do
|
||||
set_cookie('new_repo', 'false')
|
||||
end
|
||||
|
||||
it 'creates directory in current directory' do
|
||||
find('.add-to-tree').click
|
||||
|
||||
click_link('New directory')
|
||||
|
||||
page.within('.modal') do
|
||||
find('.form-control').set('folder name')
|
||||
|
||||
click_button('Create directory')
|
||||
end
|
||||
|
||||
find('.add-to-tree').click
|
||||
|
||||
click_link('New file')
|
||||
|
||||
page.within('.modal-dialog') do
|
||||
find('.form-control').set('file name')
|
||||
|
||||
click_button('Create file')
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
||||
find('.multi-file-commit-panel-collapse-btn').click
|
||||
|
||||
fill_in('commit-message', with: 'commit message ide')
|
||||
|
||||
click_button('Commit')
|
||||
|
||||
expect(page).to have_content('folder name')
|
||||
end
|
||||
end
|
|
@ -1,47 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'Multi-file editor new file', :js do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, :repository) }
|
||||
|
||||
before do
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
|
||||
set_cookie('new_repo', 'true')
|
||||
|
||||
visit project_tree_path(project, :master)
|
||||
|
||||
wait_for_requests
|
||||
|
||||
click_link('Web IDE')
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
after do
|
||||
set_cookie('new_repo', 'false')
|
||||
end
|
||||
|
||||
it 'creates file in current directory' do
|
||||
find('.add-to-tree').click
|
||||
|
||||
click_link('New file')
|
||||
|
||||
page.within('.modal') do
|
||||
find('.form-control').set('file name')
|
||||
|
||||
click_button('Create file')
|
||||
end
|
||||
|
||||
wait_for_requests
|
||||
|
||||
find('.multi-file-commit-panel-collapse-btn').click
|
||||
|
||||
fill_in('commit-message', with: 'commit message ide')
|
||||
|
||||
click_button('Commit')
|
||||
|
||||
expect(page).to have_content('file name')
|
||||
end
|
||||
end
|
|
@ -1,53 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
feature 'Multi-file editor upload file', :js do
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:txt_file) { File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt') }
|
||||
let(:img_file) { File.join(Rails.root, 'spec', 'fixtures', 'dk.png') }
|
||||
|
||||
before do
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
|
||||
set_cookie('new_repo', 'true')
|
||||
|
||||
visit project_tree_path(project, :master)
|
||||
|
||||
wait_for_requests
|
||||
|
||||
click_link('Web IDE')
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
after do
|
||||
set_cookie('new_repo', 'false')
|
||||
end
|
||||
|
||||
it 'uploads text file' do
|
||||
find('.add-to-tree').click
|
||||
|
||||
# make the field visible so capybara can use it
|
||||
execute_script('document.querySelector("#file-upload").classList.remove("hidden")')
|
||||
attach_file('file-upload', txt_file)
|
||||
|
||||
find('.add-to-tree').click
|
||||
|
||||
expect(page).to have_selector('.multi-file-tab', text: 'doc_sample.txt')
|
||||
expect(find('.blob-editor-container .lines-content')['innerText']).to have_content(File.open(txt_file, &:readline))
|
||||
end
|
||||
|
||||
it 'uploads image file' do
|
||||
find('.add-to-tree').click
|
||||
|
||||
# make the field visible so capybara can use it
|
||||
execute_script('document.querySelector("#file-upload").classList.remove("hidden")')
|
||||
attach_file('file-upload', img_file)
|
||||
|
||||
find('.add-to-tree').click
|
||||
|
||||
expect(page).to have_selector('.multi-file-tab', text: 'dk.png')
|
||||
expect(page).not_to have_selector('.monaco-editor')
|
||||
end
|
||||
end
|
|
@ -1,33 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import listCollapsed from '~/ide/components/commit_sidebar/list_collapsed.vue';
|
||||
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||
import { file } from '../../helpers';
|
||||
|
||||
describe('Multi-file editor commit sidebar list collapsed', () => {
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
const Component = Vue.extend(listCollapsed);
|
||||
|
||||
vm = createComponentWithStore(Component, store);
|
||||
|
||||
vm.$store.state.openFiles.push(file('file1'), file('file2'));
|
||||
vm.$store.state.openFiles[0].tempFile = true;
|
||||
vm.$store.state.openFiles.forEach((f) => {
|
||||
Object.assign(f, {
|
||||
changed: true,
|
||||
});
|
||||
});
|
||||
|
||||
vm.$mount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
it('renders added & modified files count', () => {
|
||||
expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toBe('1 1');
|
||||
});
|
||||
});
|
|
@ -1,53 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import listItem from '~/ide/components/commit_sidebar/list_item.vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import { file } from '../../helpers';
|
||||
|
||||
describe('Multi-file editor commit sidebar list item', () => {
|
||||
let vm;
|
||||
let f;
|
||||
|
||||
beforeEach(() => {
|
||||
const Component = Vue.extend(listItem);
|
||||
|
||||
f = file('test-file');
|
||||
|
||||
vm = mountComponent(Component, {
|
||||
file: f,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
it('renders file path', () => {
|
||||
expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent.trim()).toBe(f.path);
|
||||
});
|
||||
|
||||
describe('computed', () => {
|
||||
describe('iconName', () => {
|
||||
it('returns modified when not a tempFile', () => {
|
||||
expect(vm.iconName).toBe('file-modified');
|
||||
});
|
||||
|
||||
it('returns addition when not a tempFile', () => {
|
||||
f.tempFile = true;
|
||||
|
||||
expect(vm.iconName).toBe('file-addition');
|
||||
});
|
||||
});
|
||||
|
||||
describe('iconClass', () => {
|
||||
it('returns modified when not a tempFile', () => {
|
||||
expect(vm.iconClass).toContain('multi-file-modified');
|
||||
});
|
||||
|
||||
it('returns addition when not a tempFile', () => {
|
||||
f.tempFile = true;
|
||||
|
||||
expect(vm.iconClass).toContain('multi-file-addition');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,59 +0,0 @@
|
|||
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';
|
||||
|
||||
describe('Multi-file editor commit sidebar list', () => {
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
const Component = Vue.extend(commitSidebarList);
|
||||
|
||||
vm = createComponentWithStore(Component, store, {
|
||||
title: 'Staged',
|
||||
fileList: [],
|
||||
});
|
||||
|
||||
vm.$store.state.rightPanelCollapsed = false;
|
||||
|
||||
vm.$mount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('empty file list', () => {
|
||||
it('renders no changes text', () => {
|
||||
expect(vm.$el.querySelector('.help-block').textContent.trim()).toBe('No changes');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a list of files', () => {
|
||||
beforeEach((done) => {
|
||||
const f = file('file name');
|
||||
f.changed = true;
|
||||
vm.fileList.push(f);
|
||||
|
||||
Vue.nextTick(done);
|
||||
});
|
||||
|
||||
it('renders list', () => {
|
||||
expect(vm.$el.querySelectorAll('li').length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('collapsed', () => {
|
||||
beforeEach((done) => {
|
||||
vm.$store.state.rightPanelCollapsed = true;
|
||||
|
||||
Vue.nextTick(done);
|
||||
});
|
||||
|
||||
it('hides list', () => {
|
||||
expect(vm.$el.querySelector('.list-unstyled')).toBeNull();
|
||||
expect(vm.$el.querySelector('.help-block')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import ideContextBar from '~/ide/components/ide_context_bar.vue';
|
||||
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||
|
||||
describe('Multi-file editor right context bar', () => {
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
const Component = Vue.extend(ideContextBar);
|
||||
|
||||
vm = createComponentWithStore(Component, store);
|
||||
|
||||
vm.$store.state.rightPanelCollapsed = false;
|
||||
|
||||
vm.$mount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('collapsed', () => {
|
||||
beforeEach((done) => {
|
||||
vm.$store.state.rightPanelCollapsed = true;
|
||||
|
||||
Vue.nextTick(done);
|
||||
});
|
||||
|
||||
it('adds collapsed class', () => {
|
||||
expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('shows correct icon', () => {
|
||||
expect(vm.currentIcon).toBe('angle-double-left');
|
||||
});
|
||||
});
|
||||
|
||||
it('clicking toggle collapse button collapses the bar', () => {
|
||||
spyOn(vm, 'setPanelCollapsedStatus').and.returnValue(Promise.resolve());
|
||||
|
||||
vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click();
|
||||
|
||||
expect(vm.setPanelCollapsedStatus).toHaveBeenCalledWith({
|
||||
side: 'right',
|
||||
collapsed: true,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,63 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import ideRepoTree from '~/ide/components/ide_repo_tree.vue';
|
||||
import { file, resetStore } from '../helpers';
|
||||
|
||||
describe('IdeRepoTree', () => {
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
const IdeRepoTree = Vue.extend(ideRepoTree);
|
||||
|
||||
vm = new IdeRepoTree({
|
||||
store,
|
||||
propsData: {
|
||||
treeId: 'abcproject/mybranch',
|
||||
},
|
||||
});
|
||||
|
||||
vm.$store.state.currentBranch = 'master';
|
||||
vm.$store.state.isRoot = true;
|
||||
vm.$store.state.trees['abcproject/mybranch'] = {
|
||||
tree: [file()],
|
||||
};
|
||||
|
||||
vm.$mount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('renders a sidebar', () => {
|
||||
const tbody = vm.$el.querySelector('tbody');
|
||||
|
||||
expect(vm.$el.classList.contains('sidebar-mini')).toBeFalsy();
|
||||
expect(tbody.querySelector('.repo-file-options')).toBeFalsy();
|
||||
expect(tbody.querySelector('.prev-directory')).toBeFalsy();
|
||||
expect(tbody.querySelector('.loading-file')).toBeFalsy();
|
||||
expect(tbody.querySelector('.file')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders 3 loading files if tree is loading', (done) => {
|
||||
vm.treeId = '123';
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toEqual(3);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders a prev directory if is not root', (done) => {
|
||||
vm.$store.state.isRoot = false;
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$el.querySelector('tbody .prev-directory')).toBeTruthy();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,43 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import ideSidebar from '~/ide/components/ide_side_bar.vue';
|
||||
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||
import { resetStore } from '../helpers';
|
||||
|
||||
describe('IdeSidebar', () => {
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
const Component = Vue.extend(ideSidebar);
|
||||
|
||||
vm = createComponentWithStore(Component, store).$mount();
|
||||
|
||||
vm.$store.state.leftPanelCollapsed = false;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('renders a sidebar', () => {
|
||||
expect(vm.$el.querySelector('.multi-file-commit-panel-inner')).not.toBeNull();
|
||||
});
|
||||
|
||||
describe('collapsed', () => {
|
||||
beforeEach((done) => {
|
||||
vm.$store.state.leftPanelCollapsed = true;
|
||||
|
||||
Vue.nextTick(done);
|
||||
});
|
||||
|
||||
it('adds collapsed class', () => {
|
||||
expect(vm.$el.classList).toContain('is-collapsed');
|
||||
});
|
||||
|
||||
it('shows correct icon', () => {
|
||||
expect(vm.currentIcon).toBe('angle-double-right');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,39 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import ide from '~/ide/components/ide.vue';
|
||||
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||
import { file, resetStore } from '../helpers';
|
||||
|
||||
describe('ide component', () => {
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
const Component = Vue.extend(ide);
|
||||
|
||||
vm = createComponentWithStore(Component, store, {
|
||||
emptyStateSvgPath: 'svg',
|
||||
}).$mount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('does not render panel right when no files open', () => {
|
||||
expect(vm.$el.querySelector('.panel-right')).toBeNull();
|
||||
});
|
||||
|
||||
it('renders panel right when files are open', (done) => {
|
||||
vm.$store.state.trees['abcproject/mybranch'] = {
|
||||
tree: [file()],
|
||||
};
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$el.querySelector('.panel-right')).toBeNull();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,114 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import newBranchForm from '~/ide/components/new_branch_form.vue';
|
||||
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||
import { resetStore } from '../helpers';
|
||||
|
||||
describe('Multi-file editor new branch form', () => {
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
const Component = Vue.extend(newBranchForm);
|
||||
|
||||
vm = createComponentWithStore(Component, store);
|
||||
|
||||
vm.$store.state.currentBranch = 'master';
|
||||
|
||||
vm.$mount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
it('renders submit as disabled', () => {
|
||||
expect(vm.$el.querySelector('.btn').getAttribute('disabled')).toBe('disabled');
|
||||
});
|
||||
|
||||
it('enables the submit button when branch is not empty', (done) => {
|
||||
vm.branchName = 'testing';
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$el.querySelector('.btn').getAttribute('disabled')).toBeNull();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('displays current branch creating from', (done) => {
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$el.querySelector('p').textContent.replace(/\s+/g, ' ').trim()).toBe('Create from: master');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('submitNewBranch', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(vm, 'createNewBranch').and.returnValue(Promise.resolve());
|
||||
});
|
||||
|
||||
it('sets to loading', () => {
|
||||
vm.submitNewBranch();
|
||||
|
||||
expect(vm.loading).toBeTruthy();
|
||||
});
|
||||
|
||||
it('hides current flash element', (done) => {
|
||||
vm.$refs.flashContainer.innerHTML = '<div class="flash-alert"></div>';
|
||||
|
||||
vm.submitNewBranch();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$el.querySelector('.flash-alert')).toBeNull();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls createdNewBranch with branchName', () => {
|
||||
vm.branchName = 'testing';
|
||||
|
||||
vm.submitNewBranch();
|
||||
|
||||
expect(vm.createNewBranch).toHaveBeenCalledWith('testing');
|
||||
});
|
||||
});
|
||||
|
||||
describe('submitNewBranch with error', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(vm, 'createNewBranch').and.returnValue(Promise.reject({
|
||||
json: () => Promise.resolve({
|
||||
message: 'error message',
|
||||
}),
|
||||
}));
|
||||
});
|
||||
|
||||
it('sets loading to false', (done) => {
|
||||
vm.loading = true;
|
||||
|
||||
vm.submitNewBranch();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(vm.loading).toBeFalsy();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('creates flash element', (done) => {
|
||||
vm.submitNewBranch();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(vm.$el.querySelector('.flash-alert')).not.toBeNull();
|
||||
expect(vm.$el.querySelector('.flash-alert').textContent.trim()).toBe('error message');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,77 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import newDropdown from '~/ide/components/new_dropdown/index.vue';
|
||||
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||
import { resetStore } from '../../helpers';
|
||||
|
||||
describe('new dropdown component', () => {
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
const component = Vue.extend(newDropdown);
|
||||
|
||||
vm = createComponentWithStore(component, store, {
|
||||
branch: 'master',
|
||||
path: '',
|
||||
});
|
||||
|
||||
vm.$store.state.currentProjectId = 'abcproject';
|
||||
vm.$store.state.path = '';
|
||||
|
||||
vm.$mount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('renders new file, upload and new directory links', () => {
|
||||
expect(vm.$el.querySelectorAll('a')[0].textContent.trim()).toBe('New file');
|
||||
expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe('Upload file');
|
||||
expect(vm.$el.querySelectorAll('a')[2].textContent.trim()).toBe('New directory');
|
||||
});
|
||||
|
||||
describe('createNewItem', () => {
|
||||
it('sets modalType to blob when new file is clicked', () => {
|
||||
vm.$el.querySelectorAll('a')[0].click();
|
||||
|
||||
expect(vm.modalType).toBe('blob');
|
||||
});
|
||||
|
||||
it('sets modalType to tree when new directory is clicked', () => {
|
||||
vm.$el.querySelectorAll('a')[2].click();
|
||||
|
||||
expect(vm.modalType).toBe('tree');
|
||||
});
|
||||
|
||||
it('opens modal when link is clicked', (done) => {
|
||||
vm.$el.querySelectorAll('a')[0].click();
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$el.querySelector('.modal')).not.toBeNull();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('hideModal', () => {
|
||||
beforeAll((done) => {
|
||||
vm.openModal = true;
|
||||
Vue.nextTick(done);
|
||||
});
|
||||
|
||||
it('closes modal after toggling', (done) => {
|
||||
vm.hideModal();
|
||||
|
||||
Vue.nextTick()
|
||||
.then(() => {
|
||||
expect(vm.$el.querySelector('.modal')).toBeNull();
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,237 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import service from '~/ide/services';
|
||||
import modal from '~/ide/components/new_dropdown/modal.vue';
|
||||
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||
import { file, resetStore } from '../../helpers';
|
||||
|
||||
describe('new file modal component', () => {
|
||||
const Component = Vue.extend(modal);
|
||||
let vm;
|
||||
let projectTree;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(service, 'getProjectData').and.returnValue(Promise.resolve({
|
||||
data: {
|
||||
id: '123',
|
||||
},
|
||||
}));
|
||||
|
||||
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
|
||||
data: {
|
||||
commit: {
|
||||
id: '123branch',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
|
||||
headers: {
|
||||
'page-title': 'test',
|
||||
},
|
||||
json: () => Promise.resolve({
|
||||
last_commit_path: 'last_commit_path',
|
||||
parent_tree_url: 'parent_tree_url',
|
||||
path: '/',
|
||||
trees: [{ name: 'tree' }],
|
||||
blobs: [{ name: 'blob' }],
|
||||
submodules: [{ name: 'submodule' }],
|
||||
}),
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
['tree', 'blob'].forEach((type) => {
|
||||
describe(type, () => {
|
||||
beforeEach(() => {
|
||||
store.state.projects.abcproject = {
|
||||
web_url: '',
|
||||
};
|
||||
store.state.trees = [];
|
||||
store.state.trees['abcproject/mybranch'] = {
|
||||
tree: [],
|
||||
};
|
||||
projectTree = store.state.trees['abcproject/mybranch'];
|
||||
store.state.currentProjectId = 'abcproject';
|
||||
|
||||
vm = createComponentWithStore(Component, store, {
|
||||
type,
|
||||
branchId: 'master',
|
||||
path: '',
|
||||
parent: projectTree,
|
||||
});
|
||||
|
||||
vm.entryName = 'testing';
|
||||
|
||||
vm.$mount();
|
||||
});
|
||||
|
||||
it(`sets modal title as ${type}`, () => {
|
||||
const title = type === 'tree' ? 'directory' : 'file';
|
||||
|
||||
expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe(`Create new ${title}`);
|
||||
});
|
||||
|
||||
it(`sets button label as ${type}`, () => {
|
||||
const title = type === 'tree' ? 'directory' : 'file';
|
||||
|
||||
expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe(`Create ${title}`);
|
||||
});
|
||||
|
||||
it(`sets form label as ${type}`, () => {
|
||||
const title = type === 'tree' ? 'Directory' : 'File';
|
||||
|
||||
expect(vm.$el.querySelector('.label-light').textContent.trim()).toBe(`${title} name`);
|
||||
});
|
||||
|
||||
describe('createEntryInStore', () => {
|
||||
it('calls createTempEntry', () => {
|
||||
spyOn(vm, 'createTempEntry');
|
||||
|
||||
vm.createEntryInStore();
|
||||
|
||||
expect(vm.createTempEntry).toHaveBeenCalledWith({
|
||||
projectId: 'abcproject',
|
||||
branchId: 'master',
|
||||
parent: projectTree,
|
||||
name: 'testing',
|
||||
type,
|
||||
});
|
||||
});
|
||||
|
||||
it('sets editMode to true', (done) => {
|
||||
vm.createEntryInStore();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(vm.$store.state.editMode).toBeTruthy();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('toggles blob view', (done) => {
|
||||
vm.createEntryInStore();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(vm.$store.state.currentBlobView).toBe('repo-editor');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('opens newly created file', (done) => {
|
||||
if (type === 'blob') {
|
||||
vm.createEntryInStore();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(vm.$store.state.openFiles.length).toBe(1);
|
||||
expect(vm.$store.state.openFiles[0].name).toBe(type === 'blob' ? 'testing' : '.gitkeep');
|
||||
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
if (type === 'blob') {
|
||||
it('creates new file', (done) => {
|
||||
vm.createEntryInStore();
|
||||
|
||||
setTimeout(() => {
|
||||
const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
|
||||
expect(baseTree.length).toBe(1);
|
||||
expect(baseTree[0].name).toBe('testing');
|
||||
expect(baseTree[0].type).toBe('blob');
|
||||
expect(baseTree[0].tempFile).toBeTruthy();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not create temp file when file already exists', (done) => {
|
||||
const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
|
||||
baseTree.push(file('testing', '1', type));
|
||||
|
||||
vm.createEntryInStore();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(baseTree.length).toBe(1);
|
||||
expect(baseTree[0].name).toBe('testing');
|
||||
expect(baseTree[0].type).toBe('blob');
|
||||
expect(baseTree[0].tempFile).toBeFalsy();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
it('creates new tree', () => {
|
||||
vm.createEntryInStore();
|
||||
|
||||
const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
|
||||
expect(baseTree.length).toBe(1);
|
||||
expect(baseTree[0].name).toBe('testing');
|
||||
expect(baseTree[0].type).toBe('tree');
|
||||
expect(baseTree[0].tempFile).toBeTruthy();
|
||||
});
|
||||
|
||||
it('creates multiple trees when entryName has slashes', () => {
|
||||
vm.entryName = 'app/test';
|
||||
vm.createEntryInStore();
|
||||
|
||||
const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
|
||||
expect(baseTree.length).toBe(1);
|
||||
expect(baseTree[0].name).toBe('app');
|
||||
});
|
||||
|
||||
it('creates tree in existing tree', () => {
|
||||
const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
|
||||
baseTree.push(file('app', '1', 'tree'));
|
||||
|
||||
vm.entryName = 'app/test';
|
||||
vm.createEntryInStore();
|
||||
|
||||
expect(baseTree.length).toBe(1);
|
||||
expect(baseTree[0].name).toBe('app');
|
||||
expect(baseTree[0].tempFile).toBeFalsy();
|
||||
expect(baseTree[0].tree[0].tempFile).toBeTruthy();
|
||||
expect(baseTree[0].tree[0].name).toBe('test');
|
||||
});
|
||||
|
||||
it('does not create new tree when already exists', () => {
|
||||
const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
|
||||
baseTree.push(file('app', '1', 'tree'));
|
||||
|
||||
vm.entryName = 'app';
|
||||
vm.createEntryInStore();
|
||||
|
||||
expect(baseTree.length).toBe(1);
|
||||
expect(baseTree[0].name).toBe('app');
|
||||
expect(baseTree[0].tempFile).toBeFalsy();
|
||||
expect(baseTree[0].tree.length).toBe(0);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('focuses field on mount', () => {
|
||||
document.body.innerHTML += '<div class="js-test"></div>';
|
||||
|
||||
vm = createComponentWithStore(Component, store, {
|
||||
type: 'tree',
|
||||
projectId: 'abcproject',
|
||||
branchId: 'master',
|
||||
path: '',
|
||||
}).$mount('.js-test');
|
||||
|
||||
expect(document.activeElement).toBe(vm.$refs.fieldName);
|
||||
|
||||
vm.$el.remove();
|
||||
});
|
||||
});
|
|
@ -1,158 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import upload from '~/ide/components/new_dropdown/upload.vue';
|
||||
import store from '~/ide/stores';
|
||||
import service from '~/ide/services';
|
||||
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||
import { resetStore } from '../../helpers';
|
||||
|
||||
describe('new dropdown upload', () => {
|
||||
let vm;
|
||||
let projectTree;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(service, 'getProjectData').and.returnValue(Promise.resolve({
|
||||
data: {
|
||||
id: '123',
|
||||
},
|
||||
}));
|
||||
|
||||
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
|
||||
data: {
|
||||
commit: {
|
||||
id: '123branch',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
|
||||
headers: {
|
||||
'page-title': 'test',
|
||||
},
|
||||
json: () => Promise.resolve({
|
||||
last_commit_path: 'last_commit_path',
|
||||
parent_tree_url: 'parent_tree_url',
|
||||
path: '/',
|
||||
trees: [{ name: 'tree' }],
|
||||
blobs: [{ name: 'blob' }],
|
||||
submodules: [{ name: 'submodule' }],
|
||||
}),
|
||||
}));
|
||||
|
||||
const Component = Vue.extend(upload);
|
||||
|
||||
store.state.projects.abcproject = {
|
||||
web_url: '',
|
||||
};
|
||||
store.state.currentProjectId = 'abcproject';
|
||||
store.state.trees = [];
|
||||
store.state.trees['abcproject/mybranch'] = {
|
||||
tree: [],
|
||||
};
|
||||
projectTree = store.state.trees['abcproject/mybranch'];
|
||||
|
||||
vm = createComponentWithStore(Component, store, {
|
||||
branchId: 'master',
|
||||
path: '',
|
||||
parent: projectTree,
|
||||
});
|
||||
|
||||
vm.entryName = 'testing';
|
||||
|
||||
vm.$mount();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
describe('readFile', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(FileReader.prototype, 'readAsText');
|
||||
spyOn(FileReader.prototype, 'readAsDataURL');
|
||||
});
|
||||
|
||||
it('calls readAsText for text files', () => {
|
||||
const file = {
|
||||
type: 'text/html',
|
||||
};
|
||||
|
||||
vm.readFile(file);
|
||||
|
||||
expect(FileReader.prototype.readAsText).toHaveBeenCalledWith(file);
|
||||
});
|
||||
|
||||
it('calls readAsDataURL for non-text files', () => {
|
||||
const file = {
|
||||
type: 'images/png',
|
||||
};
|
||||
|
||||
vm.readFile(file);
|
||||
|
||||
expect(FileReader.prototype.readAsDataURL).toHaveBeenCalledWith(file);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createFile', () => {
|
||||
const target = {
|
||||
result: 'content',
|
||||
};
|
||||
const binaryTarget = {
|
||||
result: 'base64,base64content',
|
||||
};
|
||||
const file = {
|
||||
name: 'file',
|
||||
};
|
||||
|
||||
it('creates new file', (done) => {
|
||||
vm.createFile(target, file, true);
|
||||
|
||||
vm.$nextTick(() => {
|
||||
const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
|
||||
expect(baseTree.length).toBe(1);
|
||||
expect(baseTree[0].name).toBe(file.name);
|
||||
expect(baseTree[0].content).toBe(target.result);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('creates new file in path', (done) => {
|
||||
const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
|
||||
const tree = {
|
||||
type: 'tree',
|
||||
name: 'testing',
|
||||
path: 'testing',
|
||||
tree: [],
|
||||
};
|
||||
baseTree.push(tree);
|
||||
|
||||
vm.parent = tree;
|
||||
vm.createFile(target, file, true);
|
||||
|
||||
vm.$nextTick(() => {
|
||||
expect(baseTree.length).toBe(1);
|
||||
expect(baseTree[0].tree[0].name).toBe(file.name);
|
||||
expect(baseTree[0].tree[0].content).toBe(target.result);
|
||||
expect(baseTree[0].tree[0].path).toBe(`testing/${file.name}`);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('splits content on base64 if binary', (done) => {
|
||||
vm.createFile(binaryTarget, file, false);
|
||||
|
||||
vm.$nextTick(() => {
|
||||
const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
|
||||
expect(baseTree.length).toBe(1);
|
||||
expect(baseTree[0].name).toBe(file.name);
|
||||
expect(baseTree[0].content).toBe(binaryTarget.result.split('base64,')[1]);
|
||||
expect(baseTree[0].base64).toBe(true);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,140 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import * as urlUtils from '~/lib/utils/url_utility';
|
||||
import store from '~/ide/stores';
|
||||
import service from '~/ide/services';
|
||||
import repoCommitSection from '~/ide/components/repo_commit_section.vue';
|
||||
import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
|
||||
import { file, resetStore } from '../helpers';
|
||||
|
||||
describe('RepoCommitSection', () => {
|
||||
let vm;
|
||||
|
||||
function createComponent() {
|
||||
const RepoCommitSection = Vue.extend(repoCommitSection);
|
||||
|
||||
const comp = new RepoCommitSection({
|
||||
store,
|
||||
}).$mount();
|
||||
|
||||
comp.$store.state.currentProjectId = 'abcproject';
|
||||
comp.$store.state.currentBranchId = 'master';
|
||||
comp.$store.state.projects.abcproject = {
|
||||
web_url: '',
|
||||
branches: {
|
||||
master: {
|
||||
workingReference: '1',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
comp.$store.state.rightPanelCollapsed = false;
|
||||
comp.$store.state.currentBranch = 'master';
|
||||
comp.$store.state.openFiles = [file('file1'), file('file2')];
|
||||
comp.$store.state.openFiles.forEach(f => Object.assign(f, {
|
||||
changed: true,
|
||||
content: 'testing',
|
||||
}));
|
||||
|
||||
return comp.$mount();
|
||||
}
|
||||
|
||||
beforeEach((done) => {
|
||||
vm = createComponent();
|
||||
|
||||
spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
|
||||
headers: {
|
||||
'page-title': 'test',
|
||||
},
|
||||
json: () => Promise.resolve({
|
||||
last_commit_path: 'last_commit_path',
|
||||
parent_tree_url: 'parent_tree_url',
|
||||
path: '/',
|
||||
trees: [{ name: 'tree' }],
|
||||
blobs: [{ name: 'blob' }],
|
||||
submodules: [{ name: 'submodule' }],
|
||||
}),
|
||||
}));
|
||||
|
||||
Vue.nextTick(done);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('renders a commit section', () => {
|
||||
const changedFileElements = [...vm.$el.querySelectorAll('.multi-file-commit-list li')];
|
||||
const submitCommit = vm.$el.querySelector('form .btn');
|
||||
|
||||
expect(vm.$el.querySelector('.multi-file-commit-form')).not.toBeNull();
|
||||
expect(changedFileElements.length).toEqual(2);
|
||||
|
||||
changedFileElements.forEach((changedFile, i) => {
|
||||
expect(changedFile.textContent.trim()).toEqual(vm.$store.getters.changedFiles[i].path);
|
||||
});
|
||||
|
||||
expect(submitCommit.disabled).toBeTruthy();
|
||||
expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeNull();
|
||||
});
|
||||
|
||||
describe('when submitting', () => {
|
||||
let changedFiles;
|
||||
|
||||
beforeEach(() => {
|
||||
vm.commitMessage = 'testing';
|
||||
changedFiles = JSON.parse(JSON.stringify(vm.$store.getters.changedFiles));
|
||||
|
||||
spyOn(service, 'commit').and.returnValue(Promise.resolve({
|
||||
data: {
|
||||
short_id: '1',
|
||||
stats: {},
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
it('allows you to submit', () => {
|
||||
expect(vm.$el.querySelector('form .btn').disabled).toBeTruthy();
|
||||
});
|
||||
|
||||
it('submits commit', (done) => {
|
||||
vm.makeCommit();
|
||||
|
||||
// Wait for the branch check to finish
|
||||
getSetTimeoutPromise()
|
||||
.then(() => Vue.nextTick())
|
||||
.then(() => {
|
||||
const args = service.commit.calls.allArgs()[0];
|
||||
const { commit_message, actions, branch: payloadBranch } = args[1];
|
||||
|
||||
expect(commit_message).toBe('testing');
|
||||
expect(actions.length).toEqual(2);
|
||||
expect(payloadBranch).toEqual('master');
|
||||
expect(actions[0].action).toEqual('update');
|
||||
expect(actions[1].action).toEqual('update');
|
||||
expect(actions[0].content).toEqual(changedFiles[0].content);
|
||||
expect(actions[1].content).toEqual(changedFiles[1].content);
|
||||
expect(actions[0].file_path).toEqual(changedFiles[0].path);
|
||||
expect(actions[1].file_path).toEqual(changedFiles[1].path);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('redirects to MR creation page if start new MR checkbox checked', (done) => {
|
||||
spyOn(urlUtils, 'visitUrl');
|
||||
vm.startNewMR = true;
|
||||
|
||||
vm.makeCommit();
|
||||
|
||||
getSetTimeoutPromise()
|
||||
.then(() => Vue.nextTick())
|
||||
.then(() => {
|
||||
expect(urlUtils.visitUrl).toHaveBeenCalled();
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,83 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import repoEditButton from '~/ide/components/repo_edit_button.vue';
|
||||
import { file, resetStore } from '../helpers';
|
||||
|
||||
describe('RepoEditButton', () => {
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
const f = file();
|
||||
const RepoEditButton = Vue.extend(repoEditButton);
|
||||
|
||||
vm = new RepoEditButton({
|
||||
store,
|
||||
});
|
||||
|
||||
f.active = true;
|
||||
vm.$store.dispatch('setInitialData', {
|
||||
canCommit: true,
|
||||
onTopOfBranch: true,
|
||||
});
|
||||
vm.$store.state.openFiles.push(f);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('renders an edit button', () => {
|
||||
vm.$mount();
|
||||
|
||||
expect(vm.$el.querySelector('.btn')).not.toBeNull();
|
||||
expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Cancel edit');
|
||||
});
|
||||
|
||||
it('renders edit button with cancel text', () => {
|
||||
vm.$store.state.editMode = true;
|
||||
|
||||
vm.$mount();
|
||||
|
||||
expect(vm.$el.querySelector('.btn')).not.toBeNull();
|
||||
expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Cancel edit');
|
||||
});
|
||||
|
||||
it('toggles edit mode on click', (done) => {
|
||||
vm.$mount();
|
||||
|
||||
vm.$el.querySelector('.btn').click();
|
||||
|
||||
vm.$nextTick(() => {
|
||||
expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Edit');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('discardPopupOpen', () => {
|
||||
beforeEach(() => {
|
||||
vm.$store.state.discardPopupOpen = true;
|
||||
vm.$store.state.editMode = true;
|
||||
vm.$store.state.openFiles[0].changed = true;
|
||||
|
||||
vm.$mount();
|
||||
});
|
||||
|
||||
it('renders popup', () => {
|
||||
expect(vm.$el.querySelector('.modal')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('removes all changed files', (done) => {
|
||||
vm.$el.querySelector('.btn-warning').click();
|
||||
|
||||
vm.$nextTick(() => {
|
||||
expect(vm.$store.getters.changedFiles.length).toBe(0);
|
||||
expect(vm.$el.querySelector('.modal')).toBeNull();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,60 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import repoEditor from '~/ide/components/repo_editor.vue';
|
||||
import monacoLoader from '~/ide/monaco_loader';
|
||||
import { file, resetStore } from '../helpers';
|
||||
|
||||
describe('RepoEditor', () => {
|
||||
let vm;
|
||||
|
||||
beforeEach((done) => {
|
||||
const f = file();
|
||||
const RepoEditor = Vue.extend(repoEditor);
|
||||
|
||||
vm = new RepoEditor({
|
||||
store,
|
||||
});
|
||||
|
||||
f.active = true;
|
||||
f.tempFile = true;
|
||||
vm.$store.state.openFiles.push(f);
|
||||
vm.$store.getters.activeFile.html = 'testing';
|
||||
vm.monaco = true;
|
||||
|
||||
vm.$mount();
|
||||
|
||||
monacoLoader(['vs/editor/editor.main'], () => {
|
||||
setTimeout(done, 0);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('renders an ide container', (done) => {
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.shouldHideEditor).toBeFalsy();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when open file is binary and not raw', () => {
|
||||
beforeEach((done) => {
|
||||
vm.$store.getters.activeFile.binary = true;
|
||||
|
||||
Vue.nextTick(done);
|
||||
});
|
||||
|
||||
it('does not render the IDE', () => {
|
||||
expect(vm.shouldHideEditor).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows activeFile html', () => {
|
||||
expect(vm.$el.textContent).toContain('testing');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import repoFileButtons from '~/ide/components/repo_file_buttons.vue';
|
||||
import { file, resetStore } from '../helpers';
|
||||
|
||||
describe('RepoFileButtons', () => {
|
||||
const activeFile = file();
|
||||
let vm;
|
||||
|
||||
function createComponent() {
|
||||
const RepoFileButtons = Vue.extend(repoFileButtons);
|
||||
|
||||
activeFile.rawPath = 'test';
|
||||
activeFile.blamePath = 'test';
|
||||
activeFile.commitsPath = 'test';
|
||||
activeFile.active = true;
|
||||
store.state.openFiles.push(activeFile);
|
||||
|
||||
return new RepoFileButtons({
|
||||
store,
|
||||
}).$mount();
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('renders Raw, Blame, History, Permalink and Preview toggle', (done) => {
|
||||
vm = createComponent();
|
||||
|
||||
vm.$nextTick(() => {
|
||||
const raw = vm.$el.querySelector('.raw');
|
||||
const blame = vm.$el.querySelector('.blame');
|
||||
const history = vm.$el.querySelector('.history');
|
||||
|
||||
expect(raw.href).toMatch(`/${activeFile.rawPath}`);
|
||||
expect(raw.textContent.trim()).toEqual('Raw');
|
||||
expect(blame.href).toMatch(`/${activeFile.blamePath}`);
|
||||
expect(blame.textContent.trim()).toEqual('Blame');
|
||||
expect(history.href).toMatch(`/${activeFile.commitsPath}`);
|
||||
expect(history.textContent.trim()).toEqual('History');
|
||||
expect(vm.$el.querySelector('.permalink').textContent.trim()).toEqual('Permalink');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,98 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import repoFile from '~/ide/components/repo_file.vue';
|
||||
import { file, resetStore } from '../helpers';
|
||||
|
||||
describe('RepoFile', () => {
|
||||
const updated = 'updated';
|
||||
let vm;
|
||||
|
||||
function createComponent(propsData) {
|
||||
const RepoFile = Vue.extend(repoFile);
|
||||
|
||||
return new RepoFile({
|
||||
store,
|
||||
propsData,
|
||||
}).$mount();
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('renders link, icon and name', () => {
|
||||
const RepoFile = Vue.extend(repoFile);
|
||||
vm = new RepoFile({
|
||||
store,
|
||||
propsData: {
|
||||
file: file('t4'),
|
||||
},
|
||||
});
|
||||
spyOn(vm, 'timeFormated').and.returnValue(updated);
|
||||
vm.$mount();
|
||||
|
||||
const name = vm.$el.querySelector('.repo-file-name');
|
||||
|
||||
expect(name.href).toMatch('');
|
||||
expect(name.textContent.trim()).toEqual(vm.file.name);
|
||||
});
|
||||
|
||||
it('does render if hasFiles is true and is loading tree', () => {
|
||||
vm = createComponent({
|
||||
file: file('t1'),
|
||||
});
|
||||
|
||||
expect(vm.$el.querySelector('.fa-spin.fa-spinner')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('does not render commit message and datetime if mini', (done) => {
|
||||
vm = createComponent({
|
||||
file: file('t2'),
|
||||
});
|
||||
vm.$store.state.openFiles.push(vm.file);
|
||||
|
||||
vm.$nextTick(() => {
|
||||
expect(vm.$el.querySelector('.commit-message')).toBeFalsy();
|
||||
expect(vm.$el.querySelector('.commit-update')).toBeFalsy();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fires clickFile when the link is clicked', () => {
|
||||
vm = createComponent({
|
||||
file: file('t3'),
|
||||
});
|
||||
|
||||
spyOn(vm, 'clickFile');
|
||||
|
||||
vm.$el.click();
|
||||
|
||||
expect(vm.clickFile).toHaveBeenCalledWith(vm.file);
|
||||
});
|
||||
|
||||
describe('submodule', () => {
|
||||
let f;
|
||||
|
||||
beforeEach(() => {
|
||||
f = file('submodule name', '123456789');
|
||||
f.type = 'submodule';
|
||||
|
||||
vm = createComponent({
|
||||
file: f,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
it('renders submodule short ID', () => {
|
||||
expect(vm.$el.querySelector('.commit-sha').textContent.trim()).toBe('12345678');
|
||||
});
|
||||
|
||||
it('renders ID next to submodule name', () => {
|
||||
expect(vm.$el.querySelector('td').textContent.replace(/\s+/g, ' ')).toContain('submodule name @ 12345678');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,63 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import repoLoadingFile from '~/ide/components/repo_loading_file.vue';
|
||||
import { resetStore } from '../helpers';
|
||||
|
||||
describe('RepoLoadingFile', () => {
|
||||
let vm;
|
||||
|
||||
function createComponent() {
|
||||
const RepoLoadingFile = Vue.extend(repoLoadingFile);
|
||||
|
||||
return new RepoLoadingFile({
|
||||
store,
|
||||
}).$mount();
|
||||
}
|
||||
|
||||
function assertLines(lines) {
|
||||
lines.forEach((line, n) => {
|
||||
const index = n + 1;
|
||||
expect(line.classList.contains(`skeleton-line-${index}`)).toBeTruthy();
|
||||
});
|
||||
}
|
||||
|
||||
function assertColumns(columns) {
|
||||
columns.forEach((column) => {
|
||||
const container = column.querySelector('.animation-container');
|
||||
const lines = [...container.querySelectorAll(':scope > div')];
|
||||
|
||||
expect(container).toBeTruthy();
|
||||
expect(lines.length).toEqual(6);
|
||||
assertLines(lines);
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('renders 3 columns of animated LoC', () => {
|
||||
vm = createComponent();
|
||||
const columns = [...vm.$el.querySelectorAll('td')];
|
||||
|
||||
expect(columns.length).toEqual(3);
|
||||
assertColumns(columns);
|
||||
});
|
||||
|
||||
it('renders 1 column of animated LoC if isMini', (done) => {
|
||||
vm = createComponent();
|
||||
vm.$store.state.leftPanelCollapsed = true;
|
||||
vm.$store.state.openFiles.push('test');
|
||||
|
||||
vm.$nextTick(() => {
|
||||
const columns = [...vm.$el.querySelectorAll('td')];
|
||||
|
||||
expect(columns.length).toEqual(1);
|
||||
assertColumns(columns);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,45 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import repoPrevDirectory from '~/ide/components/repo_prev_directory.vue';
|
||||
import { resetStore } from '../helpers';
|
||||
|
||||
describe('RepoPrevDirectory', () => {
|
||||
let vm;
|
||||
const parentLink = 'parent';
|
||||
function createComponent() {
|
||||
const RepoPrevDirectory = Vue.extend(repoPrevDirectory);
|
||||
|
||||
const comp = new RepoPrevDirectory({
|
||||
store,
|
||||
});
|
||||
|
||||
comp.$store.state.parentTreeUrl = parentLink;
|
||||
|
||||
return comp.$mount();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vm = createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('renders a prev dir link', () => {
|
||||
const link = vm.$el.querySelector('a');
|
||||
|
||||
expect(link.href).toMatch(`/${parentLink}`);
|
||||
expect(link.textContent).toEqual('...');
|
||||
});
|
||||
|
||||
it('clicking row triggers getTreeData', () => {
|
||||
spyOn(vm, 'getTreeData');
|
||||
|
||||
vm.$el.querySelector('td').click();
|
||||
|
||||
expect(vm.getTreeData).toHaveBeenCalledWith({ endpoint: parentLink });
|
||||
});
|
||||
});
|
|
@ -1,37 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import repoPreview from '~/ide/components/repo_preview.vue';
|
||||
import { file, resetStore } from '../helpers';
|
||||
|
||||
describe('RepoPreview', () => {
|
||||
let vm;
|
||||
|
||||
function createComponent() {
|
||||
const f = file();
|
||||
const RepoPreview = Vue.extend(repoPreview);
|
||||
|
||||
const comp = new RepoPreview({
|
||||
store,
|
||||
});
|
||||
|
||||
f.active = true;
|
||||
f.html = 'test';
|
||||
|
||||
comp.$store.state.openFiles.push(f);
|
||||
|
||||
return comp.$mount();
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('renders a div with the activeFile html', () => {
|
||||
vm = createComponent();
|
||||
|
||||
expect(vm.$el.tagName).toEqual('DIV');
|
||||
expect(vm.$el.innerHTML).toContain('test');
|
||||
});
|
||||
});
|
|
@ -1,108 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import repoTab from '~/ide/components/repo_tab.vue';
|
||||
import { file, resetStore } from '../helpers';
|
||||
|
||||
describe('RepoTab', () => {
|
||||
let vm;
|
||||
|
||||
function createComponent(propsData) {
|
||||
const RepoTab = Vue.extend(repoTab);
|
||||
|
||||
return new RepoTab({
|
||||
store,
|
||||
propsData,
|
||||
}).$mount();
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('renders a close link and a name link', () => {
|
||||
vm = createComponent({
|
||||
tab: file(),
|
||||
});
|
||||
vm.$store.state.openFiles.push(vm.tab);
|
||||
const close = vm.$el.querySelector('.multi-file-tab-close');
|
||||
const name = vm.$el.querySelector(`[title="${vm.tab.url}"]`);
|
||||
|
||||
expect(close.querySelector('.fa-times')).toBeTruthy();
|
||||
expect(name.textContent.trim()).toEqual(vm.tab.name);
|
||||
});
|
||||
|
||||
it('fires clickFile when the link is clicked', () => {
|
||||
vm = createComponent({
|
||||
tab: file(),
|
||||
});
|
||||
|
||||
spyOn(vm, 'clickFile');
|
||||
|
||||
vm.$el.click();
|
||||
|
||||
expect(vm.clickFile).toHaveBeenCalledWith(vm.tab);
|
||||
});
|
||||
|
||||
it('calls closeFile when clicking close button', () => {
|
||||
vm = createComponent({
|
||||
tab: file(),
|
||||
});
|
||||
|
||||
spyOn(vm, 'closeFile');
|
||||
|
||||
vm.$el.querySelector('.multi-file-tab-close').click();
|
||||
|
||||
expect(vm.closeFile).toHaveBeenCalledWith({ file: vm.tab });
|
||||
});
|
||||
|
||||
it('renders an fa-circle icon if tab is changed', () => {
|
||||
const tab = file('changedFile');
|
||||
tab.changed = true;
|
||||
vm = createComponent({
|
||||
tab,
|
||||
});
|
||||
|
||||
expect(vm.$el.querySelector('.multi-file-tab-close .fa-circle')).not.toBeNull();
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
describe('closeTab', () => {
|
||||
it('does not close tab if is changed', (done) => {
|
||||
const tab = file('closeFile');
|
||||
tab.changed = true;
|
||||
tab.opened = true;
|
||||
vm = createComponent({
|
||||
tab,
|
||||
});
|
||||
vm.$store.state.openFiles.push(tab);
|
||||
vm.$store.dispatch('setFileActive', tab);
|
||||
|
||||
vm.$el.querySelector('.multi-file-tab-close').click();
|
||||
|
||||
vm.$nextTick(() => {
|
||||
expect(tab.opened).toBeTruthy();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('closes tab when clicking close btn', (done) => {
|
||||
const tab = file('lose');
|
||||
tab.opened = true;
|
||||
vm = createComponent({
|
||||
tab,
|
||||
});
|
||||
vm.$store.state.openFiles.push(tab);
|
||||
vm.$store.dispatch('setFileActive', tab);
|
||||
|
||||
vm.$el.querySelector('.multi-file-tab-close').click();
|
||||
|
||||
vm.$nextTick(() => {
|
||||
expect(tab.opened).toBeFalsy();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,37 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import repoTabs from '~/ide/components/repo_tabs.vue';
|
||||
import { file, resetStore } from '../helpers';
|
||||
|
||||
describe('RepoTabs', () => {
|
||||
const openedFiles = [file('open1'), file('open2')];
|
||||
let vm;
|
||||
|
||||
function createComponent() {
|
||||
const RepoTabs = Vue.extend(repoTabs);
|
||||
|
||||
return new RepoTabs({
|
||||
store,
|
||||
}).$mount();
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
resetStore(vm.$store);
|
||||
});
|
||||
|
||||
it('renders a list of tabs', (done) => {
|
||||
vm = createComponent();
|
||||
openedFiles[0].active = true;
|
||||
vm.$store.state.openFiles = openedFiles;
|
||||
|
||||
vm.$nextTick(() => {
|
||||
const tabs = [...vm.$el.querySelectorAll('.multi-file-tab')];
|
||||
|
||||
expect(tabs.length).toEqual(2);
|
||||
expect(tabs[0].classList.contains('active')).toBeTruthy();
|
||||
expect(tabs[1].classList.contains('active')).toBeFalsy();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,16 +0,0 @@
|
|||
import { decorateData } from '~/ide/stores/utils';
|
||||
import state from '~/ide/stores/state';
|
||||
|
||||
export const resetStore = (store) => {
|
||||
store.replaceState(state());
|
||||
};
|
||||
|
||||
export const file = (name = 'name', id = name, type = '') => decorateData({
|
||||
id,
|
||||
type,
|
||||
icon: 'icon',
|
||||
url: 'url',
|
||||
name,
|
||||
path: name,
|
||||
lastCommit: {},
|
||||
});
|
|
@ -1,44 +0,0 @@
|
|||
import Disposable from '~/ide/lib/common/disposable';
|
||||
|
||||
describe('Multi-file editor library disposable class', () => {
|
||||
let instance;
|
||||
let disposableClass;
|
||||
|
||||
beforeEach(() => {
|
||||
instance = new Disposable();
|
||||
|
||||
disposableClass = {
|
||||
dispose: jasmine.createSpy('dispose'),
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.dispose();
|
||||
});
|
||||
|
||||
describe('add', () => {
|
||||
it('adds disposable classes', () => {
|
||||
instance.add(disposableClass);
|
||||
|
||||
expect(instance.disposers.size).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dispose', () => {
|
||||
beforeEach(() => {
|
||||
instance.add(disposableClass);
|
||||
});
|
||||
|
||||
it('calls dispose on all cached disposers', () => {
|
||||
instance.dispose();
|
||||
|
||||
expect(disposableClass.dispose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('clears cached disposers', () => {
|
||||
instance.dispose();
|
||||
|
||||
expect(instance.disposers.size).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,81 +0,0 @@
|
|||
/* global monaco */
|
||||
import monacoLoader from '~/ide/monaco_loader';
|
||||
import ModelManager from '~/ide/lib/common/model_manager';
|
||||
import { file } from '../../helpers';
|
||||
|
||||
describe('Multi-file editor library model manager', () => {
|
||||
let instance;
|
||||
|
||||
beforeEach((done) => {
|
||||
monacoLoader(['vs/editor/editor.main'], () => {
|
||||
instance = new ModelManager(monaco);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.dispose();
|
||||
});
|
||||
|
||||
describe('addModel', () => {
|
||||
it('caches model', () => {
|
||||
instance.addModel(file());
|
||||
|
||||
expect(instance.models.size).toBe(1);
|
||||
});
|
||||
|
||||
it('caches model by file path', () => {
|
||||
instance.addModel(file('path-name'));
|
||||
|
||||
expect(instance.models.keys().next().value).toBe('path-name');
|
||||
});
|
||||
|
||||
it('adds model into disposable', () => {
|
||||
spyOn(instance.disposable, 'add').and.callThrough();
|
||||
|
||||
instance.addModel(file());
|
||||
|
||||
expect(instance.disposable.add).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns cached model', () => {
|
||||
spyOn(instance.models, 'get').and.callThrough();
|
||||
|
||||
instance.addModel(file());
|
||||
instance.addModel(file());
|
||||
|
||||
expect(instance.models.get).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasCachedModel', () => {
|
||||
it('returns false when no models exist', () => {
|
||||
expect(instance.hasCachedModel('path')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns true when model exists', () => {
|
||||
instance.addModel(file('path-name'));
|
||||
|
||||
expect(instance.hasCachedModel('path-name')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('dispose', () => {
|
||||
it('clears cached models', () => {
|
||||
instance.addModel(file());
|
||||
|
||||
instance.dispose();
|
||||
|
||||
expect(instance.models.size).toBe(0);
|
||||
});
|
||||
|
||||
it('calls disposable dispose', () => {
|
||||
spyOn(instance.disposable, 'dispose').and.callThrough();
|
||||
|
||||
instance.dispose();
|
||||
|
||||
expect(instance.disposable.dispose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,84 +0,0 @@
|
|||
/* global monaco */
|
||||
import monacoLoader from '~/ide/monaco_loader';
|
||||
import Model from '~/ide/lib/common/model';
|
||||
import { file } from '../../helpers';
|
||||
|
||||
describe('Multi-file editor library model', () => {
|
||||
let model;
|
||||
|
||||
beforeEach((done) => {
|
||||
monacoLoader(['vs/editor/editor.main'], () => {
|
||||
model = new Model(monaco, file('path'));
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
model.dispose();
|
||||
});
|
||||
|
||||
it('creates original model & new model', () => {
|
||||
expect(model.originalModel).not.toBeNull();
|
||||
expect(model.model).not.toBeNull();
|
||||
});
|
||||
|
||||
describe('path', () => {
|
||||
it('returns file path', () => {
|
||||
expect(model.path).toBe('path');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getModel', () => {
|
||||
it('returns model', () => {
|
||||
expect(model.getModel()).toBe(model.model);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOriginalModel', () => {
|
||||
it('returns original model', () => {
|
||||
expect(model.getOriginalModel()).toBe(model.originalModel);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onChange', () => {
|
||||
it('caches event by path', () => {
|
||||
model.onChange(() => {});
|
||||
|
||||
expect(model.events.size).toBe(1);
|
||||
expect(model.events.keys().next().value).toBe('path');
|
||||
});
|
||||
|
||||
it('calls callback on change', (done) => {
|
||||
const spy = jasmine.createSpy();
|
||||
model.onChange(spy);
|
||||
|
||||
model.getModel().setValue('123');
|
||||
|
||||
setTimeout(() => {
|
||||
expect(spy).toHaveBeenCalledWith(model.getModel(), jasmine.anything());
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('dispose', () => {
|
||||
it('calls disposable dispose', () => {
|
||||
spyOn(model.disposable, 'dispose').and.callThrough();
|
||||
|
||||
model.dispose();
|
||||
|
||||
expect(model.disposable.dispose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('clears events', () => {
|
||||
model.onChange(() => {});
|
||||
|
||||
expect(model.events.size).toBe(1);
|
||||
|
||||
model.dispose();
|
||||
|
||||
expect(model.events.size).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,120 +0,0 @@
|
|||
/* global monaco */
|
||||
import monacoLoader from '~/ide/monaco_loader';
|
||||
import editor from '~/ide/lib/editor';
|
||||
import DecorationsController from '~/ide/lib/decorations/controller';
|
||||
import Model from '~/ide/lib/common/model';
|
||||
import { file } from '../../helpers';
|
||||
|
||||
describe('Multi-file editor library decorations controller', () => {
|
||||
let editorInstance;
|
||||
let controller;
|
||||
let model;
|
||||
|
||||
beforeEach((done) => {
|
||||
monacoLoader(['vs/editor/editor.main'], () => {
|
||||
editorInstance = editor.create(monaco);
|
||||
editorInstance.createInstance(document.createElement('div'));
|
||||
|
||||
controller = new DecorationsController(editorInstance);
|
||||
model = new Model(monaco, file('path'));
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
model.dispose();
|
||||
editorInstance.dispose();
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
describe('getAllDecorationsForModel', () => {
|
||||
it('returns empty array when no decorations exist for model', () => {
|
||||
const decorations = controller.getAllDecorationsForModel(model);
|
||||
|
||||
expect(decorations).toEqual([]);
|
||||
});
|
||||
|
||||
it('returns decorations by model URL', () => {
|
||||
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
|
||||
|
||||
const decorations = controller.getAllDecorationsForModel(model);
|
||||
|
||||
expect(decorations[0]).toEqual({ decoration: 'decorationValue' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('addDecorations', () => {
|
||||
it('caches decorations in a new map', () => {
|
||||
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' }]);
|
||||
|
||||
expect(controller.decorations.size).toBe(1);
|
||||
});
|
||||
|
||||
it('caches decorations by model URL', () => {
|
||||
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
|
||||
|
||||
expect(controller.decorations.size).toBe(1);
|
||||
expect(controller.decorations.keys().next().value).toBe('path');
|
||||
});
|
||||
|
||||
it('calls decorate method', () => {
|
||||
spyOn(controller, 'decorate');
|
||||
|
||||
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
|
||||
|
||||
expect(controller.decorate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('decorate', () => {
|
||||
it('sets decorations on editor instance', () => {
|
||||
spyOn(controller.editor.instance, 'deltaDecorations');
|
||||
|
||||
controller.decorate(model);
|
||||
|
||||
expect(controller.editor.instance.deltaDecorations).toHaveBeenCalledWith([], []);
|
||||
});
|
||||
|
||||
it('caches decorations', () => {
|
||||
spyOn(controller.editor.instance, 'deltaDecorations').and.returnValue([]);
|
||||
|
||||
controller.decorate(model);
|
||||
|
||||
expect(controller.editorDecorations.size).toBe(1);
|
||||
});
|
||||
|
||||
it('caches decorations by model URL', () => {
|
||||
spyOn(controller.editor.instance, 'deltaDecorations').and.returnValue([]);
|
||||
|
||||
controller.decorate(model);
|
||||
|
||||
expect(controller.editorDecorations.keys().next().value).toBe('path');
|
||||
});
|
||||
});
|
||||
|
||||
describe('dispose', () => {
|
||||
it('clears cached decorations', () => {
|
||||
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
|
||||
|
||||
controller.dispose();
|
||||
|
||||
expect(controller.decorations.size).toBe(0);
|
||||
});
|
||||
|
||||
it('clears cached editorDecorations', () => {
|
||||
controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
|
||||
|
||||
controller.dispose();
|
||||
|
||||
expect(controller.editorDecorations.size).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,176 +0,0 @@
|
|||
/* global monaco */
|
||||
import monacoLoader from '~/ide/monaco_loader';
|
||||
import editor from '~/ide/lib/editor';
|
||||
import ModelManager from '~/ide/lib/common/model_manager';
|
||||
import DecorationsController from '~/ide/lib/decorations/controller';
|
||||
import DirtyDiffController, { getDiffChangeType, getDecorator } from '~/ide/lib/diff/controller';
|
||||
import { computeDiff } from '~/ide/lib/diff/diff';
|
||||
import { file } from '../../helpers';
|
||||
|
||||
describe('Multi-file editor library dirty diff controller', () => {
|
||||
let editorInstance;
|
||||
let controller;
|
||||
let modelManager;
|
||||
let decorationsController;
|
||||
let model;
|
||||
|
||||
beforeEach((done) => {
|
||||
monacoLoader(['vs/editor/editor.main'], () => {
|
||||
editorInstance = editor.create(monaco);
|
||||
editorInstance.createInstance(document.createElement('div'));
|
||||
|
||||
modelManager = new ModelManager(monaco);
|
||||
decorationsController = new DecorationsController(editorInstance);
|
||||
|
||||
model = modelManager.addModel(file());
|
||||
|
||||
controller = new DirtyDiffController(modelManager, decorationsController);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
controller.dispose();
|
||||
model.dispose();
|
||||
decorationsController.dispose();
|
||||
editorInstance.dispose();
|
||||
});
|
||||
|
||||
describe('getDiffChangeType', () => {
|
||||
['added', 'removed', 'modified'].forEach((type) => {
|
||||
it(`returns ${type}`, () => {
|
||||
const change = {
|
||||
[type]: true,
|
||||
};
|
||||
|
||||
expect(getDiffChangeType(change)).toBe(type);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDecorator', () => {
|
||||
['added', 'removed', 'modified'].forEach((type) => {
|
||||
it(`returns with linesDecorationsClassName for ${type}`, () => {
|
||||
const change = {
|
||||
[type]: true,
|
||||
};
|
||||
|
||||
expect(
|
||||
getDecorator(change).options.linesDecorationsClassName,
|
||||
).toBe(`dirty-diff dirty-diff-${type}`);
|
||||
});
|
||||
|
||||
it('returns with line numbers', () => {
|
||||
const change = {
|
||||
lineNumber: 1,
|
||||
endLineNumber: 2,
|
||||
[type]: true,
|
||||
};
|
||||
|
||||
const range = getDecorator(change).range;
|
||||
|
||||
expect(range.startLineNumber).toBe(1);
|
||||
expect(range.endLineNumber).toBe(2);
|
||||
expect(range.startColumn).toBe(1);
|
||||
expect(range.endColumn).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('attachModel', () => {
|
||||
it('adds change event callback', () => {
|
||||
spyOn(model, 'onChange');
|
||||
|
||||
controller.attachModel(model);
|
||||
|
||||
expect(model.onChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls throttledComputeDiff on change', () => {
|
||||
spyOn(controller, 'throttledComputeDiff');
|
||||
|
||||
controller.attachModel(model);
|
||||
|
||||
model.getModel().setValue('123');
|
||||
|
||||
expect(controller.throttledComputeDiff).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeDiff', () => {
|
||||
it('posts to worker', () => {
|
||||
spyOn(controller.dirtyDiffWorker, 'postMessage');
|
||||
|
||||
controller.computeDiff(model);
|
||||
|
||||
expect(controller.dirtyDiffWorker.postMessage).toHaveBeenCalledWith({
|
||||
path: model.path,
|
||||
originalContent: '',
|
||||
newContent: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('reDecorate', () => {
|
||||
it('calls decorations controller decorate', () => {
|
||||
spyOn(controller.decorationsController, 'decorate');
|
||||
|
||||
controller.reDecorate(model);
|
||||
|
||||
expect(controller.decorationsController.decorate).toHaveBeenCalledWith(model);
|
||||
});
|
||||
});
|
||||
|
||||
describe('decorate', () => {
|
||||
it('adds decorations into decorations controller', () => {
|
||||
spyOn(controller.decorationsController, 'addDecorations');
|
||||
|
||||
controller.decorate({ data: { changes: [], path: 'path' } });
|
||||
|
||||
expect(controller.decorationsController.addDecorations).toHaveBeenCalledWith('path', 'dirtyDiff', jasmine.anything());
|
||||
});
|
||||
|
||||
it('adds decorations into editor', () => {
|
||||
const spy = spyOn(controller.decorationsController.editor.instance, 'deltaDecorations');
|
||||
|
||||
controller.decorate({ data: { changes: computeDiff('123', '1234'), path: 'path' } });
|
||||
|
||||
expect(spy).toHaveBeenCalledWith([], [{
|
||||
range: new monaco.Range(
|
||||
1, 1, 1, 1,
|
||||
),
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
linesDecorationsClassName: 'dirty-diff dirty-diff-modified',
|
||||
},
|
||||
}]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dispose', () => {
|
||||
it('calls disposable dispose', () => {
|
||||
spyOn(controller.disposable, 'dispose').and.callThrough();
|
||||
|
||||
controller.dispose();
|
||||
|
||||
expect(controller.disposable.dispose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('terminates worker', () => {
|
||||
spyOn(controller.dirtyDiffWorker, 'terminate').and.callThrough();
|
||||
|
||||
controller.dispose();
|
||||
|
||||
expect(controller.dirtyDiffWorker.terminate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('removes worker event listener', () => {
|
||||
spyOn(controller.dirtyDiffWorker, 'removeEventListener').and.callThrough();
|
||||
|
||||
controller.dispose();
|
||||
|
||||
expect(controller.dirtyDiffWorker.removeEventListener).toHaveBeenCalledWith('message', jasmine.anything());
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,80 +0,0 @@
|
|||
import { computeDiff } from '~/ide/lib/diff/diff';
|
||||
|
||||
describe('Multi-file editor library diff calculator', () => {
|
||||
describe('computeDiff', () => {
|
||||
it('returns empty array if no changes', () => {
|
||||
const diff = computeDiff('123', '123');
|
||||
|
||||
expect(diff).toEqual([]);
|
||||
});
|
||||
|
||||
describe('modified', () => {
|
||||
it('', () => {
|
||||
const diff = computeDiff('123', '1234')[0];
|
||||
|
||||
expect(diff.added).toBeTruthy();
|
||||
expect(diff.modified).toBeTruthy();
|
||||
expect(diff.removed).toBeUndefined();
|
||||
});
|
||||
|
||||
it('', () => {
|
||||
const diff = computeDiff('123\n123\n123', '123\n1234\n123')[0];
|
||||
|
||||
expect(diff.added).toBeTruthy();
|
||||
expect(diff.modified).toBeTruthy();
|
||||
expect(diff.removed).toBeUndefined();
|
||||
expect(diff.lineNumber).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('added', () => {
|
||||
it('', () => {
|
||||
const diff = computeDiff('123', '123\n123')[0];
|
||||
|
||||
expect(diff.added).toBeTruthy();
|
||||
expect(diff.modified).toBeUndefined();
|
||||
expect(diff.removed).toBeUndefined();
|
||||
});
|
||||
|
||||
it('', () => {
|
||||
const diff = computeDiff('123\n123\n123', '123\n123\n1234\n123')[0];
|
||||
|
||||
expect(diff.added).toBeTruthy();
|
||||
expect(diff.modified).toBeUndefined();
|
||||
expect(diff.removed).toBeUndefined();
|
||||
expect(diff.lineNumber).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removed', () => {
|
||||
it('', () => {
|
||||
const diff = computeDiff('123', '')[0];
|
||||
|
||||
expect(diff.added).toBeUndefined();
|
||||
expect(diff.modified).toBeUndefined();
|
||||
expect(diff.removed).toBeTruthy();
|
||||
});
|
||||
|
||||
it('', () => {
|
||||
const diff = computeDiff('123\n123\n123', '123\n123')[0];
|
||||
|
||||
expect(diff.added).toBeUndefined();
|
||||
expect(diff.modified).toBeTruthy();
|
||||
expect(diff.removed).toBeTruthy();
|
||||
expect(diff.lineNumber).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('includes line number of change', () => {
|
||||
const diff = computeDiff('123', '')[0];
|
||||
|
||||
expect(diff.lineNumber).toBe(1);
|
||||
});
|
||||
|
||||
it('includes end line number of change', () => {
|
||||
const diff = computeDiff('123', '')[0];
|
||||
|
||||
expect(diff.endLineNumber).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,7 +0,0 @@
|
|||
import editorOptions from '~/ide/lib/editor_options';
|
||||
|
||||
describe('Multi-file editor library editor options', () => {
|
||||
it('returns an array', () => {
|
||||
expect(editorOptions).toEqual(jasmine.any(Array));
|
||||
});
|
||||
});
|
|
@ -1,128 +0,0 @@
|
|||
/* global monaco */
|
||||
import monacoLoader from '~/ide/monaco_loader';
|
||||
import editor from '~/ide/lib/editor';
|
||||
import { file } from '../helpers';
|
||||
|
||||
describe('Multi-file editor library', () => {
|
||||
let instance;
|
||||
|
||||
beforeEach((done) => {
|
||||
monacoLoader(['vs/editor/editor.main'], () => {
|
||||
instance = editor.create(monaco);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
instance.dispose();
|
||||
});
|
||||
|
||||
it('creates instance of editor', () => {
|
||||
expect(editor.editorInstance).not.toBeNull();
|
||||
});
|
||||
|
||||
describe('createInstance', () => {
|
||||
let el;
|
||||
|
||||
beforeEach(() => {
|
||||
el = document.createElement('div');
|
||||
});
|
||||
|
||||
it('creates editor instance', () => {
|
||||
spyOn(instance.monaco.editor, 'create').and.callThrough();
|
||||
|
||||
instance.createInstance(el);
|
||||
|
||||
expect(instance.monaco.editor.create).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates dirty diff controller', () => {
|
||||
instance.createInstance(el);
|
||||
|
||||
expect(instance.dirtyDiffController).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('createModel', () => {
|
||||
it('calls model manager addModel', () => {
|
||||
spyOn(instance.modelManager, 'addModel');
|
||||
|
||||
instance.createModel('FILE');
|
||||
|
||||
expect(instance.modelManager.addModel).toHaveBeenCalledWith('FILE');
|
||||
});
|
||||
});
|
||||
|
||||
describe('attachModel', () => {
|
||||
let model;
|
||||
|
||||
beforeEach(() => {
|
||||
instance.createInstance(document.createElement('div'));
|
||||
|
||||
model = instance.createModel(file());
|
||||
});
|
||||
|
||||
it('sets the current model on the instance', () => {
|
||||
instance.attachModel(model);
|
||||
|
||||
expect(instance.currentModel).toBe(model);
|
||||
});
|
||||
|
||||
it('attaches the model to the current instance', () => {
|
||||
spyOn(instance.instance, 'setModel');
|
||||
|
||||
instance.attachModel(model);
|
||||
|
||||
expect(instance.instance.setModel).toHaveBeenCalledWith(model.getModel());
|
||||
});
|
||||
|
||||
it('attaches the model to the dirty diff controller', () => {
|
||||
spyOn(instance.dirtyDiffController, 'attachModel');
|
||||
|
||||
instance.attachModel(model);
|
||||
|
||||
expect(instance.dirtyDiffController.attachModel).toHaveBeenCalledWith(model);
|
||||
});
|
||||
|
||||
it('re-decorates with the dirty diff controller', () => {
|
||||
spyOn(instance.dirtyDiffController, 'reDecorate');
|
||||
|
||||
instance.attachModel(model);
|
||||
|
||||
expect(instance.dirtyDiffController.reDecorate).toHaveBeenCalledWith(model);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearEditor', () => {
|
||||
it('resets the editor model', () => {
|
||||
instance.createInstance(document.createElement('div'));
|
||||
|
||||
spyOn(instance.instance, 'setModel');
|
||||
|
||||
instance.clearEditor();
|
||||
|
||||
expect(instance.instance.setModel).toHaveBeenCalledWith(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dispose', () => {
|
||||
it('calls disposble dispose method', () => {
|
||||
spyOn(instance.disposable, 'dispose').and.callThrough();
|
||||
|
||||
instance.dispose();
|
||||
|
||||
expect(instance.disposable.dispose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('resets instance', () => {
|
||||
instance.createInstance(document.createElement('div'));
|
||||
|
||||
expect(instance.instance).not.toBeNull();
|
||||
|
||||
instance.dispose();
|
||||
|
||||
expect(instance.instance).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,13 +0,0 @@
|
|||
import monacoContext from 'monaco-editor/dev/vs/loader';
|
||||
import monacoLoader from '~/ide/monaco_loader';
|
||||
|
||||
describe('MonacoLoader', () => {
|
||||
it('calls require.config and exports require', () => {
|
||||
expect(monacoContext.require.getConfig()).toEqual(jasmine.objectContaining({
|
||||
paths: {
|
||||
vs: `${__webpack_public_path__}monaco-editor/vs`, // eslint-disable-line camelcase
|
||||
},
|
||||
}));
|
||||
expect(monacoLoader).toBe(monacoContext.require);
|
||||
});
|
||||
});
|
|
@ -1,44 +0,0 @@
|
|||
import store from '~/ide/stores';
|
||||
import service from '~/ide/services';
|
||||
import { resetStore } from '../../helpers';
|
||||
|
||||
describe('Multi-file store branch actions', () => {
|
||||
afterEach(() => {
|
||||
resetStore(store);
|
||||
});
|
||||
|
||||
describe('createNewBranch', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(service, 'createBranch').and.returnValue(Promise.resolve({
|
||||
json: () => ({
|
||||
name: 'testing',
|
||||
}),
|
||||
}));
|
||||
spyOn(history, 'pushState');
|
||||
|
||||
store.state.currentProjectId = 'abcproject';
|
||||
store.state.currentBranchId = 'testing';
|
||||
store.state.projects.abcproject = {
|
||||
branches: {
|
||||
master: {
|
||||
workingReference: '1',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('creates new branch', (done) => {
|
||||
store.dispatch('createNewBranch', 'master')
|
||||
.then(() => {
|
||||
expect(store.state.currentBranchId).toBe('testing');
|
||||
expect(service.createBranch).toHaveBeenCalledWith('abcproject', {
|
||||
branch: 'master',
|
||||
ref: 'testing',
|
||||
});
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,431 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import service from '~/ide/services';
|
||||
import { file, resetStore } from '../../helpers';
|
||||
|
||||
describe('Multi-file store file actions', () => {
|
||||
afterEach(() => {
|
||||
resetStore(store);
|
||||
});
|
||||
|
||||
describe('closeFile', () => {
|
||||
let localFile;
|
||||
let getLastCommitDataSpy;
|
||||
let oldGetLastCommitData;
|
||||
|
||||
beforeEach(() => {
|
||||
getLastCommitDataSpy = jasmine.createSpy('getLastCommitData');
|
||||
oldGetLastCommitData = store._actions.getLastCommitData; // eslint-disable-line
|
||||
store._actions.getLastCommitData = [getLastCommitDataSpy]; // eslint-disable-line
|
||||
|
||||
localFile = file('testFile');
|
||||
localFile.active = true;
|
||||
localFile.opened = true;
|
||||
localFile.parentTreeUrl = 'parentTreeUrl';
|
||||
|
||||
store.state.openFiles.push(localFile);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store._actions.getLastCommitData = oldGetLastCommitData; // eslint-disable-line
|
||||
});
|
||||
|
||||
it('closes open files', (done) => {
|
||||
store.dispatch('closeFile', { file: localFile })
|
||||
.then(() => {
|
||||
expect(localFile.opened).toBeFalsy();
|
||||
expect(localFile.active).toBeFalsy();
|
||||
expect(store.state.openFiles.length).toBe(0);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('does not close file if has changed', (done) => {
|
||||
localFile.changed = true;
|
||||
|
||||
store.dispatch('closeFile', { file: localFile })
|
||||
.then(() => {
|
||||
expect(localFile.opened).toBeTruthy();
|
||||
expect(localFile.active).toBeTruthy();
|
||||
expect(store.state.openFiles.length).toBe(1);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('does not close file if temp file', (done) => {
|
||||
localFile.tempFile = true;
|
||||
|
||||
store.dispatch('closeFile', { file: localFile })
|
||||
.then(() => {
|
||||
expect(localFile.opened).toBeTruthy();
|
||||
expect(localFile.active).toBeTruthy();
|
||||
expect(store.state.openFiles.length).toBe(1);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('force closes a changed file', (done) => {
|
||||
localFile.changed = true;
|
||||
|
||||
store.dispatch('closeFile', { file: localFile, force: true })
|
||||
.then(() => {
|
||||
expect(localFile.opened).toBeFalsy();
|
||||
expect(localFile.active).toBeFalsy();
|
||||
expect(store.state.openFiles.length).toBe(0);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets next file as active', (done) => {
|
||||
const f = file('otherfile');
|
||||
store.state.openFiles.push(f);
|
||||
|
||||
expect(f.active).toBeFalsy();
|
||||
|
||||
store.dispatch('closeFile', { file: localFile })
|
||||
.then(() => {
|
||||
expect(f.active).toBeTruthy();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('calls getLastCommitData', (done) => {
|
||||
store.dispatch('closeFile', { file: localFile })
|
||||
.then(() => {
|
||||
expect(getLastCommitDataSpy).toHaveBeenCalled();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setFileActive', () => {
|
||||
let scrollToTabSpy;
|
||||
let oldScrollToTab;
|
||||
|
||||
beforeEach(() => {
|
||||
scrollToTabSpy = jasmine.createSpy('scrollToTab');
|
||||
oldScrollToTab = store._actions.scrollToTab; // eslint-disable-line
|
||||
store._actions.scrollToTab = [scrollToTabSpy]; // eslint-disable-line
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line
|
||||
});
|
||||
|
||||
it('calls scrollToTab', (done) => {
|
||||
store.dispatch('setFileActive', file('setThisActive'))
|
||||
.then(() => {
|
||||
expect(scrollToTabSpy).toHaveBeenCalled();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets the file active', (done) => {
|
||||
const localFile = file('activeFile');
|
||||
|
||||
store.dispatch('setFileActive', localFile)
|
||||
.then(() => {
|
||||
expect(localFile.active).toBeTruthy();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('returns early if file is already active', (done) => {
|
||||
const localFile = file('earlyActive');
|
||||
localFile.active = true;
|
||||
|
||||
store.dispatch('setFileActive', localFile)
|
||||
.then(() => {
|
||||
expect(scrollToTabSpy).not.toHaveBeenCalled();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets current active file to not active', (done) => {
|
||||
const localFile = file('currentActive');
|
||||
localFile.active = true;
|
||||
store.state.openFiles.push(localFile);
|
||||
|
||||
store.dispatch('setFileActive', file('newActive'))
|
||||
.then(() => {
|
||||
expect(localFile.active).toBeFalsy();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('resets location.hash for line highlighting', (done) => {
|
||||
location.hash = 'test';
|
||||
|
||||
store.dispatch('setFileActive', file('otherActive'))
|
||||
.then(() => {
|
||||
expect(location.hash).not.toBe('test');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFileData', () => {
|
||||
let localFile;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(service, 'getFileData').and.returnValue(Promise.resolve({
|
||||
headers: {
|
||||
'page-title': 'testing getFileData',
|
||||
},
|
||||
json: () => Promise.resolve({
|
||||
blame_path: 'blame_path',
|
||||
commits_path: 'commits_path',
|
||||
permalink: 'permalink',
|
||||
raw_path: 'raw_path',
|
||||
binary: false,
|
||||
html: '123',
|
||||
render_error: '',
|
||||
}),
|
||||
}));
|
||||
|
||||
localFile = file('newCreate');
|
||||
localFile.url = 'getFileDataURL';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store.dispatch('closeFile', {
|
||||
file: localFile,
|
||||
force: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('calls the service', (done) => {
|
||||
store.dispatch('getFileData', localFile)
|
||||
.then(() => {
|
||||
expect(service.getFileData).toHaveBeenCalledWith('getFileDataURL');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets the file data', (done) => {
|
||||
store.dispatch('getFileData', localFile)
|
||||
.then(Vue.nextTick)
|
||||
.then(() => {
|
||||
expect(localFile.blamePath).toBe('blame_path');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets document title', (done) => {
|
||||
store.dispatch('getFileData', localFile)
|
||||
.then(() => {
|
||||
expect(document.title).toBe('testing getFileData');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets the file as active', (done) => {
|
||||
store.dispatch('getFileData', localFile)
|
||||
.then(Vue.nextTick)
|
||||
.then(() => {
|
||||
expect(localFile.active).toBeTruthy();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('adds the file to open files', (done) => {
|
||||
store.dispatch('getFileData', localFile)
|
||||
.then(Vue.nextTick)
|
||||
.then(() => {
|
||||
expect(store.state.openFiles.length).toBe(1);
|
||||
expect(store.state.openFiles[0].name).toBe(localFile.name);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('toggles the file loading', (done) => {
|
||||
store.dispatch('getFileData', localFile)
|
||||
.then(() => {
|
||||
expect(localFile.loading).toBeTruthy();
|
||||
|
||||
return Vue.nextTick();
|
||||
})
|
||||
.then(() => {
|
||||
expect(localFile.loading).toBeFalsy();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRawFileData', () => {
|
||||
let tmpFile;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(service, 'getRawFileData').and.returnValue(Promise.resolve('raw'));
|
||||
|
||||
tmpFile = file('tmpFile');
|
||||
});
|
||||
|
||||
it('calls getRawFileData service method', (done) => {
|
||||
store.dispatch('getRawFileData', tmpFile)
|
||||
.then(() => {
|
||||
expect(service.getRawFileData).toHaveBeenCalledWith(tmpFile);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('updates file raw data', (done) => {
|
||||
store.dispatch('getRawFileData', tmpFile)
|
||||
.then(() => {
|
||||
expect(tmpFile.raw).toBe('raw');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('changeFileContent', () => {
|
||||
let tmpFile;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpFile = file('tmpFile');
|
||||
});
|
||||
|
||||
it('updates file content', (done) => {
|
||||
store.dispatch('changeFileContent', {
|
||||
file: tmpFile,
|
||||
content: 'content',
|
||||
})
|
||||
.then(() => {
|
||||
expect(tmpFile.content).toBe('content');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createTempFile', () => {
|
||||
let projectTree;
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML += '<div class="flash-container"></div>';
|
||||
|
||||
store.state.currentProjectId = 'abcproject';
|
||||
store.state.currentBranchId = 'master';
|
||||
store.state.projects.abcproject = {
|
||||
branches: {
|
||||
master: {
|
||||
workingReference: '1',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
store.state.trees['abcproject/mybranch'] = {
|
||||
tree: [],
|
||||
};
|
||||
|
||||
projectTree = store.state.trees['abcproject/mybranch'];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.querySelector('.flash-container').remove();
|
||||
});
|
||||
|
||||
it('creates temp file', (done) => {
|
||||
store.dispatch('createTempFile', {
|
||||
name: 'test',
|
||||
projectId: 'abcproject',
|
||||
branchId: 'mybranch',
|
||||
parent: projectTree,
|
||||
}).then((f) => {
|
||||
expect(f.tempFile).toBeTruthy();
|
||||
expect(store.state.trees['abcproject/mybranch'].tree.length).toBe(1);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('adds tmp file to open files', (done) => {
|
||||
store.dispatch('createTempFile', {
|
||||
name: 'test',
|
||||
projectId: 'abcproject',
|
||||
branchId: 'mybranch',
|
||||
parent: projectTree,
|
||||
}).then((f) => {
|
||||
expect(store.state.openFiles.length).toBe(1);
|
||||
expect(store.state.openFiles[0].name).toBe(f.name);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets tmp file as active', (done) => {
|
||||
store.dispatch('createTempFile', {
|
||||
name: 'test',
|
||||
projectId: 'abcproject',
|
||||
branchId: 'mybranch',
|
||||
parent: projectTree,
|
||||
}).then((f) => {
|
||||
expect(f.active).toBeTruthy();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('enters edit mode if file is not base64', (done) => {
|
||||
store.dispatch('createTempFile', {
|
||||
name: 'test',
|
||||
projectId: 'abcproject',
|
||||
branchId: 'mybranch',
|
||||
parent: projectTree,
|
||||
}).then(() => {
|
||||
expect(store.state.editMode).toBeTruthy();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('creates flash message is file already exists', (done) => {
|
||||
store.state.trees['abcproject/mybranch'].tree.push(file('test', '1', 'blob'));
|
||||
|
||||
store.dispatch('createTempFile', {
|
||||
name: 'test',
|
||||
projectId: 'abcproject',
|
||||
branchId: 'mybranch',
|
||||
parent: projectTree,
|
||||
}).then(() => {
|
||||
expect(document.querySelector('.flash-alert')).not.toBeNull();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('increases level of file', (done) => {
|
||||
store.state.trees['abcproject/mybranch'].level = 1;
|
||||
|
||||
store.dispatch('createTempFile', {
|
||||
name: 'test',
|
||||
projectId: 'abcproject',
|
||||
branchId: 'mybranch',
|
||||
parent: projectTree,
|
||||
}).then((f) => {
|
||||
expect(f.level).toBe(2);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,350 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import store from '~/ide/stores';
|
||||
import service from '~/ide/services';
|
||||
import { file, resetStore } from '../../helpers';
|
||||
|
||||
describe('Multi-file store tree actions', () => {
|
||||
let projectTree;
|
||||
|
||||
const basicCallParameters = {
|
||||
endpoint: 'rootEndpoint',
|
||||
projectId: 'abcproject',
|
||||
branch: 'master',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
store.state.currentProjectId = 'abcproject';
|
||||
store.state.currentBranchId = 'master';
|
||||
store.state.projects.abcproject = {
|
||||
web_url: '',
|
||||
branches: {
|
||||
master: {
|
||||
workingReference: '1',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetStore(store);
|
||||
});
|
||||
|
||||
describe('getTreeData', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
|
||||
headers: {
|
||||
'page-title': 'test',
|
||||
},
|
||||
json: () => Promise.resolve({
|
||||
last_commit_path: 'last_commit_path',
|
||||
parent_tree_url: 'parent_tree_url',
|
||||
path: '/',
|
||||
trees: [{ name: 'tree' }],
|
||||
blobs: [{ name: 'blob' }],
|
||||
submodules: [{ name: 'submodule' }],
|
||||
}),
|
||||
}));
|
||||
});
|
||||
|
||||
it('calls service getTreeData', (done) => {
|
||||
store.dispatch('getTreeData', basicCallParameters)
|
||||
.then(() => {
|
||||
expect(service.getTreeData).toHaveBeenCalledWith('rootEndpoint');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('adds data into tree', (done) => {
|
||||
store.dispatch('getTreeData', basicCallParameters)
|
||||
.then(() => {
|
||||
projectTree = store.state.trees['abcproject/master'];
|
||||
expect(projectTree.tree.length).toBe(3);
|
||||
expect(projectTree.tree[0].type).toBe('tree');
|
||||
expect(projectTree.tree[1].type).toBe('submodule');
|
||||
expect(projectTree.tree[2].type).toBe('blob');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets parent tree URL', (done) => {
|
||||
store.dispatch('getTreeData', basicCallParameters)
|
||||
.then(() => {
|
||||
expect(store.state.parentTreeUrl).toBe('parent_tree_url');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets last commit path', (done) => {
|
||||
store.dispatch('getTreeData', basicCallParameters)
|
||||
.then(() => {
|
||||
expect(store.state.trees['abcproject/master'].lastCommitPath).toBe('last_commit_path');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets root if not currently at root', (done) => {
|
||||
store.state.isInitialRoot = false;
|
||||
|
||||
store.dispatch('getTreeData', basicCallParameters)
|
||||
.then(() => {
|
||||
expect(store.state.isInitialRoot).toBeTruthy();
|
||||
expect(store.state.isRoot).toBeTruthy();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets page title', (done) => {
|
||||
store.dispatch('getTreeData', basicCallParameters)
|
||||
.then(() => {
|
||||
expect(document.title).toBe('test');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('calls getLastCommitData if prevLastCommitPath is not null', (done) => {
|
||||
const getLastCommitDataSpy = jasmine.createSpy('getLastCommitData');
|
||||
const oldGetLastCommitData = store._actions.getLastCommitData; // eslint-disable-line
|
||||
store._actions.getLastCommitData = [getLastCommitDataSpy]; // eslint-disable-line
|
||||
store.state.prevLastCommitPath = 'test';
|
||||
|
||||
store.dispatch('getTreeData', basicCallParameters)
|
||||
.then(() => {
|
||||
expect(getLastCommitDataSpy).toHaveBeenCalledWith(projectTree);
|
||||
|
||||
store._actions.getLastCommitData = oldGetLastCommitData; // eslint-disable-line
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleTreeOpen', () => {
|
||||
let oldGetTreeData;
|
||||
let getTreeDataSpy;
|
||||
let tree;
|
||||
|
||||
beforeEach(() => {
|
||||
getTreeDataSpy = jasmine.createSpy('getTreeData');
|
||||
|
||||
oldGetTreeData = store._actions.getTreeData; // eslint-disable-line
|
||||
store._actions.getTreeData = [getTreeDataSpy]; // eslint-disable-line
|
||||
|
||||
tree = {
|
||||
projectId: 'abcproject',
|
||||
branchId: 'master',
|
||||
opened: false,
|
||||
tree: [],
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
store._actions.getTreeData = oldGetTreeData; // eslint-disable-line
|
||||
});
|
||||
|
||||
it('toggles the tree open', (done) => {
|
||||
store.dispatch('toggleTreeOpen', {
|
||||
endpoint: 'test',
|
||||
tree,
|
||||
}).then(() => {
|
||||
expect(tree.opened).toBeTruthy();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('calls getTreeData if tree is closed', (done) => {
|
||||
store.dispatch('toggleTreeOpen', {
|
||||
endpoint: 'test',
|
||||
tree,
|
||||
}).then(() => {
|
||||
expect(getTreeDataSpy).toHaveBeenCalledWith({
|
||||
projectId: 'abcproject',
|
||||
branch: 'master',
|
||||
endpoint: 'test',
|
||||
tree,
|
||||
});
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('resets entries tree', (done) => {
|
||||
Object.assign(tree, {
|
||||
opened: true,
|
||||
tree: ['a'],
|
||||
});
|
||||
|
||||
store.dispatch('toggleTreeOpen', {
|
||||
endpoint: 'test',
|
||||
tree,
|
||||
}).then(() => {
|
||||
expect(tree.tree.length).toBe(0);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createTempTree', () => {
|
||||
beforeEach(() => {
|
||||
store.state.trees['abcproject/mybranch'] = {
|
||||
tree: [],
|
||||
};
|
||||
projectTree = store.state.trees['abcproject/mybranch'];
|
||||
});
|
||||
|
||||
it('creates temp tree', (done) => {
|
||||
store.dispatch('createTempTree', {
|
||||
projectId: store.state.currentProjectId,
|
||||
branchId: store.state.currentBranchId,
|
||||
name: 'test',
|
||||
parent: projectTree,
|
||||
})
|
||||
.then(() => {
|
||||
expect(projectTree.tree[0].name).toBe('test');
|
||||
expect(projectTree.tree[0].type).toBe('tree');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('creates new folder inside another tree', (done) => {
|
||||
const tree = {
|
||||
type: 'tree',
|
||||
name: 'testing',
|
||||
tree: [],
|
||||
};
|
||||
|
||||
projectTree.tree.push(tree);
|
||||
|
||||
store.dispatch('createTempTree', {
|
||||
projectId: store.state.currentProjectId,
|
||||
branchId: store.state.currentBranchId,
|
||||
name: 'testing/test',
|
||||
parent: projectTree,
|
||||
})
|
||||
.then(() => {
|
||||
expect(projectTree.tree[0].name).toBe('testing');
|
||||
expect(projectTree.tree[0].tree[0].tempFile).toBeTruthy();
|
||||
expect(projectTree.tree[0].tree[0].name).toBe('test');
|
||||
expect(projectTree.tree[0].tree[0].type).toBe('tree');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('does not create new tree if already exists', (done) => {
|
||||
const tree = {
|
||||
type: 'tree',
|
||||
name: 'testing',
|
||||
endpoint: 'test',
|
||||
tree: [],
|
||||
};
|
||||
|
||||
projectTree.tree.push(tree);
|
||||
|
||||
store.dispatch('createTempTree', {
|
||||
projectId: store.state.currentProjectId,
|
||||
branchId: store.state.currentBranchId,
|
||||
name: 'testing/test',
|
||||
parent: projectTree,
|
||||
})
|
||||
.then(() => {
|
||||
expect(projectTree.tree[0].name).toBe('testing');
|
||||
expect(projectTree.tree[0].tempFile).toBeUndefined();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLastCommitData', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(service, 'getTreeLastCommit').and.returnValue(Promise.resolve({
|
||||
headers: {
|
||||
'more-logs-url': null,
|
||||
},
|
||||
json: () => Promise.resolve([{
|
||||
type: 'tree',
|
||||
file_name: 'testing',
|
||||
commit: {
|
||||
message: 'commit message',
|
||||
authored_date: '123',
|
||||
},
|
||||
}]),
|
||||
}));
|
||||
|
||||
store.state.trees['abcproject/mybranch'] = {
|
||||
tree: [],
|
||||
};
|
||||
|
||||
projectTree = store.state.trees['abcproject/mybranch'];
|
||||
projectTree.tree.push(file('testing', '1', 'tree'));
|
||||
projectTree.lastCommitPath = 'lastcommitpath';
|
||||
});
|
||||
|
||||
it('calls service with lastCommitPath', (done) => {
|
||||
store.dispatch('getLastCommitData', projectTree)
|
||||
.then(() => {
|
||||
expect(service.getTreeLastCommit).toHaveBeenCalledWith('lastcommitpath');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('updates trees last commit data', (done) => {
|
||||
store.dispatch('getLastCommitData', projectTree)
|
||||
.then(Vue.nextTick)
|
||||
.then(() => {
|
||||
expect(projectTree.tree[0].lastCommit.message).toBe('commit message');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('does not update entry if not found', (done) => {
|
||||
projectTree.tree[0].name = 'a';
|
||||
|
||||
store.dispatch('getLastCommitData', projectTree)
|
||||
.then(Vue.nextTick)
|
||||
.then(() => {
|
||||
expect(projectTree.tree[0].lastCommit.message).not.toBe('commit message');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateDirectoryData', () => {
|
||||
it('adds data into tree', (done) => {
|
||||
const tree = {
|
||||
tree: [],
|
||||
};
|
||||
const data = {
|
||||
trees: [{ name: 'tree' }],
|
||||
submodules: [{ name: 'submodule' }],
|
||||
blobs: [{ name: 'blob' }],
|
||||
};
|
||||
|
||||
store.dispatch('updateDirectoryData', {
|
||||
data,
|
||||
tree,
|
||||
}).then(() => {
|
||||
expect(tree.tree[0].name).toBe('tree');
|
||||
expect(tree.tree[0].type).toBe('tree');
|
||||
expect(tree.tree[1].name).toBe('submodule');
|
||||
expect(tree.tree[1].type).toBe('submodule');
|
||||
expect(tree.tree[2].name).toBe('blob');
|
||||
expect(tree.tree[2].type).toBe('blob');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,432 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import * as urlUtils from '~/lib/utils/url_utility';
|
||||
import store from '~/ide/stores';
|
||||
import service from '~/ide/services';
|
||||
import { resetStore, file } from '../helpers';
|
||||
|
||||
describe('Multi-file store actions', () => {
|
||||
afterEach(() => {
|
||||
resetStore(store);
|
||||
});
|
||||
|
||||
describe('redirectToUrl', () => {
|
||||
it('calls visitUrl', (done) => {
|
||||
spyOn(urlUtils, 'visitUrl');
|
||||
|
||||
store.dispatch('redirectToUrl', 'test')
|
||||
.then(() => {
|
||||
expect(urlUtils.visitUrl).toHaveBeenCalledWith('test');
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setInitialData', () => {
|
||||
it('commits initial data', (done) => {
|
||||
store.dispatch('setInitialData', { canCommit: true })
|
||||
.then(() => {
|
||||
expect(store.state.canCommit).toBeTruthy();
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('closeDiscardPopup', () => {
|
||||
it('closes the discard popup', (done) => {
|
||||
store.dispatch('closeDiscardPopup', false)
|
||||
.then(() => {
|
||||
expect(store.state.discardPopupOpen).toBeFalsy();
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('discardAllChanges', () => {
|
||||
beforeEach(() => {
|
||||
store.state.openFiles.push(file('discardAll'));
|
||||
store.state.openFiles[0].changed = true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('closeAllFiles', () => {
|
||||
beforeEach(() => {
|
||||
store.state.openFiles.push(file('closeAll'));
|
||||
store.state.openFiles[0].opened = true;
|
||||
});
|
||||
|
||||
it('closes all open files', (done) => {
|
||||
store.dispatch('closeAllFiles')
|
||||
.then(() => {
|
||||
expect(store.state.openFiles.length).toBe(0);
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleEditMode', () => {
|
||||
it('toggles edit mode', (done) => {
|
||||
store.state.editMode = true;
|
||||
|
||||
store.dispatch('toggleEditMode')
|
||||
.then(() => {
|
||||
expect(store.state.editMode).toBeFalsy();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets preview mode', (done) => {
|
||||
store.state.currentBlobView = 'repo-editor';
|
||||
store.state.editMode = true;
|
||||
|
||||
store.dispatch('toggleEditMode')
|
||||
.then(Vue.nextTick)
|
||||
.then(() => {
|
||||
expect(store.state.currentBlobView).toBe('repo-preview');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('opens discard popup if there are changed files', (done) => {
|
||||
store.state.editMode = true;
|
||||
store.state.openFiles.push(file('discardChanges'));
|
||||
store.state.openFiles[0].changed = true;
|
||||
|
||||
store.dispatch('toggleEditMode')
|
||||
.then(() => {
|
||||
expect(store.state.discardPopupOpen).toBeTruthy();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('can force closed if there are changed files', (done) => {
|
||||
store.state.editMode = true;
|
||||
|
||||
store.state.openFiles.push(file('forceClose'));
|
||||
store.state.openFiles[0].changed = true;
|
||||
|
||||
store.dispatch('toggleEditMode', true)
|
||||
.then(() => {
|
||||
expect(store.state.discardPopupOpen).toBeFalsy();
|
||||
expect(store.state.editMode).toBeFalsy();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('discards file changes', (done) => {
|
||||
const f = file('discard');
|
||||
store.state.editMode = true;
|
||||
store.state.openFiles.push(f);
|
||||
f.changed = true;
|
||||
|
||||
store.dispatch('toggleEditMode', true)
|
||||
.then(Vue.nextTick)
|
||||
.then(() => {
|
||||
expect(f.changed).toBeFalsy();
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleBlobView', () => {
|
||||
it('sets edit mode view if in edit mode', (done) => {
|
||||
store.dispatch('toggleBlobView')
|
||||
.then(() => {
|
||||
expect(store.state.currentBlobView).toBe('repo-editor');
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('sets preview mode view if not in edit mode', (done) => {
|
||||
store.state.editMode = false;
|
||||
|
||||
store.dispatch('toggleBlobView')
|
||||
.then(() => {
|
||||
expect(store.state.currentBlobView).toBe('repo-preview');
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkCommitStatus', () => {
|
||||
beforeEach(() => {
|
||||
store.state.currentProjectId = 'abcproject';
|
||||
store.state.currentBranchId = 'master';
|
||||
store.state.projects.abcproject = {
|
||||
branches: {
|
||||
master: {
|
||||
workingReference: '1',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('calls service', (done) => {
|
||||
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
|
||||
data: {
|
||||
commit: { id: '123' },
|
||||
},
|
||||
}));
|
||||
|
||||
store.dispatch('checkCommitStatus')
|
||||
.then(() => {
|
||||
expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master');
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('returns true if current ref does not equal returned ID', (done) => {
|
||||
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
|
||||
data: {
|
||||
commit: { id: '123' },
|
||||
},
|
||||
}));
|
||||
|
||||
store.dispatch('checkCommitStatus')
|
||||
.then((val) => {
|
||||
expect(val).toBeTruthy();
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('returns false if current ref equals returned ID', (done) => {
|
||||
spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
|
||||
data: {
|
||||
commit: { id: '1' },
|
||||
},
|
||||
}));
|
||||
|
||||
store.dispatch('checkCommitStatus')
|
||||
.then((val) => {
|
||||
expect(val).toBeFalsy();
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('commitChanges', () => {
|
||||
let payload;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(window, 'scrollTo');
|
||||
|
||||
document.body.innerHTML += '<div class="flash-container"></div>';
|
||||
|
||||
store.state.currentProjectId = 'abcproject';
|
||||
store.state.currentBranchId = 'master';
|
||||
store.state.projects.abcproject = {
|
||||
web_url: 'webUrl',
|
||||
branches: {
|
||||
master: {
|
||||
workingReference: '1',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
payload = {
|
||||
branch: 'master',
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.querySelector('.flash-container').remove();
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(service, 'commit').and.returnValue(Promise.resolve({
|
||||
data: {
|
||||
id: '123456',
|
||||
short_id: '123',
|
||||
message: 'test message',
|
||||
committed_date: 'date',
|
||||
stats: {
|
||||
additions: '1',
|
||||
deletions: '2',
|
||||
},
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
it('calls service', (done) => {
|
||||
store.dispatch('commitChanges', { payload, newMr: false })
|
||||
.then(() => {
|
||||
expect(service.commit).toHaveBeenCalledWith('abcproject', payload);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('shows flash notice', (done) => {
|
||||
store.dispatch('commitChanges', { payload, newMr: false })
|
||||
.then(() => {
|
||||
const alert = document.querySelector('.flash-container');
|
||||
|
||||
expect(alert.querySelector('.flash-notice')).not.toBeNull();
|
||||
expect(alert.textContent.trim()).toBe(
|
||||
'Your changes have been committed. Commit 123 with 1 additions, 2 deletions.',
|
||||
);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('adds commit data to changed files', (done) => {
|
||||
const changedFile = file('changed');
|
||||
const f = file('newfile');
|
||||
changedFile.changed = true;
|
||||
|
||||
store.state.openFiles.push(changedFile, f);
|
||||
|
||||
store.dispatch('commitChanges', { payload, newMr: false })
|
||||
.then(() => {
|
||||
expect(changedFile.lastCommit.message).toBe('test message');
|
||||
expect(f.lastCommit.message).not.toBe('test message');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('scrolls to top of page', (done) => {
|
||||
store.dispatch('commitChanges', { payload, newMr: false })
|
||||
.then(() => {
|
||||
expect(window.scrollTo).toHaveBeenCalledWith(0, 0);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
it('redirects to new merge request page', (done) => {
|
||||
spyOn(urlUtils, 'visitUrl');
|
||||
|
||||
store.dispatch('commitChanges', { payload, newMr: true })
|
||||
.then(() => {
|
||||
expect(urlUtils.visitUrl).toHaveBeenCalledWith('webUrl/merge_requests/new?merge_request%5Bsource_branch%5D=master');
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('failed', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(service, 'commit').and.returnValue(Promise.resolve({
|
||||
data: {
|
||||
message: 'failed message',
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
it('shows failed message', (done) => {
|
||||
store.dispatch('commitChanges', { payload, newMr: false })
|
||||
.then(() => {
|
||||
const alert = document.querySelector('.flash-container');
|
||||
|
||||
expect(alert.textContent.trim()).toBe(
|
||||
'failed message',
|
||||
);
|
||||
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createTempEntry', () => {
|
||||
beforeEach(() => {
|
||||
store.state.trees['abcproject/mybranch'] = {
|
||||
tree: [],
|
||||
};
|
||||
store.state.projects.abcproject = {
|
||||
web_url: '',
|
||||
};
|
||||
});
|
||||
|
||||
it('creates a temp tree', (done) => {
|
||||
const projectTree = store.state.trees['abcproject/mybranch'];
|
||||
|
||||
store.dispatch('createTempEntry', {
|
||||
projectId: 'abcproject',
|
||||
branchId: 'mybranch',
|
||||
parent: projectTree,
|
||||
name: 'test',
|
||||
type: 'tree',
|
||||
})
|
||||
.then(() => {
|
||||
const baseTree = projectTree.tree;
|
||||
expect(baseTree.length).toBe(1);
|
||||
expect(baseTree[0].tempFile).toBeTruthy();
|
||||
expect(baseTree[0].type).toBe('tree');
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
|
||||
it('creates temp file', (done) => {
|
||||
const projectTree = store.state.trees['abcproject/mybranch'];
|
||||
|
||||
store.dispatch('createTempEntry', {
|
||||
projectId: 'abcproject',
|
||||
branchId: 'mybranch',
|
||||
parent: projectTree,
|
||||
name: 'test',
|
||||
type: 'blob',
|
||||
})
|
||||
.then(() => {
|
||||
const baseTree = projectTree.tree;
|
||||
expect(baseTree.length).toBe(1);
|
||||
expect(baseTree[0].tempFile).toBeTruthy();
|
||||
expect(baseTree[0].type).toBe('blob');
|
||||
|
||||
done();
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
||||
describe('popHistoryState', () => {
|
||||
|
||||
});
|
||||
|
||||
describe('scrollToTab', () => {
|
||||
it('focuses the current active element', (done) => {
|
||||
document.body.innerHTML += '<div id="tabs"><div class="active"><div class="repo-tab"></div></div></div>';
|
||||
const el = document.querySelector('.repo-tab');
|
||||
spyOn(el, 'focus');
|
||||
|
||||
store.dispatch('scrollToTab')
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
expect(el.focus).toHaveBeenCalled();
|
||||
|
||||
document.getElementById('tabs').remove();
|
||||
|
||||
done();
|
||||
});
|
||||
})
|
||||
.catch(done.fail);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,114 +0,0 @@
|
|||
import * as getters from '~/ide/stores/getters';
|
||||
import state from '~/ide/stores/state';
|
||||
import { file } from '../helpers';
|
||||
|
||||
describe('Multi-file store getters', () => {
|
||||
let localState;
|
||||
|
||||
beforeEach(() => {
|
||||
localState = state();
|
||||
});
|
||||
|
||||
describe('changedFiles', () => {
|
||||
it('returns a list of changed opened files', () => {
|
||||
localState.openFiles.push(file());
|
||||
localState.openFiles.push(file('changed'));
|
||||
localState.openFiles[1].changed = true;
|
||||
|
||||
const changedFiles = getters.changedFiles(localState);
|
||||
|
||||
expect(changedFiles.length).toBe(1);
|
||||
expect(changedFiles[0].name).toBe('changed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('activeFile', () => {
|
||||
it('returns the current active file', () => {
|
||||
localState.openFiles.push(file());
|
||||
localState.openFiles.push(file('active'));
|
||||
localState.openFiles[1].active = true;
|
||||
|
||||
expect(getters.activeFile(localState).name).toBe('active');
|
||||
});
|
||||
|
||||
it('returns undefined if no active files are found', () => {
|
||||
localState.openFiles.push(file());
|
||||
localState.openFiles.push(file('active'));
|
||||
|
||||
expect(getters.activeFile(localState)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('activeFileExtension', () => {
|
||||
it('returns the file extension for the current active file', () => {
|
||||
localState.openFiles.push(file('active'));
|
||||
localState.openFiles[0].active = true;
|
||||
localState.openFiles[0].path = 'test.js';
|
||||
|
||||
expect(getters.activeFileExtension(localState)).toBe('.js');
|
||||
|
||||
localState.openFiles[0].path = 'test.es6.js';
|
||||
|
||||
expect(getters.activeFileExtension(localState)).toBe('.js');
|
||||
});
|
||||
});
|
||||
|
||||
describe('canEditFile', () => {
|
||||
beforeEach(() => {
|
||||
localState.onTopOfBranch = true;
|
||||
localState.canCommit = true;
|
||||
|
||||
localState.openFiles.push(file());
|
||||
localState.openFiles[0].active = true;
|
||||
});
|
||||
|
||||
it('returns true if user can commit and has open files', () => {
|
||||
expect(getters.canEditFile(localState)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns false if user can commit and has no open files', () => {
|
||||
localState.openFiles = [];
|
||||
|
||||
expect(getters.canEditFile(localState)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns false if user can commit and active file is binary', () => {
|
||||
localState.openFiles[0].binary = true;
|
||||
|
||||
expect(getters.canEditFile(localState)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns false if user cant commit', () => {
|
||||
localState.canCommit = false;
|
||||
|
||||
expect(getters.canEditFile(localState)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('modifiedFiles', () => {
|
||||
it('returns a list of modified files', () => {
|
||||
localState.openFiles.push(file());
|
||||
localState.openFiles.push(file('changed'));
|
||||
localState.openFiles[1].changed = true;
|
||||
|
||||
const modifiedFiles = getters.modifiedFiles(localState);
|
||||
|
||||
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.openFiles.push(file('added'));
|
||||
localState.openFiles[1].changed = true;
|
||||
localState.openFiles[1].tempFile = true;
|
||||
|
||||
const modifiedFiles = getters.addedFiles(localState);
|
||||
|
||||
expect(modifiedFiles.length).toBe(1);
|
||||
expect(modifiedFiles[0].name).toBe('added');
|
||||
});
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue