Commit 26c241d9 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'ide-workbench-bar' into '44846-improve-web-ide-left-panel-and-modes'

Add left links sidebar to IDE

See merge request gitlab-org/gitlab-ce!18368
parents a5ffb012 7311e69f
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import { activityBarViews } from '../constants';
export default {
components: {
Icon,
},
computed: {
...mapGetters(['currentProject']),
...mapState(['currentActivityView']),
goBackUrl() {
return document.referrer || this.currentProject.web_url;
},
},
methods: {
...mapActions(['updateActivityBarView']),
},
activityBarViews,
};
</script>
<template>
<nav class="ide-activity-bar">
<ul class="list-unstyled">
<li v-once>
<a
:href="goBackUrl"
class="ide-sidebar-link"
:aria-label="s__('IDE|Go back')"
>
<icon
:size="16"
name="go-back"
/>
</a>
</li>
<li>
<button
type="button"
class="ide-sidebar-link js-ide-edit-mode"
:class="{
active: currentActivityView === $options.activityBarViews.edit
}"
@click.prevent="updateActivityBarView($options.activityBarViews.edit)"
:aria-label="s__('IDE|Edit mode')"
>
<icon
name="code"
/>
</button>
</li>
<li>
<button
type="button"
class="ide-sidebar-link js-ide-commit-mode"
:class="{
active: currentActivityView === $options.activityBarViews.commit
}"
@click.prevent="updateActivityBarView($options.activityBarViews.commit)"
:aria-label="s__('IDE|Commit mode')"
>
<icon
name="commit"
/>
</button>
</li>
</ul>
</nav>
</template>
<script> <script>
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapState } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
...@@ -10,26 +10,12 @@ export default { ...@@ -10,26 +10,12 @@ export default {
directives: { directives: {
tooltip, tooltip,
}, },
props: {
noChangesStateSvgPath: {
type: String,
required: true,
},
committedStateSvgPath: {
type: String,
required: true,
},
},
computed: { computed: {
...mapState(['lastCommitMsg', 'rightPanelCollapsed']), ...mapState(['lastCommitMsg', 'noChangesStateSvgPath', 'committedStateSvgPath']),
...mapGetters(['collapseButtonIcon', 'collapseButtonTooltip']),
statusSvg() { statusSvg() {
return this.lastCommitMsg ? this.committedStateSvgPath : this.noChangesStateSvgPath; return this.lastCommitMsg ? this.committedStateSvgPath : this.noChangesStateSvgPath;
}, },
}, },
methods: {
...mapActions(['toggleRightPanelCollapsed']),
},
}; };
</script> </script>
...@@ -37,31 +23,8 @@ export default { ...@@ -37,31 +23,8 @@ export default {
<div <div
class="multi-file-commit-panel-section ide-commit-empty-state js-empty-state" class="multi-file-commit-panel-section ide-commit-empty-state js-empty-state"
> >
<header
class="multi-file-commit-panel-header"
:class="{
'is-collapsed': rightPanelCollapsed,
}"
>
<button
v-tooltip
:title="collapseButtonTooltip"
data-container="body"
data-placement="left"
type="button"
class="btn btn-transparent multi-file-commit-panel-collapse-btn"
:aria-label="__('Toggle sidebar')"
@click.stop="toggleRightPanelCollapsed"
>
<icon
:name="collapseButtonIcon"
:size="18"
/>
</button>
</header>
<div <div
class="ide-commit-empty-state-container" class="ide-commit-empty-state-container"
v-if="!rightPanelCollapsed"
> >
<div class="svg-content svg-80"> <div class="svg-content svg-80">
<img :src="statusSvg" /> <img :src="statusSvg" />
......
<script> <script>
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions } from 'vuex';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
import ListItem from './list_item.vue'; import ListItem from './list_item.vue';
import ListCollapsed from './list_collapsed.vue';
export default { export default {
components: { components: {
Icon, Icon,
ListItem, ListItem,
ListCollapsed,
}, },
directives: { directives: {
tooltip, tooltip,
...@@ -24,11 +22,6 @@ export default { ...@@ -24,11 +22,6 @@ export default {
type: Array, type: Array,
required: true, required: true,
}, },
showToggle: {
type: Boolean,
required: false,
default: true,
},
iconName: { iconName: {
type: String, type: String,
required: true, required: true,
...@@ -52,8 +45,6 @@ export default { ...@@ -52,8 +45,6 @@ export default {
}, },
}, },
computed: { computed: {
...mapState(['rightPanelCollapsed']),
...mapGetters(['collapseButtonIcon', 'collapseButtonTooltip']),
titleText() { titleText() {
return sprintf(__('%{title} changes'), { return sprintf(__('%{title} changes'), {
title: this.title, title: this.title,
...@@ -61,7 +52,7 @@ export default { ...@@ -61,7 +52,7 @@ export default {
}, },
}, },
methods: { methods: {
...mapActions(['toggleRightPanelCollapsed', 'stageAllChanges', 'unstageAllChanges']), ...mapActions(['stageAllChanges', 'unstageAllChanges']),
actionBtnClicked() { actionBtnClicked() {
this[this.action](); this[this.action]();
}, },
...@@ -72,19 +63,12 @@ export default { ...@@ -72,19 +63,12 @@ export default {
<template> <template>
<div <div
class="ide-commit-list-container" class="ide-commit-list-container"
:class="{
'is-collapsed': rightPanelCollapsed,
}"
> >
<header <header
class="multi-file-commit-panel-header" class="multi-file-commit-panel-header"
> >
<div <div
v-if="!rightPanelCollapsed"
class="multi-file-commit-panel-header-title" class="multi-file-commit-panel-header-title"
:class="{
'append-right-10': showToggle,
}"
> >
<icon <icon
v-once v-once
...@@ -100,52 +84,28 @@ export default { ...@@ -100,52 +84,28 @@ export default {
{{ actionBtnText }} {{ actionBtnText }}
</button> </button>
</div> </div>
<button
v-if="showToggle"
v-tooltip
:title="collapseButtonTooltip"
data-container="body"
data-placement="left"
type="button"
class="btn btn-transparent multi-file-commit-panel-collapse-btn"
:aria-label="__('Toggle sidebar')"
@click.stop="toggleRightPanelCollapsed"
>
<icon
:name="collapseButtonIcon"
:size="18"
/>
</button>
</header> </header>
<list-collapsed <ul
v-if="rightPanelCollapsed" v-if="fileList.length"
:files="fileList" class="multi-file-commit-list list-unstyled append-bottom-0"
:icon-name="iconName" >
:title="title" <li
/> v-for="file in fileList"
<template v-else> :key="file.key"
<ul
v-if="fileList.length"
class="multi-file-commit-list list-unstyled append-bottom-0"
>
<li
v-for="file in fileList"
:key="file.key"
>
<list-item
:file="file"
:action-component="itemActionComponent"
:key-prefix="title"
:staged-list="stagedList"
/>
</li>
</ul>
<p
v-else
class="multi-file-commit-list help-block"
> >
{{ __('No changes') }} <list-item
</p> :file="file"
</template> :action-component="itemActionComponent"
:key-prefix="title"
:staged-list="stagedList"
/>
</li>
</ul>
<p
v-else
class="multi-file-commit-list help-block"
>
{{ __('No changes') }}
</p>
</div> </div>
</template> </template>
<script> <script>
import { mapActions, mapState, mapGetters } from 'vuex'; import Mousetrap from 'mousetrap';
import Mousetrap from 'mousetrap'; import { mapActions, mapState, mapGetters } from 'vuex';
import ideSidebar from './ide_side_bar.vue'; import IdeSidebar from './ide_side_bar.vue';
import ideContextbar from './ide_context_bar.vue'; import RepoTabs from './repo_tabs.vue';
import repoTabs from './repo_tabs.vue'; import IdeStatusBar from './ide_status_bar.vue';
import ideStatusBar from './ide_status_bar.vue'; import RepoEditor from './repo_editor.vue';
import repoEditor from './repo_editor.vue'; import FindFile from './file_finder/index.vue';
import FindFile from './file_finder/index.vue';
const originalStopCallback = Mousetrap.stopCallback; const originalStopCallback = Mousetrap.stopCallback;
export default { export default {
components: { components: {
ideSidebar, IdeSidebar,
ideContextbar, RepoTabs,
repoTabs, IdeStatusBar,
ideStatusBar, RepoEditor,
repoEditor, FindFile,
FindFile, },
}, computed: {
props: { ...mapState([
emptyStateSvgPath: { 'changedFiles',
type: String, 'openFiles',
required: true, 'viewer',
}, 'currentMergeRequestId',
noChangesStateSvgPath: { 'fileFindVisible',
type: String, 'emptyStateSvgPath',
required: true, ]),
}, ...mapGetters(['activeFile', 'hasChanges']),
committedStateSvgPath: { },
type: String, mounted() {
required: true, const returnValue = 'Are you sure you want to lose unsaved changes?';
}, window.onbeforeunload = e => {
}, if (!this.changedFiles.length) return undefined;
computed: {
...mapState([
'changedFiles',
'openFiles',
'viewer',
'currentMergeRequestId',
'fileFindVisible',
]),
...mapGetters(['activeFile', 'hasChanges']),
},
mounted() {
const returnValue = 'Are you sure you want to lose unsaved changes?';
window.onbeforeunload = e => {
if (!this.changedFiles.length) return undefined;
Object.assign(e, { Object.assign(e, {
returnValue, returnValue,
}); });
return returnValue; return returnValue;
}; };
Mousetrap.bind(['t', 'command+p', 'ctrl+p'], e => { Mousetrap.bind(['t', 'command+p', 'ctrl+p'], e => {
if (e.preventDefault) { if (e.preventDefault) {
e.preventDefault(); e.preventDefault();
} }
this.toggleFileFinder(!this.fileFindVisible); this.toggleFileFinder(!this.fileFindVisible);
}); });
Mousetrap.stopCallback = (e, el, combo) => this.mousetrapStopCallback(e, el, combo); Mousetrap.stopCallback = (e, el, combo) => this.mousetrapStopCallback(e, el, combo);
}, },
methods: { methods: {
...mapActions(['toggleFileFinder']), ...mapActions(['toggleFileFinder']),
mousetrapStopCallback(e, el, combo) { mousetrapStopCallback(e, el, combo) {
if (combo === 't' && el.classList.contains('dropdown-input-field')) { if (combo === 't' && el.classList.contains('dropdown-input-field')) {
return true; return true;
} else if (combo === 'command+p' || combo === 'ctrl+p') { } else if (combo === 'command+p' || combo === 'ctrl+p') {
return false; return false;
} }
return originalStopCallback(e, el, combo); return originalStopCallback(e, el, combo);
},
}, },
}; },
};
</script> </script>
<template> <template>
...@@ -136,9 +121,5 @@ ...@@ -136,9 +121,5 @@
</div> </div>
</template> </template>
</div> </div>
<ide-contextbar
:no-changes-state-svg-path="noChangesStateSvgPath"
:committed-state-svg-path="committedStateSvgPath"
/>
</div> </div>
</template> </template>
<script>
import icon from '~/vue_shared/components/icon.vue';
import panelResizer from '~/vue_shared/components/panel_resizer.vue';
import repoCommitSection from './repo_commit_section.vue';
import ResizablePanel from './resizable_panel.vue';
export default {
components: {
repoCommitSection,
icon,
panelResizer,
ResizablePanel,
},
props: {
noChangesStateSvgPath: {
type: String,
required: true,
},
committedStateSvgPath: {
type: String,
required: true,
},
},
};
</script>
<template>
<resizable-panel
:collapsible="true"
:initial-width="340"
side="right"
>
<div
class="multi-file-commit-panel-section"
>
<repo-commit-section
:no-changes-state-svg-path="noChangesStateSvgPath"
:committed-state-svg-path="committedStateSvgPath"
/>
</div>
</resizable-panel>
</template>
<script>
import icon from '~/vue_shared/components/icon.vue';
export default {
components: {
icon,
},
props: {
projectUrl: {
type: String,
required: true,
},
},
computed: {
goBackUrl() {
return document.referrer || this.projectUrl;
},
},
};
</script>
<template>
<nav
class="ide-external-links"
v-once
>
<p>
<a
:href="goBackUrl"
class="ide-sidebar-link"
>
<icon
:size="16"
class="append-right-8"
name="go-back"
/>
<span class="ide-external-links-text">
{{ s__('Go back') }}
</span>
</a>
</p>
</nav>
</template>
<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 str-truncated ref-name">
<icon
name="branch"
:size="12"
/>
{{ branch.name }}
</div>
<div class="branch-header-btns">
<new-dropdown
:project-id="projectId"
:branch="branch.name"
path=""
/>
</div>
</div>
<repo-tree
:tree="branch.tree"
/>
</div>
</template>
<script>
import ProjectAvatarImage from '~/vue_shared/components/project_avatar/image.vue';
import Identicon from '../../vue_shared/components/identicon.vue';
import BranchesTree from './ide_project_branches_tree.vue';
import ExternalLinks from './ide_external_links.vue';
export default {
components: {
BranchesTree,
ExternalLinks,
ProjectAvatarImage,
Identicon,
},
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
v-if="project.avatar_url"
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>
<identicon
v-else
size-class="s40"
:entity-id="project.id"
:entity-name="project.name"
/>
<div class="sidebar-context-title">
{{ project.name }}
</div>
</a>
</div>
<external-links
:project-url="project.web_url"
/>
<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>
<script> <script>
import { mapState, mapGetters } from 'vuex'; import { mapState, mapGetters } from 'vuex';
import icon from '~/vue_shared/components/icon.vue'; import ProjectAvatarImage from '~/vue_shared/components/project_avatar/image.vue';
import panelResizer from '~/vue_shared/components/panel_resizer.vue'; import Icon from '~/vue_shared/components/icon.vue';
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
import projectTree from './ide_project_tree.vue'; import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import ResizablePanel from './resizable_panel.vue'; import Identicon from '../../vue_shared/components/identicon.vue';
import IdeTree from './ide_tree.vue';
import ResizablePanel from './resizable_panel.vue';
import ActivityBar from './activity_bar.vue';
import CommitSection from './repo_commit_section.vue';
export default { export default {
components: { components: {
projectTree, Icon,
icon, PanelResizer,
panelResizer, SkeletonLoadingContainer,
skeletonLoadingContainer, ResizablePanel,
ResizablePanel, ActivityBar,
}, ProjectAvatarImage,
computed: { Identicon,
...mapState([ CommitSection,
'loading', IdeTree,
]), },
...mapGetters([ computed: {
'projectsWithTrees', ...mapState(['loading', 'currentBranchId', 'currentActivityView']),
]), ...mapGetters(['currentProject']),
}, },
}; };
</script> </script>
<template> <template>
<resizable-panel <resizable-panel
:collapsible="false" :collapsible="false"
:initial-width="290" :initial-width="340"
side="left" side="left"
> >
<activity-bar
v-if="!loading"
/>
<div class="multi-file-commit-panel-inner"> <div class="multi-file-commit-panel-inner">
<template v-if="loading"> <template v-if="loading">
<div <div
...@@ -41,11 +48,50 @@ ...@@ -41,11 +48,50 @@
<skeleton-loading-container /> <skeleton-loading-container />
</div> </div>
</template> </template>
<project-tree <template v-else>
v-for="project in projectsWithTrees" <div class="context-header ide-context-header">
:key="project.id" <a
:project="project" :href="currentProject.web_url"
/> >
<div
v-if="currentProject.avatar_url"
class="avatar-container s40 project-avatar"
>
<project-avatar-image
class="avatar-container project-avatar"
:link-href="currentProject.path"
:img-src="currentProject.avatar_url"
:img-alt="currentProject.name"
:img-size="40"
/>
</div>
<identicon
v-else
size-class="s40"
:entity-id="currentProject.id"
:entity-name="currentProject.name"
/>
<div class="ide-sidebar-project-title">
<div class="sidebar-context-title">
{{ currentProject.name }}
</div>
<div
class="sidebar-context-title ide-sidebar-branch-title"
>
<icon
name="branch"
css-classes="append-right-5"
/>{{ currentBranchId }}
</div>
</div>
</a>
</div>
<div class="multi-file-commit-panel-inner-scroll">
<component
:is="currentActivityView"
/>
</div>
</template>
</div> </div>
</resizable-panel> </resizable-panel>
</template> </template>
<script> <script>
import { mapGetters, mapState } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import RepoFile from './repo_file.vue'; import RepoFile from './repo_file.vue';
import NewDropdown from './new_dropdown/index.vue';
export default { export default {
components: { components: {
Icon,
RepoFile, RepoFile,
SkeletonLoadingContainer, SkeletonLoadingContainer,
NewDropdown,
}, },
props: { computed: {
tree: { ...mapState(['currentBranchId']),
type: Object, ...mapGetters(['currentProject', 'currentTree']),
required: true,
},
}, },
}; };
</script> </script>
...@@ -20,7 +23,7 @@ export default { ...@@ -20,7 +23,7 @@ export default {
<div <div
class="ide-file-list" class="ide-file-list"
> >
<template v-if="tree.loading"> <template v-if="!currentTree || currentTree.loading">
<div <div
class="multi-file-loading-container" class="multi-file-loading-container"
v-for="n in 3" v-for="n in 3"
...@@ -30,8 +33,16 @@ export default { ...@@ -30,8 +33,16 @@ export default {
</div> </div>
</template> </template>
<template v-else> <template v-else>
<header class="ide-tree-header">
{{ __('Edit') }}
<new-dropdown
:project-id="currentProject.name_with_namespace"
:branch="currentBranchId"
path=""
/>
</header>
<repo-file <repo-file
v-for="file in tree.tree" v-for="file in currentTree.tree"
:key="file.key" :key="file.key"
:file="file" :file="file"
:level="0" :level="0"
......
...@@ -23,18 +23,8 @@ export default { ...@@ -23,18 +23,8 @@ export default {
directives: { directives: {
tooltip, tooltip,
}, },
props: {
noChangesStateSvgPath: {
type: String,
required: true,
},
committedStateSvgPath: {
type: String,
required: true,
},
},
computed: { computed: {
...mapState(['changedFiles', 'stagedFiles', 'rightPanelCollapsed']), ...mapState(['changedFiles', 'stagedFiles']),
...mapState('commit', ['commitMessage', 'submitCommitLoading']), ...mapState('commit', ['commitMessage', 'submitCommitLoading']),
...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled', 'branchName']), ...mapGetters('commit', ['commitButtonDisabled', 'discardDraftButtonDisabled', 'branchName']),
}, },
...@@ -86,13 +76,11 @@ export default { ...@@ -86,13 +76,11 @@ export default {
action="unstageAllChanges" action="unstageAllChanges"
:action-btn-text="__('Unstage all')" :action-btn-text="__('Unstage all')"
item-action-component="unstage-button" item-action-component="unstage-button"
:show-toggle="false"
:staged-list="true" :staged-list="true"
/> />
<form <form
class="form-horizontal multi-file-commit-form" class="form-horizontal multi-file-commit-form"
@submit.prevent.stop="commitChanges" @submit.prevent.stop="commitChanges"
v-if="!rightPanelCollapsed"
> >
<commit-message-field <commit-message-field
:text="commitMessage" :text="commitMessage"
...@@ -120,8 +108,6 @@ export default { ...@@ -120,8 +108,6 @@ export default {
</template> </template>
<empty-state <empty-state
v-else v-else
:no-changes-state-svg-path="noChangesStateSvgPath"
:committed-state-svg-path="committedStateSvgPath"
/> />
</div> </div>
</template> </template>
...@@ -66,10 +66,26 @@ export default { ...@@ -66,10 +66,26 @@ export default {
<template> <template>
<li <li
:class="{
active: tab.active
}"
@click="clickFile(tab)" @click="clickFile(tab)"
@mouseover="mouseOverTab" @mouseover="mouseOverTab"
@mouseout="mouseOutTab" @mouseout="mouseOutTab"
> >
<div
class="multi-file-tab"
:title="tab.url"
>
<file-icon
:file-name="tab.name"
:size="16"
/>
{{ tab.name }}
<file-status-icon
:file="tab"
/>
</div>
<button <button
type="button" type="button"
class="multi-file-tab-close" class="multi-file-tab-close"
...@@ -86,22 +102,5 @@ export default { ...@@ -86,22 +102,5 @@ export default {
:file="tab" :file="tab"
/> />
</button> </button>
<div
class="multi-file-tab"
:class="{
active: tab.active
}"
:title="tab.url"
>
<file-icon
:file-name="tab.name"
:size="16"
/>
{{ tab.name }}
<file-status-icon
:file="tab"
/>
</div>
</li> </li>
</template> </template>
...@@ -6,3 +6,8 @@ export const FILE_FINDER_EMPTY_ROW_HEIGHT = 33; ...@@ -6,3 +6,8 @@ export const FILE_FINDER_EMPTY_ROW_HEIGHT = 33;
// Commit message textarea // Commit message textarea
export const MAX_TITLE_LENGTH = 50; export const MAX_TITLE_LENGTH = 50;
export const MAX_BODY_LENGTH = 72; export const MAX_BODY_LENGTH = 72;
export const activityBarViews = {
edit: 'ide-tree',
commit: 'commit-section',
};
...@@ -63,6 +63,8 @@ router.beforeEach((to, from, next) => { ...@@ -63,6 +63,8 @@ router.beforeEach((to, from, next) => {
const fullProjectId = `${to.params.namespace}/${to.params.project}`; const fullProjectId = `${to.params.namespace}/${to.params.project}`;
if (to.params.branch) { if (to.params.branch) {
store.dispatch('setCurrentBranchId', to.params.branch);
store.dispatch('getBranchData', { store.dispatch('getBranchData', {
projectId: fullProjectId, projectId: fullProjectId,
branchId: to.params.branch, branchId: to.params.branch,
......
...@@ -14,15 +14,16 @@ function initIde(el) { ...@@ -14,15 +14,16 @@ function initIde(el) {
components: { components: {
ide, ide,
}, },
render(createElement) { created() {
return createElement('ide', { this.$store.dispatch('setEmptyStateSvgs', {
props: { emptyStateSvgPath: el.dataset.emptyStateSvgPath,
emptyStateSvgPath: el.dataset.emptyStateSvgPath, noChangesStateSvgPath: el.dataset.noChangesStateSvgPath,
noChangesStateSvgPath: el.dataset.noChangesStateSvgPath, committedStateSvgPath: el.dataset.committedStateSvgPath,
committedStateSvgPath: el.dataset.committedStateSvgPath,
},
}); });
}, },
render(createElement) {
return createElement('ide');
},
}); });
} }
......
...@@ -33,10 +33,7 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => { ...@@ -33,10 +33,7 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
} }
}; };
export const toggleRightPanelCollapsed = ( export const toggleRightPanelCollapsed = ({ dispatch, state }, e = undefined) => {
{ dispatch, state },
e = undefined,
) => {
if (e) { if (e) {
$(e.currentTarget) $(e.currentTarget)
.tooltip('hide') .tooltip('hide')
...@@ -137,6 +134,18 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => { ...@@ -137,6 +134,18 @@ export const updateDelayViewerUpdated = ({ commit }, delay) => {
commit(types.UPDATE_DELAY_VIEWER_CHANGE, delay); commit(types.UPDATE_DELAY_VIEWER_CHANGE, delay);
}; };
export const updateActivityBarView = ({ commit }, view) => {
commit(types.UPDATE_ACTIVITY_BAR_VIEW, view);
};
export const setEmptyStateSvgs = ({ commit }, svgs) => {
commit(types.SET_EMPTY_STATE_SVGS, svgs);
};
export const setCurrentBranchId = ({ commit }, currentBranchId) => {
commit(types.SET_CURRENT_BRANCH, currentBranchId);
};
export const toggleFileFinder = ({ commit }, fileFindVisible) => export const toggleFileFinder = ({ commit }, fileFindVisible) =>
commit(types.TOGGLE_FILE_FINDER, fileFindVisible); commit(types.TOGGLE_FILE_FINDER, fileFindVisible);
......
...@@ -193,7 +193,7 @@ export const unstageChange = ({ commit }, path) => { ...@@ -193,7 +193,7 @@ export const unstageChange = ({ commit }, path) => {
}; };
export const openPendingTab = ({ commit, getters, dispatch, state }, { file, keyPrefix }) => { export const openPendingTab = ({ commit, getters, dispatch, state }, { file, keyPrefix }) => {
if (getters.activeFile && getters.activeFile === file && state.viewer === 'diff') { if (getters.activeFile && getters.activeFile.path === file.path && state.viewer === 'diff') {
return false; return false;
} }
......
...@@ -55,7 +55,6 @@ export const getBranchData = ( ...@@ -55,7 +55,6 @@ export const getBranchData = (
branch: data, branch: data,
}); });
commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id }); commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id });
commit(types.SET_CURRENT_BRANCH, branchId);
resolve(data); resolve(data);
}) })
.catch(() => { .catch(() => {
......
import { __ } from '~/locale';
export const activeFile = state => state.openFiles.find(file => file.active) || null; export const activeFile = state => state.openFiles.find(file => file.active) || null;
export const addedFiles = state => state.changedFiles.filter(f => f.tempFile); export const addedFiles = state => state.changedFiles.filter(f => f.tempFile);
...@@ -30,15 +28,12 @@ export const currentMergeRequest = state => { ...@@ -30,15 +28,12 @@ export const currentMergeRequest = state => {
return null; return null;
}; };
// eslint-disable-next-line no-confusing-arrow export const currentProject = state => state.projects[state.currentProjectId];
export const collapseButtonIcon = state =>
state.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
export const hasChanges = state => !!state.changedFiles.length || !!state.stagedFiles.length; export const currentTree = state =>
state.trees[`${state.currentProjectId}/${state.currentBranchId}`];
// eslint-disable-next-line no-confusing-arrow export const hasChanges = state => !!state.changedFiles.length || !!state.stagedFiles.length;
export const collapseButtonTooltip = state =>
state.rightPanelCollapsed ? __('Expand sidebar') : __('Collapse sidebar');
export const hasMergeRequest = state => !!state.currentMergeRequestId; export const hasMergeRequest = state => !!state.currentMergeRequestId;
......
...@@ -5,6 +5,7 @@ export const SET_LAST_COMMIT_MSG = 'SET_LAST_COMMIT_MSG'; ...@@ -5,6 +5,7 @@ export const SET_LAST_COMMIT_MSG = 'SET_LAST_COMMIT_MSG';
export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED'; export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED';
export const SET_RIGHT_PANEL_COLLAPSED = 'SET_RIGHT_PANEL_COLLAPSED'; export const SET_RIGHT_PANEL_COLLAPSED = 'SET_RIGHT_PANEL_COLLAPSED';
export const SET_RESIZING_STATUS = 'SET_RESIZING_STATUS'; export const SET_RESIZING_STATUS = 'SET_RESIZING_STATUS';
export const SET_EMPTY_STATE_SVGS = 'SET_EMPTY_STATE_SVGS';
// Project Mutation Types // Project Mutation Types
export const SET_PROJECT = 'SET_PROJECT'; export const SET_PROJECT = 'SET_PROJECT';
...@@ -59,4 +60,5 @@ export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT'; ...@@ -59,4 +60,5 @@ export const UPDATE_FILE_AFTER_COMMIT = 'UPDATE_FILE_AFTER_COMMIT';
export const ADD_PENDING_TAB = 'ADD_PENDING_TAB'; export const ADD_PENDING_TAB = 'ADD_PENDING_TAB';
export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB'; export const REMOVE_PENDING_TAB = 'REMOVE_PENDING_TAB';
export const UPDATE_ACTIVITY_BAR_VIEW = 'UPDATE_ACTIVITY_BAR_VIEW';
export const TOGGLE_FILE_FINDER = 'TOGGLE_FILE_FINDER'; export const TOGGLE_FILE_FINDER = 'TOGGLE_FILE_FINDER';
...@@ -100,6 +100,21 @@ export default { ...@@ -100,6 +100,21 @@ export default {
delayViewerUpdated, delayViewerUpdated,
}); });
}, },
[types.UPDATE_ACTIVITY_BAR_VIEW](state, currentActivityView) {
Object.assign(state, {
currentActivityView,
});
},
[types.SET_EMPTY_STATE_SVGS](
state,
{ emptyStateSvgPath, noChangesStateSvgPath, committedStateSvgPath },
) {
Object.assign(state, {
emptyStateSvgPath,
noChangesStateSvgPath,
committedStateSvgPath,
});
},
[types.TOGGLE_FILE_FINDER](state, fileFindVisible) { [types.TOGGLE_FILE_FINDER](state, fileFindVisible) {
Object.assign(state, { Object.assign(state, {
fileFindVisible, fileFindVisible,
......
...@@ -192,6 +192,10 @@ export default { ...@@ -192,6 +192,10 @@ export default {
return acc.concat(f); return acc.concat(f);
}, []); }, []);
} else {
openFiles = state.openFiles.map(f =>
Object.assign(f, { active: f.key === key, opened: f.key === key }),
);
} }
Object.assign(state, { openFiles }); Object.assign(state, { openFiles });
......
import { activityBarViews } from '../constants';
export default () => ({ export default () => ({
currentProjectId: '', currentProjectId: '',
currentBranchId: '', currentBranchId: '',
...@@ -18,5 +20,6 @@ export default () => ({ ...@@ -18,5 +20,6 @@ export default () => ({
entries: {}, entries: {},
viewer: 'editor', viewer: 'editor',
delayViewerUpdated: false, delayViewerUpdated: false,
currentActivityView: activityBarViews.edit,
fileFindVisible: false, fileFindVisible: false,
}); });
...@@ -177,25 +177,6 @@ ...@@ -177,25 +177,6 @@
} }
} }
// Web IDE
.ide-sidebar-link {
color: $color-200;
background-color: $color-700;
&:hover,
&:focus {
background-color: $color-500;
}
&:active {
background: $color-800;
}
}
.branch-container {
border-left-color: $color-700;
}
.branch-header-title { .branch-header-title {
color: $color-700; color: $color-700;
} }
...@@ -203,6 +184,18 @@ ...@@ -203,6 +184,18 @@
.ide-file-list .file.file-active { .ide-file-list .file.file-active {
color: $color-700; color: $color-700;
} }
.ide-sidebar-link {
&:hover,
&:focus,
&.active {
color: $color-700;
}
&.active {
box-shadow: inset 3px 0 $color-700;
}
}
} }
body { body {
...@@ -343,9 +336,5 @@ body { ...@@ -343,9 +336,5 @@ body {
.sidebar-top-level-items > li.active .badge { .sidebar-top-level-items > li.active .badge {
color: $theme-gray-900; color: $theme-gray-900;
} }
.ide-sidebar-link {
color: $white-light;
}
} }
} }
...@@ -55,6 +55,7 @@ ...@@ -55,6 +55,7 @@
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
max-width: inherit; max-width: inherit;
line-height: 22px;
svg { svg {
vertical-align: middle; vertical-align: middle;
...@@ -78,7 +79,6 @@ ...@@ -78,7 +79,6 @@
.ide-new-btn { .ide-new-btn {
display: none; display: none;
margin-bottom: -4px; margin-bottom: -4px;
margin-right: -8px;
} }
&:hover, &:hover,
...@@ -111,20 +111,12 @@ ...@@ -111,20 +111,12 @@
.file-col-commit-message { .file-col-commit-message {
display: flex; display: flex;
overflow: visible; overflow: visible;
padding: 6px 12px; padding: 6px 10px;
} }
.multi-file-loading-container { .multi-file-loading-container {
margin-top: 10px; margin-top: 10px;
padding: 10px; padding: 10px;
.animation-container {
background: $gray-light;
div {
background: $gray-light;
}
}
} }
.multi-file-table-col-commit-message { .multi-file-table-col-commit-message {
...@@ -151,7 +143,17 @@ ...@@ -151,7 +143,17 @@
} }
li { li {
position: relative; display: flex;
align-items: center;
padding: $grid-size $gl-padding;
background-color: $gray-normal;
border-right: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
&.active {
background-color: $white-light;
border-bottom-color: $white-light;
}
} }
.dropdown { .dropdown {
...@@ -174,41 +176,36 @@ ...@@ -174,41 +176,36 @@
} }
.multi-file-tab { .multi-file-tab {
@include str-truncated(150px); @include str-truncated(141px);
padding: ($gl-padding / 2) ($gl-padding + 12) ($gl-padding / 2) $gl-padding;
background-color: $gray-normal;
border-right: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
cursor: pointer; cursor: pointer;
svg { svg {
vertical-align: middle; vertical-align: middle;
} }
&.active {
background-color: $white-light;
border-bottom-color: $white-light;
}
} }
.multi-file-tab-close { .multi-file-tab-close {
position: absolute;
right: 8px;
top: 50%;
width: 16px; width: 16px;
height: 16px; height: 16px;
padding: 0; padding: 0;
margin-left: $grid-size;
background: none; background: none;
border: 0; border: 0;
border-radius: $border-radius-default; border-radius: $border-radius-default;
color: $theme-gray-900; color: $theme-gray-900;
transform: translateY(-50%);
svg { svg {
position: relative; position: relative;
top: -1px; top: -1px;
} }
.ide-file-changed-icon {
display: block;
position: relative;
top: 1px;
right: 3px;
}
&:hover { &:hover {
background-color: $theme-gray-200; background-color: $theme-gray-200;
} }
...@@ -311,6 +308,7 @@ ...@@ -311,6 +308,7 @@
.multi-file-editor-holder { .multi-file-editor-holder {
height: 100%; height: 100%;
min-height: 0;
} }
.preview-container { .preview-container {
...@@ -429,27 +427,27 @@ ...@@ -429,27 +427,27 @@
.multi-file-commit-panel { .multi-file-commit-panel {
display: flex; display: flex;
position: relative; position: relative;
flex-direction: column;
width: 340px; width: 340px;
padding: 0; padding: 0;
background-color: $gray-light; background-color: $gray-light;
padding-right: 3px; padding-right: 1px;
.projects-sidebar { .context-header {
display: flex; width: auto;
flex-direction: column; margin-right: 0;
height: 100%;
.context-header { a:hover,
width: auto; a:focus {
margin-right: 0; text-decoration: none;
} }
} }
.multi-file-commit-panel-inner { .multi-file-commit-panel-inner {
display: flex; display: flex;
flex: 1;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
width: 0;
} }
.multi-file-commit-panel-inner-scroll { .multi-file-commit-panel-inner-scroll {
...@@ -457,68 +455,10 @@ ...@@ -457,68 +455,10 @@
flex: 1; flex: 1;
flex-direction: column; flex-direction: column;
overflow: auto; overflow: auto;
} background-color: $white-light;
border-left: 1px solid $white-dark;
&.is-collapsed {
width: 60px;
.multi-file-commit-list {
padding-top: $gl-padding;
overflow: hidden;
}
.multi-file-context-bar-icon {
align-items: center;
svg {
float: none;
margin: 0;
}
}
}
.branch-container {
border-left: 4px solid;
margin-bottom: $gl-bar-padding;
}
.branch-header {
background: $white-dark;
display: flex;
}
.branch-header-title {
flex: 1;
padding: $grid-size $gl-padding;
font-weight: $gl-font-weight-bold;
svg {
vertical-align: middle;
}
}
.branch-header-btns {
padding: $gl-vert-padding $gl-padding;
}
.left-collapse-btn {
display: none;
background: $gray-light;
text-align: left;
border-top: 1px solid $white-dark; border-top: 1px solid $white-dark;
border-top-left-radius: $border-radius-small;
svg {
vertical-align: middle;
}
}
}
.multi-file-context-bar-icon {
padding: 10px;
svg {
margin-right: 10px;
float: left;
} }
} }
...@@ -802,7 +742,7 @@ ...@@ -802,7 +742,7 @@
position: absolute; position: absolute;
top: 0; top: 0;
bottom: 0; bottom: 0;
width: 3px; width: 1px;
background-color: $white-dark; background-color: $white-dark;
&.dragright { &.dragright {
...@@ -816,32 +756,11 @@ ...@@ -816,32 +756,11 @@
.ide-commit-list-container { .ide-commit-list-container {
display: flex; display: flex;
flex: 1;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
min-height: 140px;
padding: 0 16px; padding: 0 16px;
&:not(.is-collapsed) {
flex: 1;
min-height: 140px;
}
&.is-collapsed {
.multi-file-commit-panel-header {
margin-left: -$gl-padding;
margin-right: -$gl-padding;
svg {
margin-left: auto;
margin-right: auto;
}
.multi-file-commit-panel-collapse-btn {
margin-right: auto;
margin-left: auto;
border-left: 0;
}
}
}
} }
.ide-staged-action-btn { .ide-staged-action-btn {
...@@ -864,17 +783,55 @@ ...@@ -864,17 +783,55 @@
margin-left: 25px; margin-left: 25px;
} }
.ide-external-links {
p {
margin: 0;
}
}
.ide-sidebar-link { .ide-sidebar-link {
padding: $gl-padding-8 $gl-padding;
display: flex; display: flex;
align-items: center; align-items: center;
font-weight: $gl-font-weight-bold; position: relative;
height: 60px;
width: 100%;
padding: 0 $gl-padding;
color: $gl-text-color-secondary;
background-color: transparent;
border: 0;
border-top: 1px solid transparent;
border-bottom: 1px solid transparent;
outline: 0;
svg {
margin: 0 auto;
}
&:hover {
background-color: $theme-gray-100;
}
&:focus {
background-color: $theme-gray-200;
}
&.active {
// extend width over border of sidebar section
width: calc(100% + 1px);
padding-right: $gl-padding + 1px;
background-color: $white-light;
border-top-color: $white-dark;
border-bottom-color: $white-dark;
&::after {
content: '';
position: absolute;
right: -1px;
top: 0;
bottom: 0;
width: 1px;
background: $white-light;
}
}
}
.ide-activity-bar {
position: relative;
flex: 0 0 60px;
} }
.ide-file-finder-overlay { .ide-file-finder-overlay {
...@@ -967,3 +924,26 @@ ...@@ -967,3 +924,26 @@
background: transparent; background: transparent;
resize: none; resize: none;
} }
.ide-tree-header {
display: flex;
align-items: center;
padding: 10px 0;
margin-left: 10px;
margin-right: 10px;
border-bottom: 1px solid $white-dark;
.ide-new-btn {
margin-left: auto;
}
}
.ide-sidebar-branch-title {
font-weight: $gl-font-weight-normal;
svg {
position: relative;
top: 3px;
margin-top: -1px;
}
}
...@@ -44,12 +44,16 @@ feature 'Multi-file editor new directory', :js do ...@@ -44,12 +44,16 @@ feature 'Multi-file editor new directory', :js do
wait_for_requests wait_for_requests
find('.js-ide-commit-mode').click
click_button 'Stage all' click_button 'Stage all'
fill_in('commit-message', with: 'commit message ide') fill_in('commit-message', with: 'commit message ide')
click_button('Commit') click_button('Commit')
find('.js-ide-edit-mode').click
expect(page).to have_content('folder name') expect(page).to have_content('folder name')
end end
end end
...@@ -34,6 +34,8 @@ feature 'Multi-file editor new file', :js do ...@@ -34,6 +34,8 @@ feature 'Multi-file editor new file', :js do
wait_for_requests wait_for_requests
find('.js-ide-commit-mode').click
click_button 'Stage all' click_button 'Stage all'
fill_in('commit-message', with: 'commit message ide') fill_in('commit-message', with: 'commit message ide')
......
import Vue from 'vue';
import store from '~/ide/stores';
import { activityBarViews } from '~/ide/constants';
import ActivityBar from '~/ide/components/activity_bar.vue';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { resetStore } from '../helpers';
describe('IDE activity bar', () => {
const Component = Vue.extend(ActivityBar);
let vm;
beforeEach(() => {
Vue.set(store.state.projects, 'abcproject', {
web_url: 'testing',
});
Vue.set(store.state, 'currentProjectId', 'abcproject');
vm = createComponentWithStore(Component, store);
});
afterEach(() => {
vm.$destroy();
resetStore(vm.$store);
});
describe('goBackUrl', () => {
it('renders the Go Back link with the referrer when present', () => {
const fakeReferrer = '/example/README.md';
spyOnProperty(document, 'referrer').and.returnValue(fakeReferrer);
vm.$mount();
expect(vm.goBackUrl).toEqual(fakeReferrer);
});
it('renders the Go Back link with the project url when referrer is not present', () => {
const fakeReferrer = '';
spyOnProperty(document, 'referrer').and.returnValue(fakeReferrer);
vm.$mount();
expect(vm.goBackUrl).toEqual('testing');
});
});
describe('updateActivityBarView', () => {
beforeEach(() => {
spyOn(vm, 'updateActivityBarView');
vm.$mount();
});
it('calls updateActivityBarView with edit value on click', () => {
vm.$el.querySelector('.js-ide-edit-mode').click();
expect(vm.updateActivityBarView).toHaveBeenCalledWith(activityBarViews.edit);
});
it('calls updateActivityBarView with commit value on click', () => {
vm.$el.querySelector('.js-ide-commit-mode').click();
expect(vm.updateActivityBarView).toHaveBeenCalledWith(activityBarViews.commit);
});
});
describe('active item', () => {
beforeEach(() => {
vm.$mount();
});
it('sets edit item active', () => {
expect(vm.$el.querySelector('.js-ide-edit-mode').classList).toContain('active');
});
it('sets commit item active', done => {
vm.$store.state.currentActivityView = activityBarViews.commit;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.js-ide-commit-mode').classList).toContain('active');
done();
});
});
});
});
...@@ -10,10 +10,10 @@ describe('IDE commit panel empty state', () => { ...@@ -10,10 +10,10 @@ describe('IDE commit panel empty state', () => {
beforeEach(() => { beforeEach(() => {
const Component = Vue.extend(emptyState); const Component = Vue.extend(emptyState);
vm = createComponentWithStore(Component, store, { Vue.set(store.state, 'noChangesStateSvgPath', 'no-changes');
noChangesStateSvgPath: 'no-changes', Vue.set(store.state, 'committedStateSvgPath', 'committed-state');
committedStateSvgPath: 'committed-state',
}); vm = createComponentWithStore(Component, store);
vm.$mount(); vm.$mount();
}); });
...@@ -27,9 +27,7 @@ describe('IDE commit panel empty state', () => { ...@@ -27,9 +27,7 @@ describe('IDE commit panel empty state', () => {
describe('statusSvg', () => { describe('statusSvg', () => {
it('uses noChangesStateSvgPath when commit message is empty', () => { it('uses noChangesStateSvgPath when commit message is empty', () => {
expect(vm.statusSvg).toBe('no-changes'); expect(vm.statusSvg).toBe('no-changes');
expect(vm.$el.querySelector('img').getAttribute('src')).toBe( expect(vm.$el.querySelector('img').getAttribute('src')).toBe('no-changes');
'no-changes',
);
}); });
it('uses committedStateSvgPath when commit message exists', done => { it('uses committedStateSvgPath when commit message exists', done => {
...@@ -37,9 +35,7 @@ describe('IDE commit panel empty state', () => { ...@@ -37,9 +35,7 @@ describe('IDE commit panel empty state', () => {
Vue.nextTick(() => { Vue.nextTick(() => {
expect(vm.statusSvg).toBe('committed-state'); expect(vm.statusSvg).toBe('committed-state');
expect(vm.$el.querySelector('img').getAttribute('src')).toBe( expect(vm.$el.querySelector('img').getAttribute('src')).toBe('committed-state');
'committed-state',
);
done(); done();
}); });
...@@ -59,37 +55,4 @@ describe('IDE commit panel empty state', () => { ...@@ -59,37 +55,4 @@ describe('IDE commit panel empty state', () => {
done(); done();
}); });
}); });
describe('toggle button', () => {
it('calls store action', () => {
spyOn(vm, 'toggleRightPanelCollapsed');
vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click();
expect(vm.toggleRightPanelCollapsed).toHaveBeenCalled();
});
it('renders collapsed class', done => {
vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click();
Vue.nextTick(() => {
expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull();
done();
});
});
});
describe('collapsed state', () => {
beforeEach(done => {
vm.$store.state.rightPanelCollapsed = true;
Vue.nextTick(done);
});
it('does not render text & svg', () => {
expect(vm.$el.querySelector('img')).toBeNull();
expect(vm.$el.textContent).not.toContain('No changes');
});
});
}); });
...@@ -50,35 +50,6 @@ describe('Multi-file editor commit sidebar list', () => { ...@@ -50,35 +50,6 @@ describe('Multi-file editor commit sidebar list', () => {
}); });
}); });
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();
});
});
describe('with toggle', () => {
beforeEach(done => {
spyOn(vm, 'toggleRightPanelCollapsed');
vm.showToggle = true;
Vue.nextTick(done);
});
it('calls setPanelCollapsedStatus when clickin toggle', () => {
vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click();
expect(vm.toggleRightPanelCollapsed).toHaveBeenCalled();
});
});
describe('action button', () => { describe('action button', () => {
beforeEach(() => { beforeEach(() => {
spyOn(vm, 'stageAllChanges'); spyOn(vm, 'stageAllChanges');
......
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, {
noChangesStateSvgPath: 'svg',
committedStateSvgPath: 'svg',
});
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();
});
});
});
import Vue from 'vue';
import ideExternalLinks from '~/ide/components/ide_external_links.vue';
import createComponent from 'spec/helpers/vue_mount_component_helper';
describe('ide external links component', () => {
let vm;
let fakeReferrer;
let Component;
const fakeProjectUrl = '/project/';
beforeEach(() => {
Component = Vue.extend(ideExternalLinks);
});
afterEach(() => {
vm.$destroy();
});
describe('goBackUrl', () => {
it('renders the Go Back link with the referrer when present', () => {
fakeReferrer = '/example/README.md';
spyOnProperty(document, 'referrer').and.returnValue(fakeReferrer);
vm = createComponent(Component, {
projectUrl: fakeProjectUrl,
}).$mount();
expect(vm.goBackUrl).toEqual(fakeReferrer);
});
it('renders the Go Back link with the project url when referrer is not present', () => {
fakeReferrer = '';
spyOnProperty(document, 'referrer').and.returnValue(fakeReferrer);
vm = createComponent(Component, {
projectUrl: fakeProjectUrl,
}).$mount();
expect(vm.goBackUrl).toEqual(fakeProjectUrl);
});
});
});
import Vue from 'vue';
import ProjectTree from '~/ide/components/ide_project_tree.vue';
import createComponent from 'spec/helpers/vue_mount_component_helper';
describe('IDE project tree', () => {
const Component = Vue.extend(ProjectTree);
let vm;
beforeEach(() => {
vm = createComponent(Component, {
project: {
id: 1,
name: 'test',
web_url: gl.TEST_HOST,
avatar_url: '',
branches: [],
},
});
});
afterEach(() => {
vm.$destroy();
});
it('renders identicon when projct has no avatar', () => {
expect(vm.$el.querySelector('.identicon')).not.toBeNull();
});
it('renders avatar image if project has avatar', done => {
vm.project.avatar_url = gl.TEST_HOST;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.identicon')).toBeNull();
expect(vm.$el.querySelector('img.avatar')).not.toBeNull();
done();
});
});
});
import Vue from 'vue';
import ideRepoTree from '~/ide/components/ide_repo_tree.vue';
import createComponent from '../../helpers/vue_mount_component_helper';
import { file } from '../helpers';
describe('IdeRepoTree', () => {
let vm;
let tree;
beforeEach(() => {
const IdeRepoTree = Vue.extend(ideRepoTree);
tree = {
tree: [file()],
loading: false,
};
vm = createComponent(IdeRepoTree, {
tree,
});
});
afterEach(() => {
vm.$destroy();
});
it('renders a sidebar', () => {
expect(vm.$el.querySelector('.loading-file')).toBeNull();
expect(vm.$el.querySelector('.file')).not.toBeNull();
});
it('renders 3 loading files if tree is loading', done => {
tree.loading = true;
vm.$nextTick(() => {
expect(
vm.$el.querySelectorAll('.multi-file-loading-container').length,
).toEqual(3);
done();
});
});
});
import Vue from 'vue'; import Vue from 'vue';
import store from '~/ide/stores'; import store from '~/ide/stores';
import ideSidebar from '~/ide/components/ide_side_bar.vue'; import ideSidebar from '~/ide/components/ide_side_bar.vue';
import { activityBarViews } from '~/ide/constants';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { resetStore } from '../helpers'; import { resetStore } from '../helpers';
import { projectData } from '../mock_data';
describe('IdeSidebar', () => { describe('IdeSidebar', () => {
let vm; let vm;
...@@ -10,6 +12,9 @@ describe('IdeSidebar', () => { ...@@ -10,6 +12,9 @@ describe('IdeSidebar', () => {
beforeEach(() => { beforeEach(() => {
const Component = Vue.extend(ideSidebar); const Component = Vue.extend(ideSidebar);
store.state.currentProjectId = 'abcproject';
store.state.projects.abcproject = projectData;
vm = createComponentWithStore(Component, store).$mount(); vm = createComponentWithStore(Component, store).$mount();
}); });
...@@ -20,23 +25,33 @@ describe('IdeSidebar', () => { ...@@ -20,23 +25,33 @@ describe('IdeSidebar', () => {
}); });
it('renders a sidebar', () => { it('renders a sidebar', () => {
expect( expect(vm.$el.querySelector('.multi-file-commit-panel-inner')).not.toBeNull();
vm.$el.querySelector('.multi-file-commit-panel-inner'),
).not.toBeNull();
}); });
it('renders loading icon component', done => { it('renders loading icon component', done => {
vm.$store.state.loading = true; vm.$store.state.loading = true;
vm.$nextTick(() => { vm.$nextTick(() => {
expect( expect(vm.$el.querySelector('.multi-file-loading-container')).not.toBeNull();
vm.$el.querySelector('.multi-file-loading-container'), expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toBe(3);
).not.toBeNull();
expect(
vm.$el.querySelectorAll('.multi-file-loading-container').length,
).toBe(3);
done(); done();
}); });
}); });
describe('activityBarComponent', () => {
it('renders tree component', () => {
expect(vm.$el.querySelector('.ide-file-list')).not.toBeNull();
});
it('renders commit component', done => {
vm.$store.state.currentActivityView = activityBarViews.commit;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.multi-file-commit-panel-section')).not.toBeNull();
done();
});
});
});
}); });
...@@ -4,6 +4,7 @@ import store from '~/ide/stores'; ...@@ -4,6 +4,7 @@ import store from '~/ide/stores';
import ide from '~/ide/components/ide.vue'; import ide from '~/ide/components/ide.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { file, resetStore } from '../helpers'; import { file, resetStore } from '../helpers';
import { projectData } from '../mock_data';
describe('ide component', () => { describe('ide component', () => {
let vm; let vm;
...@@ -11,6 +12,10 @@ describe('ide component', () => { ...@@ -11,6 +12,10 @@ describe('ide component', () => {
beforeEach(() => { beforeEach(() => {
const Component = Vue.extend(ide); const Component = Vue.extend(ide);
store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'master';
store.state.projects.abcproject = Object.assign({}, projectData);
vm = createComponentWithStore(Component, store, { vm = createComponentWithStore(Component, store, {
emptyStateSvgPath: 'svg', emptyStateSvgPath: 'svg',
noChangesStateSvgPath: 'svg', noChangesStateSvgPath: 'svg',
...@@ -24,11 +29,11 @@ describe('ide component', () => { ...@@ -24,11 +29,11 @@ describe('ide component', () => {
resetStore(vm.$store); resetStore(vm.$store);
}); });
it('does not render panel right when no files open', () => { it('does not render right right when no files open', () => {
expect(vm.$el.querySelector('.panel-right')).toBeNull(); expect(vm.$el.querySelector('.panel-right')).toBeNull();
}); });
it('renders panel right when files are open', done => { it('renders right panel when files are open', done => {
vm.$store.state.trees['abcproject/mybranch'] = { vm.$store.state.trees['abcproject/mybranch'] = {
tree: [file()], tree: [file()],
}; };
......
import Vue from 'vue';
import IdeTree from '~/ide/components/ide_tree.vue';
import store from '~/ide/stores';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { resetStore, file } from '../helpers';
import { projectData } from '../mock_data';
describe('IdeRepoTree', () => {
let vm;
beforeEach(() => {
const IdeRepoTree = Vue.extend(IdeTree);
store.state.currentProjectId = 'abcproject';
store.state.currentBranchId = 'master';
store.state.projects.abcproject = Object.assign({}, projectData);
Vue.set(store.state.trees, 'abcproject/master', {
tree: [file('fileName')],
loading: false,
});
vm = createComponentWithStore(IdeRepoTree, store).$mount();
});
afterEach(() => {
vm.$destroy();
resetStore(vm.$store);
});
it('renders loading', done => {
vm.currentTree.loading = true;
vm.$nextTick(() => {
expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toBe(3);
done();
});
});
it('renders list of files', () => {
expect(vm.$el.textContent).toContain('fileName');
});
});
...@@ -12,10 +12,10 @@ describe('RepoCommitSection', () => { ...@@ -12,10 +12,10 @@ describe('RepoCommitSection', () => {
function createComponent() { function createComponent() {
const Component = Vue.extend(repoCommitSection); const Component = Vue.extend(repoCommitSection);
vm = createComponentWithStore(Component, store, { store.state.noChangesStateSvgPath = 'svg';
noChangesStateSvgPath: 'svg', store.state.committedStateSvgPath = 'commitsvg';
committedStateSvgPath: 'commitsvg',
}); vm = createComponentWithStore(Component, store);
vm.$store.state.currentProjectId = 'abcproject'; vm.$store.state.currentProjectId = 'abcproject';
vm.$store.state.currentBranchId = 'master'; vm.$store.state.currentBranchId = 'master';
...@@ -93,28 +93,20 @@ describe('RepoCommitSection', () => { ...@@ -93,28 +93,20 @@ describe('RepoCommitSection', () => {
resetStore(vm.$store); resetStore(vm.$store);
const Component = Vue.extend(repoCommitSection); const Component = Vue.extend(repoCommitSection);
vm = createComponentWithStore(Component, store, { store.state.noChangesStateSvgPath = 'nochangessvg';
noChangesStateSvgPath: 'nochangessvg', store.state.committedStateSvgPath = 'svg';
committedStateSvgPath: 'svg',
}).$mount();
expect( vm = createComponentWithStore(Component, store).$mount();
vm.$el.querySelector('.js-empty-state').textContent.trim(),
).toContain('No changes'); expect(vm.$el.querySelector('.js-empty-state').textContent.trim()).toContain('No changes');
expect( expect(vm.$el.querySelector('.js-empty-state img').getAttribute('src')).toBe('nochangessvg');
vm.$el.querySelector('.js-empty-state img').getAttribute('src'),
).toBe('nochangessvg');
}); });
}); });
it('renders a commit section', () => { it('renders a commit section', () => {
const changedFileElements = [ const changedFileElements = [...vm.$el.querySelectorAll('.multi-file-commit-list li')];
...vm.$el.querySelectorAll('.multi-file-commit-list li'),
];
const submitCommit = vm.$el.querySelector('form .btn'); const submitCommit = vm.$el.querySelector('form .btn');
const allFiles = vm.$store.state.changedFiles.concat( const allFiles = vm.$store.state.changedFiles.concat(vm.$store.state.stagedFiles);
vm.$store.state.stagedFiles,
);
expect(vm.$el.querySelector('.multi-file-commit-form')).not.toBeNull(); expect(vm.$el.querySelector('.multi-file-commit-form')).not.toBeNull();
expect(changedFileElements.length).toEqual(4); expect(changedFileElements.length).toEqual(4);
...@@ -131,9 +123,9 @@ describe('RepoCommitSection', () => { ...@@ -131,9 +123,9 @@ describe('RepoCommitSection', () => {
vm.$el.querySelector('.ide-staged-action-btn').click(); vm.$el.querySelector('.ide-staged-action-btn').click();
Vue.nextTick(() => { Vue.nextTick(() => {
expect( expect(vm.$el.querySelector('.ide-commit-list-container').textContent).toContain(
vm.$el.querySelector('.ide-commit-list-container').textContent, 'No changes',
).toContain('No changes'); );
done(); done();
}); });
...@@ -143,11 +135,9 @@ describe('RepoCommitSection', () => { ...@@ -143,11 +135,9 @@ describe('RepoCommitSection', () => {
vm.$el.querySelector('.multi-file-discard-btn .btn').click(); vm.$el.querySelector('.multi-file-discard-btn .btn').click();
Vue.nextTick(() => { Vue.nextTick(() => {
expect( expect(vm.$el.querySelector('.ide-commit-list-container').querySelectorAll('li').length).toBe(
vm.$el 1,
.querySelector('.ide-commit-list-container') );
.querySelectorAll('li').length,
).toBe(1);
done(); done();
}); });
...@@ -157,14 +147,10 @@ describe('RepoCommitSection', () => { ...@@ -157,14 +147,10 @@ describe('RepoCommitSection', () => {
vm.$el.querySelectorAll('.multi-file-discard-btn .btn')[1].click(); vm.$el.querySelectorAll('.multi-file-discard-btn .btn')[1].click();
Vue.nextTick(() => { Vue.nextTick(() => {
expect( expect(vm.$el.querySelector('.ide-commit-list-container').textContent).not.toContain('file1');
vm.$el.querySelector('.ide-commit-list-container').textContent, expect(vm.$el.querySelector('.ide-commit-list-container').querySelectorAll('li').length).toBe(
).not.toContain('file1'); 1,
expect( );
vm.$el
.querySelector('.ide-commit-list-container')
.querySelectorAll('li').length,
).toBe(1);
done(); done();
}); });
...@@ -174,9 +160,9 @@ describe('RepoCommitSection', () => { ...@@ -174,9 +160,9 @@ describe('RepoCommitSection', () => {
vm.$el.querySelectorAll('.ide-staged-action-btn')[1].click(); vm.$el.querySelectorAll('.ide-staged-action-btn')[1].click();
Vue.nextTick(() => { Vue.nextTick(() => {
expect( expect(vm.$el.querySelectorAll('.ide-commit-list-container')[1].textContent).toContain(
vm.$el.querySelectorAll('.ide-commit-list-container')[1].textContent, 'No changes',
).toContain('No changes'); );
done(); done();
}); });
...@@ -190,9 +176,7 @@ describe('RepoCommitSection', () => { ...@@ -190,9 +176,7 @@ describe('RepoCommitSection', () => {
Vue.nextTick(() => { Vue.nextTick(() => {
expect( expect(
vm.$el vm.$el.querySelectorAll('.ide-commit-list-container')[1].querySelectorAll('li').length,
.querySelectorAll('.ide-commit-list-container')[1]
.querySelectorAll('li').length,
).toBe(1); ).toBe(1);
done(); done();
...@@ -208,9 +192,7 @@ describe('RepoCommitSection', () => { ...@@ -208,9 +192,7 @@ describe('RepoCommitSection', () => {
getSetTimeoutPromise() getSetTimeoutPromise()
.then(() => { .then(() => {
expect(vm.$store.state.commit.commitMessage).toBe( expect(vm.$store.state.commit.commitMessage).toBe('testing commit message');
'testing commit message',
);
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
...@@ -218,9 +200,7 @@ describe('RepoCommitSection', () => { ...@@ -218,9 +200,7 @@ describe('RepoCommitSection', () => {
describe('discard draft button', () => { describe('discard draft button', () => {
it('hidden when commitMessage is empty', () => { it('hidden when commitMessage is empty', () => {
expect( expect(vm.$el.querySelector('.multi-file-commit-form .btn-default')).toBeNull();
vm.$el.querySelector('.multi-file-commit-form .btn-default'),
).toBeNull();
}); });
it('resets commitMessage when clicking discard button', done => { it('resets commitMessage when clicking discard button', done => {
...@@ -232,9 +212,7 @@ describe('RepoCommitSection', () => { ...@@ -232,9 +212,7 @@ describe('RepoCommitSection', () => {
}) })
.then(Vue.nextTick) .then(Vue.nextTick)
.then(() => { .then(() => {
expect(vm.$store.state.commit.commitMessage).not.toBe( expect(vm.$store.state.commit.commitMessage).not.toBe('testing commit message');
'testing commit message',
);
}) })
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
......
...@@ -26,8 +26,8 @@ describe('RepoTabs', () => { ...@@ -26,8 +26,8 @@ describe('RepoTabs', () => {
const tabs = [...vm.$el.querySelectorAll('.multi-file-tab')]; const tabs = [...vm.$el.querySelectorAll('.multi-file-tab')];
expect(tabs.length).toEqual(2); expect(tabs.length).toEqual(2);
expect(tabs[0].classList.contains('active')).toEqual(true); expect(tabs[0].parentNode.classList.contains('active')).toEqual(true);
expect(tabs[1].classList.contains('active')).toEqual(false); expect(tabs[1].parentNode.classList.contains('active')).toEqual(false);
done(); done();
}); });
......
// eslint-disable-next-line import/prefer-default-export
export const projectData = {
id: 1,
name: 'abcproject',
web_url: '',
avatar_url: '',
path: '',
name_with_namespace: 'namespace/abcproject',
branches: {
master: {
treeId: 'abcproject/master',
},
},
};
import actions, { stageAllChanges, unstageAllChanges, toggleFileFinder } from '~/ide/stores/actions'; import actions, {
stageAllChanges,
unstageAllChanges,
toggleFileFinder,
setCurrentBranchId,
setEmptyStateSvgs,
updateActivityBarView,
} from '~/ide/stores/actions';
import store from '~/ide/stores'; import store from '~/ide/stores';
import * as types from '~/ide/stores/mutation_types'; import * as types from '~/ide/stores/mutation_types';
import router from '~/ide/ide_router'; import router from '~/ide/ide_router';
...@@ -340,6 +347,45 @@ describe('Multi-file store actions', () => { ...@@ -340,6 +347,45 @@ describe('Multi-file store actions', () => {
}); });
}); });
describe('updateActivityBarView', () => {
it('commits UPDATE_ACTIVITY_BAR_VIEW', done => {
testAction(
updateActivityBarView,
'test',
{},
[{ type: 'UPDATE_ACTIVITY_BAR_VIEW', payload: 'test' }],
[],
done,
);
});
});
describe('setEmptyStateSvgs', () => {
it('commits setEmptyStateSvgs', done => {
testAction(
setEmptyStateSvgs,
'svg',
{},
[{ type: 'SET_EMPTY_STATE_SVGS', payload: 'svg' }],
[],
done,
);
});
});
describe('setCurrentBranchId', () => {
it('commits setCurrentBranchId', done => {
testAction(
setCurrentBranchId,
'branchId',
{},
[{ type: 'SET_CURRENT_BRANCH', payload: 'branchId' }],
[],
done,
);
});
});
describe('toggleFileFinder', () => { describe('toggleFileFinder', () => {
it('commits TOGGLE_FILE_FINDER', done => { it('commits TOGGLE_FILE_FINDER', done => {
testAction( testAction(
......
...@@ -37,12 +37,6 @@ describe('IDE store getters', () => { ...@@ -37,12 +37,6 @@ describe('IDE store getters', () => {
expect(modifiedFiles.length).toBe(1); expect(modifiedFiles.length).toBe(1);
expect(modifiedFiles[0].name).toBe('changed'); expect(modifiedFiles[0].name).toBe('changed');
}); });
it('returns angle left when collapsed', () => {
localState.rightPanelCollapsed = true;
expect(getters.collapseButtonIcon(localState)).toBe('angle-double-left');
});
}); });
describe('currentMergeRequest', () => { describe('currentMergeRequest', () => {
......
...@@ -87,6 +87,28 @@ describe('Multi-file store mutations', () => { ...@@ -87,6 +87,28 @@ describe('Multi-file store mutations', () => {
}); });
}); });
describe('UPDATE_ACTIVITY_BAR_VIEW', () => {
it('updates currentActivityBar', () => {
mutations.UPDATE_ACTIVITY_BAR_VIEW(localState, 'test');
expect(localState.currentActivityView).toBe('test');
});
});
describe('SET_EMPTY_STATE_SVGS', () => {
it('updates empty state SVGs', () => {
mutations.SET_EMPTY_STATE_SVGS(localState, {
emptyStateSvgPath: 'emptyState',
noChangesStateSvgPath: 'noChanges',
committedStateSvgPath: 'commited',
});
expect(localState.emptyStateSvgPath).toBe('emptyState');
expect(localState.noChangesStateSvgPath).toBe('noChanges');
expect(localState.committedStateSvgPath).toBe('commited');
});
});
describe('TOGGLE_FILE_FINDER', () => { describe('TOGGLE_FILE_FINDER', () => {
it('updates fileFindVisible', () => { it('updates fileFindVisible', () => {
mutations.TOGGLE_FILE_FINDER(localState, true); mutations.TOGGLE_FILE_FINDER(localState, true);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment