summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFilipa Lacerda <filipa@gitlab.com>2017-10-17 17:40:13 +0000
committerFilipa Lacerda <filipa@gitlab.com>2017-10-17 17:40:13 +0000
commitb3f749036ea919de3982c81b157ab2d790ecb4c5 (patch)
tree2bc62622bd4eafb45e822a5977132ad9584e7d1d
parentc3d310384065d21de245397741267cffc4a6e6a4 (diff)
parentbdbcf58ac085e4c96a6eb0bc2a2be767e3b74b9d (diff)
downloadgitlab-ce-b3f749036ea919de3982c81b157ab2d790ecb4c5.tar.gz
Merge branch 'ph-multi-file-editor-restructure-data' into 'master'
Refactored multi-file data structure See merge request gitlab-org/gitlab-ce!14862
-rw-r--r--app/assets/javascripts/repo/components/repo.vue4
-rw-r--r--app/assets/javascripts/repo/components/repo_commit_section.vue4
-rw-r--r--app/assets/javascripts/repo/components/repo_edit_button.vue4
-rw-r--r--app/assets/javascripts/repo/components/repo_editor.vue6
-rw-r--r--app/assets/javascripts/repo/components/repo_file.vue161
-rw-r--r--app/assets/javascripts/repo/components/repo_file_buttons.vue4
-rw-r--r--app/assets/javascripts/repo/components/repo_file_options.vue25
-rw-r--r--app/assets/javascripts/repo/components/repo_loading_file.vue87
-rw-r--r--app/assets/javascripts/repo/components/repo_prev_directory.vue58
-rw-r--r--app/assets/javascripts/repo/components/repo_preview.vue4
-rw-r--r--app/assets/javascripts/repo/components/repo_sidebar.vue80
-rw-r--r--app/assets/javascripts/repo/components/repo_tab.vue51
-rw-r--r--app/assets/javascripts/repo/components/repo_tabs.vue51
-rw-r--r--app/assets/javascripts/repo/event_hub.js3
-rw-r--r--app/assets/javascripts/repo/helpers/repo_helper.js182
-rw-r--r--app/assets/javascripts/repo/index.js3
-rw-r--r--app/assets/javascripts/repo/stores/repo_store.js46
-rw-r--r--app/assets/stylesheets/framework/animations.scss7
-rw-r--r--app/assets/stylesheets/pages/repo.scss82
-rw-r--r--app/controllers/projects/tree_controller.rb1
-rw-r--r--app/views/shared/repo/_repo.html.haml3
-rw-r--r--spec/javascripts/repo/components/repo_commit_section_spec.js1
-rw-r--r--spec/javascripts/repo/components/repo_edit_button_spec.js12
-rw-r--r--spec/javascripts/repo/components/repo_editor_spec.js5
-rw-r--r--spec/javascripts/repo/components/repo_file_buttons_spec.js4
-rw-r--r--spec/javascripts/repo/components/repo_file_options_spec.js33
-rw-r--r--spec/javascripts/repo/components/repo_file_spec.js103
-rw-r--r--spec/javascripts/repo/components/repo_loading_file_spec.js29
-rw-r--r--spec/javascripts/repo/components/repo_prev_directory_spec.js16
-rw-r--r--spec/javascripts/repo/components/repo_sidebar_spec.js117
-rw-r--r--spec/javascripts/repo/components/repo_tab_spec.js40
-rw-r--r--spec/javascripts/repo/components/repo_tabs_spec.js18
-rw-r--r--spec/javascripts/repo/mock_data.js13
33 files changed, 522 insertions, 735 deletions
diff --git a/app/assets/javascripts/repo/components/repo.vue b/app/assets/javascripts/repo/components/repo.vue
index cc60aa5939c..0a89a9f16cb 100644
--- a/app/assets/javascripts/repo/components/repo.vue
+++ b/app/assets/javascripts/repo/components/repo.vue
@@ -11,7 +11,9 @@ import Helper from '../helpers/repo_helper';
import MonacoLoaderHelper from '../helpers/monaco_loader_helper';
export default {
- data: () => Store,
+ data() {
+ return Store;
+ },
mixins: [RepoMixin],
components: {
RepoSidebar,
diff --git a/app/assets/javascripts/repo/components/repo_commit_section.vue b/app/assets/javascripts/repo/components/repo_commit_section.vue
index c0dc4c8cd8b..185cd90ac06 100644
--- a/app/assets/javascripts/repo/components/repo_commit_section.vue
+++ b/app/assets/javascripts/repo/components/repo_commit_section.vue
@@ -9,7 +9,9 @@ import { visitUrl } from '../../lib/utils/url_utility';
export default {
mixins: [RepoMixin],
- data: () => Store,
+ data() {
+ return Store;
+ },
components: {
PopupDialog,
diff --git a/app/assets/javascripts/repo/components/repo_edit_button.vue b/app/assets/javascripts/repo/components/repo_edit_button.vue
index 353142edeb7..e6e8b2e5205 100644
--- a/app/assets/javascripts/repo/components/repo_edit_button.vue
+++ b/app/assets/javascripts/repo/components/repo_edit_button.vue
@@ -3,7 +3,9 @@ import Store from '../stores/repo_store';
import RepoMixin from '../mixins/repo_mixin';
export default {
- data: () => Store,
+ data() {
+ return Store;
+ },
mixins: [RepoMixin],
computed: {
buttonLabel() {
diff --git a/app/assets/javascripts/repo/components/repo_editor.vue b/app/assets/javascripts/repo/components/repo_editor.vue
index 2c597a3cd65..4639bee6d66 100644
--- a/app/assets/javascripts/repo/components/repo_editor.vue
+++ b/app/assets/javascripts/repo/components/repo_editor.vue
@@ -5,7 +5,9 @@ import Service from '../services/repo_service';
import Helper from '../helpers/repo_helper';
const RepoEditor = {
- data: () => Store,
+ data() {
+ return Store;
+ },
destroyed() {
if (Helper.monacoInstance) {
@@ -93,7 +95,7 @@ const RepoEditor = {
},
blobRaw() {
- if (Helper.monacoInstance && !this.isTree) {
+ if (Helper.monacoInstance) {
this.setupEditor();
}
},
diff --git a/app/assets/javascripts/repo/components/repo_file.vue b/app/assets/javascripts/repo/components/repo_file.vue
index 8b9cbd23456..c7e69340f17 100644
--- a/app/assets/javascripts/repo/components/repo_file.vue
+++ b/app/assets/javascripts/repo/components/repo_file.vue
@@ -1,107 +1,78 @@
<script>
-import TimeAgoMixin from '../../vue_shared/mixins/timeago';
+ import timeAgoMixin from '../../vue_shared/mixins/timeago';
+ import eventHub from '../event_hub';
+ import repoMixin from '../mixins/repo_mixin';
-const RepoFile = {
- mixins: [TimeAgoMixin],
- props: {
- file: {
- type: Object,
- required: true,
+ export default {
+ mixins: [
+ repoMixin,
+ timeAgoMixin,
+ ],
+ props: {
+ file: {
+ type: Object,
+ required: true,
+ },
},
- isMini: {
- type: Boolean,
- required: false,
- default: false,
+ computed: {
+ fileIcon() {
+ const classObj = {
+ 'fa-spinner fa-spin': this.file.loading,
+ [this.file.icon]: !this.file.loading,
+ 'fa-folder-open': !this.file.loading && this.file.opened,
+ };
+ return classObj;
+ },
+ levelIndentation() {
+ return {
+ marginLeft: `${this.file.level * 16}px`,
+ };
+ },
},
- loading: {
- type: Object,
- required: false,
- default() { return { tree: false }; },
+ methods: {
+ linkClicked(file) {
+ eventHub.$emit('fileNameClicked', file);
+ },
},
- hasFiles: {
- type: Boolean,
- required: false,
- default: false,
- },
- activeFile: {
- type: Object,
- required: true,
- },
- },
-
- computed: {
- canShowFile() {
- return !this.loading.tree || this.hasFiles;
- },
-
- fileIcon() {
- const classObj = {
- 'fa-spinner fa-spin': this.file.loading,
- [this.file.icon]: !this.file.loading,
- };
- return classObj;
- },
-
- fileIndentation() {
- return {
- 'margin-left': `${this.file.level * 10}px`,
- };
- },
-
- activeFileClass() {
- return {
- active: this.activeFile.url === this.file.url,
- };
- },
- },
-
- methods: {
- linkClicked(file) {
- this.$emit('linkclicked', file);
- },
- },
-};
-
-export default RepoFile;
+ };
</script>
<template>
-<tr
- v-if="canShowFile"
- class="file"
- :class="activeFileClass"
- @click.prevent="linkClicked(file)">
- <td>
- <i
- class="fa fa-fw file-icon"
- :class="fileIcon"
- :style="fileIndentation"
- aria-label="file icon">
- </i>
- <a
- :href="file.url"
- class="repo-file-name"
- :title="file.url">
- {{file.name}}
- </a>
- </td>
+ <tr
+ class="file"
+ @click.prevent="linkClicked(file)">
+ <td>
+ <i
+ class="fa fa-fw file-icon"
+ :class="fileIcon"
+ :style="levelIndentation"
+ aria-hidden="true"
+ >
+ </i>
+ <a
+ :href="file.url"
+ class="repo-file-name"
+ >
+ {{ file.name }}
+ </a>
+ </td>
- <template v-if="!isMini">
- <td class="hidden-sm hidden-xs">
- <div class="commit-message">
- <a @click.stop :href="file.lastCommitUrl">
- {{file.lastCommitMessage}}
+ <template v-if="!isMini">
+ <td class="hidden-sm hidden-xs">
+ <a
+ @click.stop
+ :href="file.lastCommit.url"
+ class="commit-message"
+ >
+ {{ file.lastCommit.message }}
</a>
- </div>
- </td>
+ </td>
- <td class="hidden-xs text-right">
- <span
- class="commit-update"
- :title="tooltipTitle(file.lastCommitUpdate)">
- {{timeFormated(file.lastCommitUpdate)}}
- </span>
- </td>
- </template>
-</tr>
+ <td class="commit-update hidden-xs text-right">
+ <span :title="tooltipTitle(file.lastCommit.updatedAt)">
+ {{ timeFormated(file.lastCommit.updatedAt) }}
+ </span>
+ </td>
+ </template>
+ </tr>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_file_buttons.vue b/app/assets/javascripts/repo/components/repo_file_buttons.vue
index e43ef366f47..03cd219e718 100644
--- a/app/assets/javascripts/repo/components/repo_file_buttons.vue
+++ b/app/assets/javascripts/repo/components/repo_file_buttons.vue
@@ -4,7 +4,9 @@ import Helper from '../helpers/repo_helper';
import RepoMixin from '../mixins/repo_mixin';
const RepoFileButtons = {
- data: () => Store,
+ data() {
+ return Store;
+ },
mixins: [RepoMixin],
diff --git a/app/assets/javascripts/repo/components/repo_file_options.vue b/app/assets/javascripts/repo/components/repo_file_options.vue
deleted file mode 100644
index 6a15755f029..00000000000
--- a/app/assets/javascripts/repo/components/repo_file_options.vue
+++ /dev/null
@@ -1,25 +0,0 @@
-<script>
-const RepoFileOptions = {
- props: {
- isMini: {
- type: Boolean,
- required: false,
- default: false,
- },
- projectName: {
- type: String,
- required: true,
- },
- },
-};
-
-export default RepoFileOptions;
-</script>
-
-<template>
- <tr v-if="isMini" class="repo-file-options">
- <td>
- <span class="title">{{projectName}}</span>
- </td>
- </tr>
-</template>
diff --git a/app/assets/javascripts/repo/components/repo_loading_file.vue b/app/assets/javascripts/repo/components/repo_loading_file.vue
index bc8c64c8362..832b45b2b29 100644
--- a/app/assets/javascripts/repo/components/repo_loading_file.vue
+++ b/app/assets/javascripts/repo/components/repo_loading_file.vue
@@ -1,43 +1,23 @@
<script>
-const RepoLoadingFile = {
- props: {
- loading: {
- type: Object,
- required: false,
- default: {},
- },
- hasFiles: {
- type: Boolean,
- required: false,
- default: false,
- },
- isMini: {
- type: Boolean,
- required: false,
- default: false,
- },
- },
-
- computed: {
- showGhostLines() {
- return this.loading.tree && !this.hasFiles;
- },
- },
+ import repoMixin from '../mixins/repo_mixin';
- methods: {
- lineOfCode(n) {
- return `skeleton-line-${n}`;
+ export default {
+ mixins: [
+ repoMixin,
+ ],
+ methods: {
+ lineOfCode(n) {
+ return `skeleton-line-${n}`;
+ },
},
- },
-};
-
-export default RepoLoadingFile;
+ };
</script>
<template>
<tr
- v-if="showGhostLines"
- class="loading-file">
+ class="loading-file"
+ aria-label="Loading files"
+ >
<td>
<div
class="animation-container animation-container-small">
@@ -48,29 +28,28 @@ export default RepoLoadingFile;
</div>
</div>
</td>
-
- <td
- v-if="!isMini"
- class="hidden-sm hidden-xs">
- <div class="animation-container">
- <div
- v-for="n in 6"
- :key="n"
- :class="lineOfCode(n)">
+ <template v-if="!isMini">
+ <td
+ class="hidden-sm hidden-xs">
+ <div class="animation-container">
+ <div
+ v-for="n in 6"
+ :key="n"
+ :class="lineOfCode(n)">
+ </div>
</div>
- </div>
- </td>
+ </td>
- <td
- v-if="!isMini"
- class="hidden-xs">
- <div class="animation-container animation-container-small">
- <div
- v-for="n in 6"
- :key="n"
- :class="lineOfCode(n)">
+ <td
+ class="hidden-xs">
+ <div class="animation-container animation-container-small animation-container-right">
+ <div
+ v-for="n in 6"
+ :key="n"
+ :class="lineOfCode(n)">
+ </div>
</div>
- </div>
- </td>
+ </td>
+ </template>
</tr>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_prev_directory.vue b/app/assets/javascripts/repo/components/repo_prev_directory.vue
index bbdbdc61e38..c4bf6dcdec2 100644
--- a/app/assets/javascripts/repo/components/repo_prev_directory.vue
+++ b/app/assets/javascripts/repo/components/repo_prev_directory.vue
@@ -1,38 +1,38 @@
<script>
-import RepoMixin from '../mixins/repo_mixin';
+ import eventHub from '../event_hub';
+ import repoMixin from '../mixins/repo_mixin';
-const RepoPreviousDirectory = {
- props: {
- prevUrl: {
- type: String,
- required: true,
+ export default {
+ mixins: [
+ repoMixin,
+ ],
+ props: {
+ prevUrl: {
+ type: String,
+ required: true,
+ },
},
- },
-
- mixins: [RepoMixin],
-
- computed: {
- colSpanCondition() {
- return this.isMini ? undefined : 3;
+ computed: {
+ colSpanCondition() {
+ return this.isMini ? undefined : 3;
+ },
},
- },
-
- methods: {
- linkClicked(file) {
- this.$emit('linkclicked', file);
+ methods: {
+ linkClicked(file) {
+ eventHub.$emit('goToPreviousDirectoryClicked', file);
+ },
},
- },
-};
-
-export default RepoPreviousDirectory;
+ };
</script>
<template>
-<tr class="prev-directory">
- <td
- :colspan="colSpanCondition"
- @click.prevent="linkClicked(prevUrl)">
- <a :href="prevUrl">..</a>
- </td>
-</tr>
+ <tr class="file prev-directory">
+ <td
+ :colspan="colSpanCondition"
+ class="table-cell"
+ @click.prevent="linkClicked(prevUrl)"
+ >
+ <a :href="prevUrl">...</a>
+ </td>
+ </tr>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_preview.vue b/app/assets/javascripts/repo/components/repo_preview.vue
index a87bef6084a..b5be771d539 100644
--- a/app/assets/javascripts/repo/components/repo_preview.vue
+++ b/app/assets/javascripts/repo/components/repo_preview.vue
@@ -4,7 +4,9 @@
import Store from '../stores/repo_store';
export default {
- data: () => Store,
+ data() {
+ return Store;
+ },
computed: {
html() {
return this.activeFile.html;
diff --git a/app/assets/javascripts/repo/components/repo_sidebar.vue b/app/assets/javascripts/repo/components/repo_sidebar.vue
index e0f3c33003a..5832e603907 100644
--- a/app/assets/javascripts/repo/components/repo_sidebar.vue
+++ b/app/assets/javascripts/repo/components/repo_sidebar.vue
@@ -1,9 +1,10 @@
<script>
+import _ from 'underscore';
import Service from '../services/repo_service';
import Helper from '../helpers/repo_helper';
import Store from '../stores/repo_store';
+import eventHub from '../event_hub';
import RepoPreviousDirectory from './repo_prev_directory.vue';
-import RepoFileOptions from './repo_file_options.vue';
import RepoFile from './repo_file.vue';
import RepoLoadingFile from './repo_loading_file.vue';
import RepoMixin from '../mixins/repo_mixin';
@@ -11,21 +12,35 @@ import RepoMixin from '../mixins/repo_mixin';
export default {
mixins: [RepoMixin],
components: {
- 'repo-file-options': RepoFileOptions,
'repo-previous-directory': RepoPreviousDirectory,
'repo-file': RepoFile,
'repo-loading-file': RepoLoadingFile,
},
-
created() {
window.addEventListener('popstate', this.checkHistory);
},
destroyed() {
+ eventHub.$off('fileNameClicked', this.fileClicked);
+ eventHub.$off('goToPreviousDirectoryClicked', this.goToPreviousDirectoryClicked);
window.removeEventListener('popstate', this.checkHistory);
},
+ mounted() {
+ eventHub.$on('fileNameClicked', this.fileClicked);
+ eventHub.$on('goToPreviousDirectoryClicked', this.goToPreviousDirectoryClicked);
+ },
+ data() {
+ return Store;
+ },
+ computed: {
+ flattendFiles() {
+ const mapFiles = arr => (!arr.files.length ? [] : _.map(arr.files, a => [a, mapFiles(a)]));
- data: () => Store,
-
+ return _.chain(this.files)
+ .map(arr => [arr, mapFiles(arr)])
+ .flatten()
+ .value();
+ },
+ },
methods: {
checkHistory() {
let selectedFile = this.files.find(file => location.pathname.indexOf(file.url) > -1);
@@ -52,21 +67,21 @@ export default {
},
fileClicked(clickedFile, lineNumber) {
- let file = clickedFile;
+ const file = clickedFile;
+
if (file.loading) return;
- file.loading = true;
if (file.type === 'tree' && file.opened) {
- file = Store.removeChildFilesOfTree(file);
- file.loading = false;
+ Helper.setDirectoryToClosed(file);
Store.setActiveLine(lineNumber);
} else {
const openFile = Helper.getFileFromPath(file.url);
+
if (openFile) {
- file.loading = false;
Store.setActiveFiles(openFile);
Store.setActiveLine(lineNumber);
} else {
+ file.loading = true;
Service.url = file.url;
Helper.getContent(file)
.then(() => {
@@ -81,7 +96,7 @@ export default {
goToPreviousDirectoryClicked(prevURL) {
Service.url = prevURL;
- Helper.getContent(null)
+ Helper.getContent(null, true)
.then(() => Helper.scrollTabsRight())
.catch(Helper.loadingError);
},
@@ -92,38 +107,43 @@ export default {
<template>
<div id="sidebar" :class="{'sidebar-mini' : isMini}">
<table class="table">
- <thead v-if="!isMini">
+ <thead>
<tr>
- <th class="name">Name</th>
- <th class="hidden-sm hidden-xs last-commit">Last commit</th>
- <th class="hidden-xs last-update text-right">Last update</th>
+ <th
+ v-if="isMini"
+ class="repo-file-options title"
+ >
+ <strong class="clgray">
+ {{ projectName }}
+ </strong>
+ </th>
+ <template v-else>
+ <th class="name">
+ Name
+ </th>
+ <th class="hidden-sm hidden-xs last-commit">
+ Last commit
+ </th>
+ <th class="hidden-xs last-update text-right">
+ Last update
+ </th>
+ </template>
</tr>
</thead>
<tbody>
- <repo-file-options
- :is-mini="isMini"
- :project-name="projectName"
- />
<repo-previous-directory
- v-if="isRoot"
+ v-if="!isRoot && !loading.tree"
:prev-url="prevURL"
- @linkclicked="goToPreviousDirectoryClicked(prevURL)"/>
+ />
<repo-loading-file
+ v-if="!flattendFiles.length && loading.tree"
v-for="n in 5"
:key="n"
- :loading="loading"
- :has-files="!!files.length"
- :is-mini="isMini"
/>
<repo-file
- v-for="file in files"
+ v-for="file in flattendFiles"
:key="file.id"
:file="file"
- :is-mini="isMini"
- @linkclicked="fileClicked(file)"
- :is-tree="isTree"
- :has-files="!!files.length"
- :active-file="activeFile"
/>
</tbody>
</table>
diff --git a/app/assets/javascripts/repo/components/repo_tab.vue b/app/assets/javascripts/repo/components/repo_tab.vue
index 0d0c34ec741..098715915b0 100644
--- a/app/assets/javascripts/repo/components/repo_tab.vue
+++ b/app/assets/javascripts/repo/components/repo_tab.vue
@@ -26,11 +26,13 @@ const RepoTab = {
},
methods: {
- tabClicked: Store.setActiveFiles,
-
+ tabClicked(file) {
+ Store.setActiveFiles(file);
+ },
closeTab(file) {
if (file.changed) return;
- this.$emit('tabclosed', file);
+
+ Store.removeFromOpenedFiles(file);
},
},
};
@@ -39,25 +41,28 @@ export default RepoTab;
</script>
<template>
-<li @click="tabClicked(tab)">
- <a
- href="#0"
- class="close"
- @click.stop.prevent="closeTab(tab)"
- :aria-label="closeLabel">
- <i
- class="fa"
- :class="changedClass"
- aria-hidden="true">
- </i>
- </a>
+ <li
+ :class="{ active : tab.active }"
+ @click="tabClicked(tab)"
+ >
+ <button
+ type="button"
+ class="close-btn"
+ @click.stop.prevent="closeTab(tab)"
+ :aria-label="closeLabel">
+ <i
+ class="fa"
+ :class="changedClass"
+ aria-hidden="true">
+ </i>
+ </button>
- <a
- href="#"
- class="repo-tab"
- :title="tab.url"
- @click.prevent="tabClicked(tab)">
- {{tab.name}}
- </a>
-</li>
+ <a
+ href="#"
+ class="repo-tab"
+ :title="tab.url"
+ @click.prevent="tabClicked(tab)">
+ {{tab.name}}
+ </a>
+ </li>
</template>
diff --git a/app/assets/javascripts/repo/components/repo_tabs.vue b/app/assets/javascripts/repo/components/repo_tabs.vue
index 9c5bfc5d0cf..b57cd0960de 100644
--- a/app/assets/javascripts/repo/components/repo_tabs.vue
+++ b/app/assets/javascripts/repo/components/repo_tabs.vue
@@ -1,36 +1,29 @@
<script>
-import Store from '../stores/repo_store';
-import RepoTab from './repo_tab.vue';
-import RepoMixin from '../mixins/repo_mixin';
+ import Store from '../stores/repo_store';
+ import RepoTab from './repo_tab.vue';
+ import RepoMixin from '../mixins/repo_mixin';
-const RepoTabs = {
- mixins: [RepoMixin],
-
- components: {
- 'repo-tab': RepoTab,
- },
-
- data: () => Store,
-
- methods: {
- tabClosed(file) {
- Store.removeFromOpenedFiles(file);
+ export default {
+ mixins: [RepoMixin],
+ components: {
+ 'repo-tab': RepoTab,
},
- },
-};
-
-export default RepoTabs;
+ data() {
+ return Store;
+ },
+ };
</script>
<template>
-<ul id="tabs">
- <repo-tab
- v-for="tab in openedFiles"
- :key="tab.id"
- :tab="tab"
- :class="{'active' : tab.active}"
- @tabclosed="tabClosed"
- />
- <li class="tabs-divider" />
-</ul>
+ <ul
+ id="tabs"
+ class="list-unstyled"
+ >
+ <repo-tab
+ v-for="tab in openedFiles"
+ :key="tab.id"
+ :tab="tab"
+ />
+ <li class="tabs-divider" />
+ </ul>
</template>
diff --git a/app/assets/javascripts/repo/event_hub.js b/app/assets/javascripts/repo/event_hub.js
new file mode 100644
index 00000000000..0948c2e5352
--- /dev/null
+++ b/app/assets/javascripts/repo/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/repo/helpers/repo_helper.js b/app/assets/javascripts/repo/helpers/repo_helper.js
index 46204598e1d..dfaf9caaee7 100644
--- a/app/assets/javascripts/repo/helpers/repo_helper.js
+++ b/app/assets/javascripts/repo/helpers/repo_helper.js
@@ -1,3 +1,4 @@
+import { convertPermissionToBoolean } from '../../lib/utils/common_utils';
import Service from '../services/repo_service';
import Store from '../stores/repo_store';
import Flash from '../../flash';
@@ -25,10 +26,6 @@ const RepoHelper = {
key: '',
- isTree(data) {
- return Object.hasOwnProperty.call(data, 'blobs');
- },
-
Time: window.performance
&& window.performance.now
? window.performance
@@ -58,13 +55,20 @@ const RepoHelper = {
},
setDirectoryOpen(tree, title) {
- const file = tree;
- if (!file) return undefined;
+ if (!tree) return;
+
+ Object.assign(tree, {
+ opened: true,
+ });
+
+ RepoHelper.updateHistoryEntry(tree.url, title);
+ },
- file.opened = true;
- file.icon = 'fa-folder-open';
- RepoHelper.updateHistoryEntry(file.url, title);
- return file;
+ setDirectoryToClosed(entry) {
+ Object.assign(entry, {
+ opened: false,
+ files: [],
+ });
},
isRenderable() {
@@ -81,63 +85,23 @@ const RepoHelper = {
.catch(RepoHelper.loadingError);
},
- // when you open a directory you need to put the directory files under
- // the directory... This will merge the list of the current directory and the new list.
- getNewMergedList(inDirectory, currentList, newList) {
- const newListSorted = newList.sort(this.compareFilesCaseInsensitive);
- if (!inDirectory) return newListSorted;
- const indexOfFile = currentList.findIndex(file => file.url === inDirectory.url);
- if (!indexOfFile) return newListSorted;
- return RepoHelper.mergeNewListToOldList(newListSorted, currentList, inDirectory, indexOfFile);
- },
-
- // within the get new merged list this does the merging of the current list of files
- // and the new list of files. The files are never "in" another directory they just
- // appear like they are because of the margin.
- mergeNewListToOldList(newList, oldList, inDirectory, indexOfFile) {
- newList.reverse().forEach((newFile) => {
- const fileIndex = indexOfFile + 1;
- const file = newFile;
- file.level = inDirectory.level + 1;
- oldList.splice(fileIndex, 0, file);
- });
-
- return oldList;
- },
-
- compareFilesCaseInsensitive(a, b) {
- const aName = a.name.toLowerCase();
- const bName = b.name.toLowerCase();
- if (a.level > 0) return 0;
- if (aName < bName) { return -1; }
- if (aName > bName) { return 1; }
- return 0;
- },
+ getContent(treeOrFile, emptyFiles = false) {
+ let file = treeOrFile;
- isRoot(url) {
- // the url we are requesting -> split by the project URL. Grab the right side.
- const isRoot = !!url.split(Store.projectUrl)[1]
- // remove the first "/"
- .slice(1)
- // split this by "/"
- .split('/')
- // remove the first two items of the array... usually /tree/master.
- .slice(2)
- // we want to know the length of the array.
- // If greater than 0 not root.
- .length;
- return isRoot;
- },
+ if (!Store.files.length) {
+ Store.loading.tree = true;
+ }
- getContent(treeOrFile) {
- let file = treeOrFile;
return Service.getContent()
.then((response) => {
const data = response.data;
if (response.headers && response.headers['page-title']) data.pageTitle = response.headers['page-title'];
+ if (response.headers && response.headers['is-root'] && !Store.isInitialRoot) {
+ Store.isRoot = convertPermissionToBoolean(response.headers['is-root']);
+ Store.isInitialRoot = Store.isRoot;
+ }
- Store.isTree = RepoHelper.isTree(data);
- if (!Store.isTree) {
+ if (file && file.type === 'blob') {
if (!file) file = data;
Store.binary = data.binary;
@@ -145,38 +109,40 @@ const RepoHelper = {
// file might be undefined
RepoHelper.setBinaryDataAsBase64(data);
Store.setViewToPreview();
- } else if (!Store.isPreviewView()) {
- if (!data.render_error) {
- Service.getRaw(data.raw_path)
- .then((rawResponse) => {
- Store.blobRaw = rawResponse.data;
- data.plain = rawResponse.data;
- RepoHelper.setFile(data, file);
- }).catch(RepoHelper.loadingError);
- }
+ } else if (!Store.isPreviewView() && !data.render_error) {
+ Service.getRaw(data.raw_path)
+ .then((rawResponse) => {
+ Store.blobRaw = rawResponse.data;
+ data.plain = rawResponse.data;
+ RepoHelper.setFile(data, file);
+ }).catch(RepoHelper.loadingError);
}
if (Store.isPreviewView()) {
RepoHelper.setFile(data, file);
}
+ } else {
+ Store.loading.tree = false;
+ RepoHelper.setDirectoryOpen(file, data.pageTitle || data.name);
- // if the file tree is empty
- if (Store.files.length === 0) {
- const parentURL = Service.blobURLtoParentTree(Service.url);
- Service.url = parentURL;
- RepoHelper.getContent();
+ if (emptyFiles) {
+ Store.files = [];
}
- } else {
- // it's a tree
- if (!file) Store.isRoot = RepoHelper.isRoot(Service.url);
- file = RepoHelper.setDirectoryOpen(file, data.pageTitle || data.name);
- const newDirectory = RepoHelper.dataToListOfFiles(data);
- Store.addFilesToDirectory(file, Store.files, newDirectory);
+
+ this.addToDirectory(file, data);
+
Store.prevURL = Service.blobURLtoParentTree(Service.url);
}
}).catch(RepoHelper.loadingError);
},
+ addToDirectory(file, data) {
+ const tree = file || Store;
+ const files = tree.files.concat(this.dataToListOfFiles(data, file ? file.level + 1 : 0));
+
+ tree.files = files;
+ },
+
setFile(data, file) {
const newFile = data;
newFile.url = file.url || Service.url; // Grab the URL from service, happens on page refresh.
@@ -190,57 +156,39 @@ const RepoHelper = {
Store.setActiveFiles(newFile);
},
- serializeBlob(blob) {
- const simpleBlob = RepoHelper.serializeRepoEntity('blob', blob);
- simpleBlob.lastCommitMessage = blob.last_commit.message;
- simpleBlob.lastCommitUpdate = blob.last_commit.committed_date;
- simpleBlob.loading = false;
-
- return simpleBlob;
- },
-
- serializeTree(tree) {
- return RepoHelper.serializeRepoEntity('tree', tree);
- },
-
- serializeSubmodule(submodule) {
- return RepoHelper.serializeRepoEntity('submodule', submodule);
- },
-
- serializeRepoEntity(type, entity) {
+ serializeRepoEntity(type, entity, level = 0) {
const { url, name, icon, last_commit } = entity;
- const returnObj = {
+
+ return {
type,
name,
url,
+ level,
icon: `fa-${icon}`,
- level: 0,
+ files: [],
loading: false,
+ opened: false,
+ // eslint-disable-next-line camelcase
+ lastCommit: last_commit ? {
+ url: `${Store.projectUrl}/commit/${last_commit.id}`,
+ message: last_commit.message,
+ updatedAt: last_commit.committed_date,
+ } : {},
};
-
- if (entity.last_commit) {
- returnObj.lastCommitUrl = `${Store.projectUrl}/commit/${last_commit.id}`;
- } else {
- returnObj.lastCommitUrl = '';
- }
- return returnObj;
},
scrollTabsRight() {
- // wait for the transition. 0.1 seconds.
- setTimeout(() => {
- const tabs = document.getElementById('tabs');
- if (!tabs) return;
- tabs.scrollLeft = tabs.scrollWidth;
- }, 200);
+ const tabs = document.getElementById('tabs');
+ if (!tabs) return;
+ tabs.scrollLeft = tabs.scrollWidth;
},
- dataToListOfFiles(data) {
+ dataToListOfFiles(data, level) {
const { blobs, trees, submodules } = data;
return [
- ...blobs.map(blob => RepoHelper.serializeBlob(blob)),
- ...trees.map(tree => RepoHelper.serializeTree(tree)),
- ...submodules.map(submodule => RepoHelper.serializeSubmodule(submodule)),
+ ...trees.map(tree => RepoHelper.serializeRepoEntity('tree', tree, level)),
+ ...submodules.map(submodule => RepoHelper.serializeRepoEntity('submodule', submodule, level)),
+ ...blobs.map(blob => RepoHelper.serializeRepoEntity('blob', blob, level)),
];
},
diff --git a/app/assets/javascripts/repo/index.js b/app/assets/javascripts/repo/index.js
index 1a09f411b22..65dee7d5fd1 100644
--- a/app/assets/javascripts/repo/index.js
+++ b/app/assets/javascripts/repo/index.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import Vue from 'vue';
+import { convertPermissionToBoolean } from '../lib/utils/common_utils';
import Service from './services/repo_service';
import Store from './stores/repo_store';
import Repo from './components/repo.vue';
@@ -33,6 +34,8 @@ function setInitialStore(data) {
Store.onTopOfBranch = data.onTopOfBranch;
Store.newMrTemplateUrl = decodeURIComponent(data.newMrTemplateUrl);
Store.customBranchURL = decodeURIComponent(data.blobUrl);
+ Store.isRoot = convertPermissionToBoolean(data.root);
+ Store.isInitialRoot = convertPermissionToBoolean(data.root);
Store.currentBranch = $('button.dropdown-menu-toggle').attr('data-ref');
Store.checkIsCommitable();
Store.setBranchHash();
diff --git a/app/assets/javascripts/repo/stores/repo_store.js b/app/assets/javascripts/repo/stores/repo_store.js
index f8d29af7ffe..49d7317a17e 100644
--- a/app/assets/javascripts/repo/stores/repo_store.js
+++ b/app/assets/javascripts/repo/stores/repo_store.js
@@ -2,14 +2,13 @@ import Helper from '../helpers/repo_helper';
import Service from '../services/repo_service';
const RepoStore = {
- monaco: {},
monacoLoading: false,
service: '',
canCommit: false,
onTopOfBranch: false,
editMode: false,
- isTree: false,
- isRoot: false,
+ isRoot: null,
+ isInitialRoot: null,
prevURL: '',
projectId: '',
projectName: '',
@@ -39,23 +38,11 @@ const RepoStore = {
newMrTemplateUrl: '',
branchChanged: false,
commitMessage: '',
- binaryTypes: {
- png: false,
- md: false,
- svg: false,
- unknown: false,
- },
loading: {
tree: false,
blob: false,
},
- resetBinaryTypes() {
- Object.keys(RepoStore.binaryTypes).forEach((key) => {
- RepoStore.binaryTypes[key] = false;
- });
- },
-
setBranchHash() {
return Service.getBranch()
.then((data) => {
@@ -72,10 +59,6 @@ const RepoStore = {
RepoStore.isCommitable = RepoStore.onTopOfBranch && RepoStore.canCommit;
},
- addFilesToDirectory(inDirectory, currentList, newList) {
- RepoStore.files = Helper.getNewMergedList(inDirectory, currentList, newList);
- },
-
toggleRawPreview() {
RepoStore.activeFile.raw = !RepoStore.activeFile.raw;
RepoStore.activeFileLabel = RepoStore.activeFile.raw ? 'Display rendered file' : 'Display source';
@@ -129,30 +112,6 @@ const RepoStore = {
RepoStore.activeFileLabel = 'Display source';
},
- removeChildFilesOfTree(tree) {
- let foundTree = false;
- const treeToClose = tree;
- let canStopSearching = false;
- RepoStore.files = RepoStore.files.filter((file) => {
- const isItTheTreeWeWant = file.url === treeToClose.url;
- // if it's the next tree
- if (foundTree && file.type === 'tree' && !isItTheTreeWeWant && file.level === treeToClose.level) {
- canStopSearching = true;
- return true;
- }
- if (canStopSearching) return true;
-
- if (isItTheTreeWeWant) foundTree = true;
-
- if (foundTree) return file.level <= treeToClose.level;
- return true;
- });
-
- treeToClose.opened = false;
- treeToClose.icon = 'fa-folder';
- return treeToClose;
- },
-
removeFromOpenedFiles(file) {
if (file.type === 'tree') return;
let foundIndex;
@@ -186,6 +145,7 @@ const RepoStore = {
if (openedFilesAlreadyExists) return;
openFile.changed = false;
+ openFile.active = true;
RepoStore.openedFiles.push(openFile);
},
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index f0e6b23757f..374988bb590 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -198,6 +198,13 @@ a {
height: 12px;
}
+ &.animation-container-right {
+ .skeleton-line-2 {
+ left: 0;
+ right: 150px;
+ }
+ }
+
&::before {
animation-duration: 1s;
animation-fill-mode: forwards;
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index c36fe25f74d..ea37ccf5e3d 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -153,28 +153,13 @@
overflow-x: auto;
li {
- animation: swipeRightAppear ease-in 0.1s;
- animation-iteration-count: 1;
- transform-origin: 0% 50%;
- list-style-type: none;
+ position: relative;
background: $gray-normal;
- display: inline-block;
padding: #{$gl-padding / 2} $gl-padding;
border-right: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
- white-space: nowrap;
cursor: pointer;
- &.remove {
- animation: swipeRightDissapear ease-in 0.1s;
- animation-iteration-count: 1;
- transform-origin: 0% 50%;
-
- a {
- width: 0;
- }
- }
-
&.active {
background: $white-light;
border-bottom: none;
@@ -182,17 +167,21 @@
a {
@include str-truncated(100px);
- color: $black;
+ color: $gl-text-color;
vertical-align: middle;
text-decoration: none;
margin-right: 12px;
+ }
- &.close {
- width: auto;
- font-size: 15px;
- opacity: 1;
- margin-right: -6px;
- }
+ .close-btn {
+ position: absolute;
+ right: 8px;
+ top: 50%;
+ padding: 0;
+ background: none;
+ border: 0;
+ font-size: $gl-font-size;
+ transform: translateY(-50%);
}
.close-icon:hover {
@@ -201,9 +190,6 @@
.close-icon,
.unsaved-icon {
- float: right;
- margin-top: 3px;
- margin-left: 15px;
color: $gray-darkest;
}
@@ -222,9 +208,7 @@
#repo-file-buttons {
background-color: $white-light;
- border-bottom: 1px solid $white-normal;
padding: 5px 10px;
- position: relative;
border-top: 1px solid $white-normal;
}
@@ -287,37 +271,23 @@
overflow: auto;
}
- table {
+ .table {
margin-bottom: 0;
}
tr {
- animation: fadein 0.5s;
- cursor: pointer;
-
- &.repo-file-options td {
- padding: 0;
- border-top: none;
- background: $gray-light;
+ .repo-file-options {
+ padding: 2px 16px;
width: 100%;
- display: inline-block;
-
- &:first-child {
- border-top-left-radius: 2px;
- }
+ }
- .title {
- display: inline-block;
- font-size: 10px;
- text-transform: uppercase;
- font-weight: $gl-font-weight-bold;
- color: $gray-darkest;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- vertical-align: middle;
- padding: 2px 16px;
- }
+ .title {
+ font-size: 10px;
+ text-transform: uppercase;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ vertical-align: middle;
}
.file-icon {
@@ -329,11 +299,13 @@
}
}
+ .file {
+ cursor: pointer;
+ }
+
a {
@include str-truncated(250px);
color: $almost-black;
- display: inline-block;
- vertical-align: middle;
}
}
}
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index f3719059f88..756f7e5df8c 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -36,6 +36,7 @@ class Projects::TreeController < Projects::ApplicationController
format.json do
page_title @path.presence || _("Files"), @ref, @project.name_with_namespace
+ response.header['is-root'] = @path.empty?
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38261
Gitlab::GitalyClient.allow_n_plus_1_calls do
diff --git a/app/views/shared/repo/_repo.html.haml b/app/views/shared/repo/_repo.html.haml
index 919f19f2c23..7185f5bcc5b 100644
--- a/app/views/shared/repo/_repo.html.haml
+++ b/app/views/shared/repo/_repo.html.haml
@@ -1,4 +1,5 @@
-#repo{ data: { url: content_url,
+#repo{ data: { root: @path.empty?.to_s,
+ url: content_url,
project_name: project.name,
refs_url: refs_project_path(project, format: :json),
project_url: project_path(project),
diff --git a/spec/javascripts/repo/components/repo_commit_section_spec.js b/spec/javascripts/repo/components/repo_commit_section_spec.js
index 0635de4b30b..e09d593f04c 100644
--- a/spec/javascripts/repo/components/repo_commit_section_spec.js
+++ b/spec/javascripts/repo/components/repo_commit_section_spec.js
@@ -134,6 +134,7 @@ describe('RepoCommitSection', () => {
afterEach(() => {
vm.$destroy();
el.remove();
+ RepoStore.openedFiles = [];
});
it('shows commit message', () => {
diff --git a/spec/javascripts/repo/components/repo_edit_button_spec.js b/spec/javascripts/repo/components/repo_edit_button_spec.js
index 411514009dc..dff2fac191d 100644
--- a/spec/javascripts/repo/components/repo_edit_button_spec.js
+++ b/spec/javascripts/repo/components/repo_edit_button_spec.js
@@ -9,6 +9,10 @@ describe('RepoEditButton', () => {
return new RepoEditButton().$mount();
}
+ afterEach(() => {
+ RepoStore.openedFiles = [];
+ });
+
it('renders an edit button that toggles the view state', (done) => {
RepoStore.isCommitable = true;
RepoStore.changedFiles = [];
@@ -38,12 +42,4 @@ describe('RepoEditButton', () => {
expect(vm.$el.innerHTML).toBeUndefined();
});
-
- describe('methods', () => {
- describe('editCancelClicked', () => {
- it('sets dialog to open when there are changedFiles');
-
- it('toggles editMode and calls toggleBlobView');
- });
- });
});
diff --git a/spec/javascripts/repo/components/repo_editor_spec.js b/spec/javascripts/repo/components/repo_editor_spec.js
index 85d55d171f9..a25a600b3be 100644
--- a/spec/javascripts/repo/components/repo_editor_spec.js
+++ b/spec/javascripts/repo/components/repo_editor_spec.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import RepoStore from '~/repo/stores/repo_store';
import repoEditor from '~/repo/components/repo_editor.vue';
describe('RepoEditor', () => {
@@ -8,6 +9,10 @@ describe('RepoEditor', () => {
this.vm = new RepoEditor().$mount();
});
+ afterEach(() => {
+ RepoStore.openedFiles = [];
+ });
+
it('renders an ide container', (done) => {
this.vm.openedFiles = ['idiidid'];
this.vm.binary = false;
diff --git a/spec/javascripts/repo/components/repo_file_buttons_spec.js b/spec/javascripts/repo/components/repo_file_buttons_spec.js
index dfab51710c3..701c260224f 100644
--- a/spec/javascripts/repo/components/repo_file_buttons_spec.js
+++ b/spec/javascripts/repo/components/repo_file_buttons_spec.js
@@ -9,6 +9,10 @@ describe('RepoFileButtons', () => {
return new RepoFileButtons().$mount();
}
+ afterEach(() => {
+ RepoStore.openedFiles = [];
+ });
+
it('renders Raw, Blame, History, Permalink and Preview toggle', () => {
const activeFile = {
extension: 'md',
diff --git a/spec/javascripts/repo/components/repo_file_options_spec.js b/spec/javascripts/repo/components/repo_file_options_spec.js
deleted file mode 100644
index 9759b4bf12d..00000000000
--- a/spec/javascripts/repo/components/repo_file_options_spec.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import Vue from 'vue';
-import repoFileOptions from '~/repo/components/repo_file_options.vue';
-
-describe('RepoFileOptions', () => {
- const projectName = 'projectName';
-
- function createComponent(propsData) {
- const RepoFileOptions = Vue.extend(repoFileOptions);
-
- return new RepoFileOptions({
- propsData,
- }).$mount();
- }
-
- it('renders the title and new file/folder buttons if isMini is true', () => {
- const vm = createComponent({
- isMini: true,
- projectName,
- });
-
- expect(vm.$el.classList.contains('repo-file-options')).toBeTruthy();
- expect(vm.$el.querySelector('.title').textContent).toEqual(projectName);
- });
-
- it('does not render if isMini is false', () => {
- const vm = createComponent({
- isMini: false,
- projectName,
- });
-
- expect(vm.$el.innerHTML).toBeFalsy();
- });
-});
diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js
index 620b604f404..334bf0997ca 100644
--- a/spec/javascripts/repo/components/repo_file_spec.js
+++ b/spec/javascripts/repo/components/repo_file_spec.js
@@ -1,21 +1,11 @@
import Vue from 'vue';
import repoFile from '~/repo/components/repo_file.vue';
import RepoStore from '~/repo/stores/repo_store';
+import eventHub from '~/repo/event_hub';
+import { file } from '../mock_data';
describe('RepoFile', () => {
const updated = 'updated';
- const file = {
- icon: 'icon',
- url: 'url',
- name: 'name',
- lastCommitMessage: 'message',
- lastCommitUpdate: Date.now(),
- level: 10,
- };
- const activeFile = {
- pageTitle: 'pageTitle',
- url: 'url',
- };
const otherFile = {
html: '<p class="file-content">html</p>',
pageTitle: 'otherpageTitle',
@@ -29,12 +19,15 @@ describe('RepoFile', () => {
}).$mount();
}
+ beforeEach(() => {
+ RepoStore.openedFiles = [];
+ });
+
it('renders link, icon, name and last commit details', () => {
const RepoFile = Vue.extend(repoFile);
const vm = new RepoFile({
propsData: {
- file,
- activeFile,
+ file: file(),
},
});
spyOn(vm, 'timeFormated').and.returnValue(updated);
@@ -43,28 +36,20 @@ describe('RepoFile', () => {
const name = vm.$el.querySelector('.repo-file-name');
const fileIcon = vm.$el.querySelector('.file-icon');
- expect(vm.$el.classList.contains('active')).toBeTruthy();
- expect(vm.$el.querySelector(`.${file.icon}`).style.marginLeft).toEqual('100px');
- expect(name.title).toEqual(file.url);
- expect(name.href).toMatch(`/${file.url}`);
- expect(name.textContent.trim()).toEqual(file.name);
- expect(vm.$el.querySelector('.commit-message').textContent.trim()).toBe(file.lastCommitMessage);
+ expect(vm.$el.querySelector(`.${vm.file.icon}`).style.marginLeft).toEqual('0px');
+ expect(name.href).toMatch(`/${vm.file.url}`);
+ expect(name.textContent.trim()).toEqual(vm.file.name);
+ expect(vm.$el.querySelector('.commit-message').textContent.trim()).toBe(vm.file.lastCommit.message);
expect(vm.$el.querySelector('.commit-update').textContent.trim()).toBe(updated);
- expect(fileIcon.classList.contains(file.icon)).toBeTruthy();
- expect(fileIcon.style.marginLeft).toEqual(`${file.level * 10}px`);
+ expect(fileIcon.classList.contains(vm.file.icon)).toBeTruthy();
+ expect(fileIcon.style.marginLeft).toEqual(`${vm.file.level * 10}px`);
});
it('does render if hasFiles is true and is loading tree', () => {
const vm = createComponent({
- file,
- activeFile,
- loading: {
- tree: true,
- },
- hasFiles: true,
+ file: file(),
});
- expect(vm.$el.innerHTML).toBeTruthy();
expect(vm.$el.querySelector('.fa-spin.fa-spinner')).toBeFalsy();
});
@@ -75,75 +60,51 @@ describe('RepoFile', () => {
});
it('renders a spinner if the file is loading', () => {
- file.loading = true;
- const vm = createComponent({
- file,
- activeFile,
- loading: {
- tree: true,
- },
- hasFiles: true,
- });
-
- expect(vm.$el.innerHTML).toBeTruthy();
- expect(vm.$el.querySelector('.fa-spin.fa-spinner').style.marginLeft).toEqual(`${file.level * 10}px`);
- });
-
- it('does not render if loading tree', () => {
+ const f = file();
+ f.loading = true;
const vm = createComponent({
- file,
- activeFile,
- loading: {
- tree: true,
- },
+ file: f,
});
- expect(vm.$el.innerHTML).toBeFalsy();
+ expect(vm.$el.querySelector('.fa-spin.fa-spinner')).not.toBeNull();
+ expect(vm.$el.querySelector('.fa-spin.fa-spinner').style.marginLeft).toEqual(`${vm.file.level * 16}px`);
});
it('does not render commit message and datetime if mini', () => {
+ RepoStore.openedFiles.push(file());
+
const vm = createComponent({
- file,
- activeFile,
- isMini: true,
+ file: file(),
});
expect(vm.$el.querySelector('.commit-message')).toBeFalsy();
expect(vm.$el.querySelector('.commit-update')).toBeFalsy();
});
- it('does not set active class if file is active file', () => {
- const vm = createComponent({
- file,
- activeFile: {},
- });
-
- expect(vm.$el.classList.contains('active')).toBeFalsy();
- });
-
it('fires linkClicked when the link is clicked', () => {
const vm = createComponent({
- file,
- activeFile,
+ file: file(),
});
spyOn(vm, 'linkClicked');
- vm.$el.querySelector('.repo-file-name').click();
+ vm.$el.click();
- expect(vm.linkClicked).toHaveBeenCalledWith(file);
+ expect(vm.linkClicked).toHaveBeenCalledWith(vm.file);
});
describe('methods', () => {
describe('linkClicked', () => {
- const vm = jasmine.createSpyObj('vm', ['$emit']);
+ it('$emits fileNameClicked with file obj', () => {
+ spyOn(eventHub, '$emit');
- it('$emits linkclicked with file obj', () => {
- const theFile = {};
+ const vm = createComponent({
+ file: file(),
+ });
- repoFile.methods.linkClicked.call(vm, theFile);
+ vm.linkClicked(vm.file);
- expect(vm.$emit).toHaveBeenCalledWith('linkclicked', theFile);
+ expect(eventHub.$emit).toHaveBeenCalledWith('fileNameClicked', vm.file);
});
});
});
diff --git a/spec/javascripts/repo/components/repo_loading_file_spec.js b/spec/javascripts/repo/components/repo_loading_file_spec.js
index a030314d749..e9f95a02028 100644
--- a/spec/javascripts/repo/components/repo_loading_file_spec.js
+++ b/spec/javascripts/repo/components/repo_loading_file_spec.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import RepoStore from '~/repo/stores/repo_store';
import repoLoadingFile from '~/repo/components/repo_loading_file.vue';
describe('RepoLoadingFile', () => {
@@ -28,6 +29,10 @@ describe('RepoLoadingFile', () => {
});
}
+ afterEach(() => {
+ RepoStore.openedFiles = [];
+ });
+
it('renders 3 columns of animated LoC', () => {
const vm = createComponent({
loading: {
@@ -42,38 +47,16 @@ describe('RepoLoadingFile', () => {
});
it('renders 1 column of animated LoC if isMini', () => {
+ RepoStore.openedFiles = new Array(1);
const vm = createComponent({
loading: {
tree: true,
},
hasFiles: false,
- isMini: true,
});
const columns = [...vm.$el.querySelectorAll('td')];
expect(columns.length).toEqual(1);
assertColumns(columns);
});
-
- it('does not render if tree is not loading', () => {
- const vm = createComponent({
- loading: {
- tree: false,
- },
- hasFiles: false,
- });
-
- expect(vm.$el.innerHTML).toBeFalsy();
- });
-
- it('does not render if hasFiles is true', () => {
- const vm = createComponent({
- loading: {
- tree: true,
- },
- hasFiles: true,
- });
-
- expect(vm.$el.innerHTML).toBeFalsy();
- });
});
diff --git a/spec/javascripts/repo/components/repo_prev_directory_spec.js b/spec/javascripts/repo/components/repo_prev_directory_spec.js
index 34dde545e6a..4c064f21084 100644
--- a/spec/javascripts/repo/components/repo_prev_directory_spec.js
+++ b/spec/javascripts/repo/components/repo_prev_directory_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import repoPrevDirectory from '~/repo/components/repo_prev_directory.vue';
+import eventHub from '~/repo/event_hub';
describe('RepoPrevDirectory', () => {
function createComponent(propsData) {
@@ -20,7 +21,7 @@ describe('RepoPrevDirectory', () => {
spyOn(vm, 'linkClicked');
expect(link.href).toMatch(`/${prevUrl}`);
- expect(link.textContent).toEqual('..');
+ expect(link.textContent).toEqual('...');
link.click();
@@ -29,14 +30,17 @@ describe('RepoPrevDirectory', () => {
describe('methods', () => {
describe('linkClicked', () => {
- const vm = jasmine.createSpyObj('vm', ['$emit']);
+ it('$emits linkclicked with prevUrl', () => {
+ const prevUrl = 'prevUrl';
+ const vm = createComponent({
+ prevUrl,
+ });
- it('$emits linkclicked with file obj', () => {
- const file = {};
+ spyOn(eventHub, '$emit');
- repoPrevDirectory.methods.linkClicked.call(vm, file);
+ vm.linkClicked(prevUrl);
- expect(vm.$emit).toHaveBeenCalledWith('linkclicked', file);
+ expect(eventHub.$emit).toHaveBeenCalledWith('goToPreviousDirectoryClicked', prevUrl);
});
});
});
diff --git a/spec/javascripts/repo/components/repo_sidebar_spec.js b/spec/javascripts/repo/components/repo_sidebar_spec.js
index 35d2b37ac2a..61283da8257 100644
--- a/spec/javascripts/repo/components/repo_sidebar_spec.js
+++ b/spec/javascripts/repo/components/repo_sidebar_spec.js
@@ -3,6 +3,7 @@ import Helper from '~/repo/helpers/repo_helper';
import RepoService from '~/repo/services/repo_service';
import RepoStore from '~/repo/stores/repo_store';
import repoSidebar from '~/repo/components/repo_sidebar.vue';
+import { file } from '../mock_data';
describe('RepoSidebar', () => {
let vm;
@@ -15,14 +16,15 @@ describe('RepoSidebar', () => {
afterEach(() => {
vm.$destroy();
+
+ RepoStore.files = [];
+ RepoStore.openedFiles = [];
});
it('renders a sidebar', () => {
- RepoStore.files = [{
- id: 0,
- }];
+ RepoStore.files = [file()];
RepoStore.openedFiles = [];
- RepoStore.isRoot = false;
+ RepoStore.isRoot = true;
vm = createComponent();
const thead = vm.$el.querySelector('thead');
@@ -30,9 +32,9 @@ describe('RepoSidebar', () => {
expect(vm.$el.id).toEqual('sidebar');
expect(vm.$el.classList.contains('sidebar-mini')).toBeFalsy();
- expect(thead.querySelector('.name').textContent).toEqual('Name');
- expect(thead.querySelector('.last-commit').textContent).toEqual('Last commit');
- expect(thead.querySelector('.last-update').textContent).toEqual('Last update');
+ expect(thead.querySelector('.name').textContent.trim()).toEqual('Name');
+ expect(thead.querySelector('.last-commit').textContent.trim()).toEqual('Last commit');
+ expect(thead.querySelector('.last-update').textContent.trim()).toEqual('Last update');
expect(tbody.querySelector('.repo-file-options')).toBeFalsy();
expect(tbody.querySelector('.prev-directory')).toBeFalsy();
expect(tbody.querySelector('.loading-file')).toBeFalsy();
@@ -46,76 +48,74 @@ describe('RepoSidebar', () => {
vm = createComponent();
expect(vm.$el.classList.contains('sidebar-mini')).toBeTruthy();
- expect(vm.$el.querySelector('thead')).toBeFalsy();
- expect(vm.$el.querySelector('tbody .repo-file-options')).toBeTruthy();
+ expect(vm.$el.querySelector('thead')).toBeTruthy();
+ expect(vm.$el.querySelector('thead .repo-file-options')).toBeTruthy();
});
it('renders 5 loading files if tree is loading and not hasFiles', () => {
- RepoStore.loading = {
- tree: true,
- };
+ RepoStore.loading.tree = true;
RepoStore.files = [];
vm = createComponent();
expect(vm.$el.querySelectorAll('tbody .loading-file').length).toEqual(5);
});
- it('renders a prev directory if isRoot', () => {
- RepoStore.files = [{
- id: 0,
- }];
- RepoStore.isRoot = true;
+ it('renders a prev directory if is not root', () => {
+ RepoStore.files = [file()];
+ RepoStore.isRoot = false;
+ RepoStore.loading.tree = false;
vm = createComponent();
expect(vm.$el.querySelector('tbody .prev-directory')).toBeTruthy();
});
+ describe('flattendFiles', () => {
+ it('returns a flattend array of files', () => {
+ const f = file();
+ f.files.push(file('testing 123'));
+ const files = [f, file()];
+ vm = createComponent();
+ vm.files = files;
+
+ expect(vm.flattendFiles.length).toBe(3);
+ expect(vm.flattendFiles[1].name).toBe('testing 123');
+ });
+ });
+
describe('methods', () => {
describe('fileClicked', () => {
it('should fetch data for new file', () => {
spyOn(Helper, 'getContent').and.callThrough();
- const file1 = {
- id: 0,
- url: '',
- };
- RepoStore.files = [file1];
+ RepoStore.files = [file()];
RepoStore.isRoot = true;
vm = createComponent();
- vm.fileClicked(file1);
+ vm.fileClicked(RepoStore.files[0]);
- expect(Helper.getContent).toHaveBeenCalledWith(file1);
+ expect(Helper.getContent).toHaveBeenCalledWith(RepoStore.files[0]);
});
it('should not fetch data for already opened files', () => {
- const file = {
- id: 42,
- url: 'foo',
- };
-
- spyOn(Helper, 'getFileFromPath').and.returnValue(file);
+ const f = file();
+ spyOn(Helper, 'getFileFromPath').and.returnValue(f);
spyOn(RepoStore, 'setActiveFiles');
vm = createComponent();
- vm.fileClicked(file);
+ vm.fileClicked(f);
- expect(RepoStore.setActiveFiles).toHaveBeenCalledWith(file);
+ expect(RepoStore.setActiveFiles).toHaveBeenCalledWith(f);
});
it('should hide files in directory if already open', () => {
- spyOn(RepoStore, 'removeChildFilesOfTree').and.callThrough();
- const file1 = {
- id: 0,
- type: 'tree',
- url: '',
- opened: true,
- };
- RepoStore.files = [file1];
- RepoStore.isRoot = true;
+ spyOn(Helper, 'setDirectoryToClosed').and.callThrough();
+ const f = file();
+ f.opened = true;
+ f.type = 'tree';
+ RepoStore.files = [f];
vm = createComponent();
- vm.fileClicked(file1);
+ vm.fileClicked(RepoStore.files[0]);
- expect(RepoStore.removeChildFilesOfTree).toHaveBeenCalledWith(file1);
+ expect(Helper.setDirectoryToClosed).toHaveBeenCalledWith(RepoStore.files[0]);
});
});
@@ -131,36 +131,31 @@ describe('RepoSidebar', () => {
});
describe('back button', () => {
- const file1 = {
- id: 1,
- url: 'file1',
- };
- const file2 = {
- id: 2,
- url: 'file2',
- };
- RepoStore.files = [file1, file2];
- RepoStore.openedFiles = [file1, file2];
- RepoStore.isRoot = true;
+ beforeEach(() => {
+ const f = file();
+ const file2 = Object.assign({}, file());
+ file2.url = 'test';
+ RepoStore.files = [f, file2];
+ RepoStore.openedFiles = [];
+ RepoStore.isRoot = true;
- vm = createComponent();
- vm.fileClicked(file1);
+ vm = createComponent();
+ });
it('render previous file when using back button', () => {
spyOn(Helper, 'getContent').and.callThrough();
- vm.fileClicked(file2);
- expect(Helper.getContent).toHaveBeenCalledWith(file2);
- Helper.getContent.calls.reset();
+ vm.fileClicked(RepoStore.files[1]);
+ expect(Helper.getContent).toHaveBeenCalledWith(RepoStore.files[1]);
history.pushState({
key: Math.random(),
- }, '', file1.url);
+ }, '', RepoStore.files[1].url);
const popEvent = document.createEvent('Event');
popEvent.initEvent('popstate', true, true);
window.dispatchEvent(popEvent);
- expect(Helper.getContent.calls.mostRecent().args[0].url).toContain(file1.url);
+ expect(Helper.getContent.calls.mostRecent().args[0].url).toContain(RepoStore.files[1].url);
window.history.pushState({}, null, '/');
});
diff --git a/spec/javascripts/repo/components/repo_tab_spec.js b/spec/javascripts/repo/components/repo_tab_spec.js
index d2a790ad73a..37e297437f0 100644
--- a/spec/javascripts/repo/components/repo_tab_spec.js
+++ b/spec/javascripts/repo/components/repo_tab_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import repoTab from '~/repo/components/repo_tab.vue';
+import RepoStore from '~/repo/stores/repo_store';
describe('RepoTab', () => {
function createComponent(propsData) {
@@ -18,7 +19,7 @@ describe('RepoTab', () => {
const vm = createComponent({
tab,
});
- const close = vm.$el.querySelector('.close');
+ const close = vm.$el.querySelector('.close-btn');
const name = vm.$el.querySelector(`a[title="${tab.url}"]`);
spyOn(vm, 'closeTab');
@@ -44,26 +45,43 @@ describe('RepoTab', () => {
tab,
});
- expect(vm.$el.querySelector('.close .fa-circle')).toBeTruthy();
+ expect(vm.$el.querySelector('.close-btn .fa-circle')).toBeTruthy();
});
describe('methods', () => {
describe('closeTab', () => {
- const vm = jasmine.createSpyObj('vm', ['$emit']);
-
it('returns undefined and does not $emit if file is changed', () => {
- const file = { changed: true };
- const returnVal = repoTab.methods.closeTab.call(vm, file);
+ const tab = {
+ url: 'url',
+ name: 'name',
+ changed: true,
+ };
+ const vm = createComponent({
+ tab,
+ });
+
+ spyOn(RepoStore, 'removeFromOpenedFiles');
+
+ vm.$el.querySelector('.close-btn').click();
- expect(returnVal).toBeUndefined();
- expect(vm.$emit).not.toHaveBeenCalled();
+ expect(RepoStore.removeFromOpenedFiles).not.toHaveBeenCalled();
});
it('$emits tabclosed event with file obj', () => {
- const file = { changed: false };
- repoTab.methods.closeTab.call(vm, file);
+ const tab = {
+ url: 'url',
+ name: 'name',
+ changed: false,
+ };
+ const vm = createComponent({
+ tab,
+ });
+
+ spyOn(RepoStore, 'removeFromOpenedFiles');
+
+ vm.$el.querySelector('.close-btn').click();
- expect(vm.$emit).toHaveBeenCalledWith('tabclosed', file);
+ expect(RepoStore.removeFromOpenedFiles).toHaveBeenCalledWith(tab);
});
});
});
diff --git a/spec/javascripts/repo/components/repo_tabs_spec.js b/spec/javascripts/repo/components/repo_tabs_spec.js
index a02b54efafc..431129bc866 100644
--- a/spec/javascripts/repo/components/repo_tabs_spec.js
+++ b/spec/javascripts/repo/components/repo_tabs_spec.js
@@ -16,6 +16,10 @@ describe('RepoTabs', () => {
return new RepoTabs().$mount();
}
+ afterEach(() => {
+ RepoStore.openedFiles = [];
+ });
+
it('renders a list of tabs', () => {
RepoStore.openedFiles = openedFiles;
@@ -28,18 +32,4 @@ describe('RepoTabs', () => {
expect(tabs[1].classList.contains('active')).toBeFalsy();
expect(tabs[2].classList.contains('tabs-divider')).toBeTruthy();
});
-
- describe('methods', () => {
- describe('tabClosed', () => {
- it('calls removeFromOpenedFiles with file obj', () => {
- const file = {};
-
- spyOn(RepoStore, 'removeFromOpenedFiles');
-
- repoTabs.methods.tabClosed(file);
-
- expect(RepoStore.removeFromOpenedFiles).toHaveBeenCalledWith(file);
- });
- });
- });
});
diff --git a/spec/javascripts/repo/mock_data.js b/spec/javascripts/repo/mock_data.js
new file mode 100644
index 00000000000..836b867205e
--- /dev/null
+++ b/spec/javascripts/repo/mock_data.js
@@ -0,0 +1,13 @@
+import RepoHelper from '~/repo/helpers/repo_helper';
+
+// eslint-disable-next-line import/prefer-default-export
+export const file = (name = 'name') => RepoHelper.serializeRepoEntity('blob', {
+ icon: 'icon',
+ url: 'url',
+ name,
+ last_commit: {
+ id: '123',
+ message: 'test',
+ committed_date: '',
+ },
+});