diff options
author | Phil Hughes <me@iamphill.com> | 2018-09-17 14:17:00 +0300 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2018-09-17 14:17:00 +0300 |
commit | 163ec966c18b88270223b2456f5ef9570b9cdda8 (patch) | |
tree | f0865deab9d4546ffed425ef66a33cabd1e3cfca | |
parent | 4945b8bf7d7374d6c566b0eadd3f8ea4ef0d8f60 (diff) |
Decouple file row from IDE
This makes the file row component re-usable ouside of the Web IDE
Pre-request for https://gitlab.com/gitlab-org/gitlab-ce/issues/14249
-rw-r--r-- | app/assets/javascripts/ide/components/file_row_extra.vue | 104 | ||||
-rw-r--r-- | app/assets/javascripts/ide/components/ide_tree_list.vue | 12 | ||||
-rw-r--r-- | app/assets/javascripts/ide/components/repo_file.vue | 227 | ||||
-rw-r--r-- | app/assets/javascripts/vue_shared/components/file_row.vue | 210 | ||||
-rw-r--r-- | app/assets/stylesheets/page_bundles/ide.scss | 142 | ||||
-rw-r--r-- | spec/javascripts/ide/components/file_row_extra_spec.js | 159 | ||||
-rw-r--r-- | spec/javascripts/ide/components/repo_file_spec.js | 145 | ||||
-rw-r--r-- | spec/javascripts/vue_shared/components/file_row_spec.js | 74 |
8 files changed, 602 insertions, 471 deletions
diff --git a/app/assets/javascripts/ide/components/file_row_extra.vue b/app/assets/javascripts/ide/components/file_row_extra.vue new file mode 100644 index 00000000000..44a360ab909 --- /dev/null +++ b/app/assets/javascripts/ide/components/file_row_extra.vue @@ -0,0 +1,104 @@ +<script> +import { mapGetters } from 'vuex'; +import { n__, __, sprintf } from '~/locale'; +import tooltip from '~/vue_shared/directives/tooltip'; +import Icon from '~/vue_shared/components/icon.vue'; +import NewDropdown from './new_dropdown/index.vue'; +import ChangedFileIcon from './changed_file_icon.vue'; +import MrFileIcon from './mr_file_icon.vue'; + +export default { + name: 'FileRowExtra', + directives: { + tooltip, + }, + components: { + Icon, + NewDropdown, + ChangedFileIcon, + MrFileIcon, + }, + props: { + file: { + type: Object, + required: true, + }, + mouseOver: { + type: Boolean, + required: true, + }, + }, + computed: { + ...mapGetters([ + 'getChangesInFolder', + 'getUnstagedFilesCountForPath', + 'getStagedFilesCountForPath', + ]), + folderUnstagedCount() { + return this.getUnstagedFilesCountForPath(this.file.path); + }, + folderStagedCount() { + return this.getStagedFilesCountForPath(this.file.path); + }, + changesCount() { + return this.getChangesInFolder(this.file.path); + }, + folderChangesTooltip() { + if (this.changesCount === 0) return undefined; + + if (this.folderUnstagedCount > 0 && this.folderStagedCount === 0) { + return n__('%d unstaged change', '%d unstaged changes', this.folderUnstagedCount); + } else if (this.folderUnstagedCount === 0 && this.folderStagedCount > 0) { + return n__('%d staged change', '%d staged changes', this.folderStagedCount); + } + + return sprintf(__('%{unstaged} unstaged and %{staged} staged changes'), { + unstaged: this.folderUnstagedCount, + staged: this.folderStagedCount, + }); + }, + showTreeChangesCount() { + return this.file.type === 'tree' && this.changesCount > 0 && !this.file.opened; + }, + showChangedFileIcon() { + return this.file.changed || this.file.tempFile || this.file.staged; + }, + }, +}; +</script> + +<template> + <div class="float-right ide-file-icon-holder"> + <mr-file-icon + v-if="file.mrChange" + /> + <span + v-if="showTreeChangesCount" + class="ide-tree-changes" + > + {{ changesCount }} + <icon + v-tooltip + :title="folderChangesTooltip" + :size="12" + data-container="body" + data-placement="right" + name="file-modified" + css-classes="prepend-left-5 ide-file-modified" + /> + </span> + <changed-file-icon + v-else-if="showChangedFileIcon" + :file="file" + :show-tooltip="true" + :show-staged-icon="true" + :force-modified-icon="true" + /> + <new-dropdown + :type="file.type" + :path="file.path" + :mouse-over="mouseOver" + class="prepend-left-8" + /> + </div> +</template> diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue index 00ae5ea2c15..e658d1bf956 100644 --- a/app/assets/javascripts/ide/components/ide_tree_list.vue +++ b/app/assets/javascripts/ide/components/ide_tree_list.vue @@ -2,15 +2,16 @@ import { mapActions, mapGetters, mapState } from 'vuex'; import Icon from '~/vue_shared/components/icon.vue'; import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; -import RepoFile from './repo_file.vue'; +import FileRow from '~/vue_shared/components/file_row.vue'; import NavDropdown from './nav_dropdown.vue'; +import FileRowExtra from './file_row_extra.vue'; export default { components: { Icon, - RepoFile, SkeletonLoadingContainer, NavDropdown, + FileRow, }, props: { viewerType: { @@ -34,8 +35,9 @@ export default { this.updateViewer(this.viewerType); }, methods: { - ...mapActions(['updateViewer']), + ...mapActions(['updateViewer', 'toggleTreeOpen']), }, + FileRowExtra, }; </script> @@ -63,11 +65,13 @@ export default { <div class="ide-tree-body h-100" > - <repo-file + <file-row v-for="file in currentTree.tree" :key="file.key" :file="file" :level="0" + :extra-component="$options.FileRowExtra" + @toggleTreeOpen="toggleTreeOpen" /> </div> </template> diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue deleted file mode 100644 index 110eda83bb4..00000000000 --- a/app/assets/javascripts/ide/components/repo_file.vue +++ /dev/null @@ -1,227 +0,0 @@ -<script> -import { mapActions, mapGetters } from 'vuex'; -import { n__, __, sprintf } from '~/locale'; -import tooltip from '~/vue_shared/directives/tooltip'; -import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; -import Icon from '~/vue_shared/components/icon.vue'; -import FileIcon from '~/vue_shared/components/file_icon.vue'; -import router from '../ide_router'; -import NewDropdown from './new_dropdown/index.vue'; -import FileStatusIcon from './repo_file_status_icon.vue'; -import ChangedFileIcon from './changed_file_icon.vue'; -import MrFileIcon from './mr_file_icon.vue'; - -export default { - name: 'RepoFile', - directives: { - tooltip, - }, - components: { - SkeletonLoadingContainer, - NewDropdown, - FileStatusIcon, - FileIcon, - ChangedFileIcon, - MrFileIcon, - Icon, - }, - props: { - file: { - type: Object, - required: true, - }, - level: { - type: Number, - required: true, - }, - }, - data() { - return { - mouseOver: false, - }; - }, - computed: { - ...mapGetters([ - 'getChangesInFolder', - 'getUnstagedFilesCountForPath', - 'getStagedFilesCountForPath', - ]), - folderUnstagedCount() { - return this.getUnstagedFilesCountForPath(this.file.path); - }, - folderStagedCount() { - return this.getStagedFilesCountForPath(this.file.path); - }, - changesCount() { - return this.getChangesInFolder(this.file.path); - }, - folderChangesTooltip() { - if (this.changesCount === 0) return undefined; - - if (this.folderUnstagedCount > 0 && this.folderStagedCount === 0) { - return n__('%d unstaged change', '%d unstaged changes', this.folderUnstagedCount); - } else if (this.folderUnstagedCount === 0 && this.folderStagedCount > 0) { - return n__('%d staged change', '%d staged changes', this.folderStagedCount); - } - - return sprintf(__('%{unstaged} unstaged and %{staged} staged changes'), { - unstaged: this.folderUnstagedCount, - staged: this.folderStagedCount, - }); - }, - isTree() { - return this.file.type === 'tree'; - }, - isBlob() { - return this.file.type === 'blob'; - }, - levelIndentation() { - return { - marginLeft: `${this.level * 16}px`, - }; - }, - fileClass() { - return { - 'file-open': this.isBlob && this.file.opened, - 'file-active': this.isBlob && this.file.active, - folder: this.isTree, - 'is-open': this.file.opened, - }; - }, - showTreeChangesCount() { - return this.isTree && this.changesCount > 0 && !this.file.opened; - }, - showChangedFileIcon() { - return this.file.changed || this.file.tempFile || this.file.staged; - }, - }, - watch: { - 'file.active': function fileActiveWatch(active) { - if (this.file.type === 'blob' && active) { - this.scrollIntoView(); - } - }, - }, - mounted() { - if (this.hasPathAtCurrentRoute()) { - this.scrollIntoView(true); - } - }, - methods: { - ...mapActions(['toggleTreeOpen']), - clickFile() { - // Manual Action if a tree is selected/opened - if (this.isTree && this.hasUrlAtCurrentRoute()) { - this.toggleTreeOpen(this.file.path); - } - - router.push(`/project${this.file.url}`); - }, - scrollIntoView(isInit = false) { - const block = isInit && this.isTree ? 'center' : 'nearest'; - - this.$el.scrollIntoView({ - behavior: 'smooth', - block, - }); - }, - hasPathAtCurrentRoute() { - if (!this.$router || !this.$router.currentRoute) { - return false; - } - - // - strip route up to "/-/" and ending "/" - const routePath = this.$router.currentRoute.path - .replace(/^.*?[/]-[/]/g, '') - .replace(/[/]$/g, ''); - - // - strip ending "/" - const filePath = this.file.path.replace(/[/]$/g, ''); - - return filePath === routePath; - }, - hasUrlAtCurrentRoute() { - return this.$router.currentRoute.path === `/project${this.file.url}`; - }, - toggleHover(over) { - this.mouseOver = over; - }, - }, -}; -</script> - -<template> - <div> - <div - :class="fileClass" - class="file" - role="button" - @click="clickFile" - @mouseover="toggleHover(true)" - @mouseout="toggleHover(false)" - > - <div - class="file-name" - > - <span - :style="levelIndentation" - class="ide-file-name str-truncated" - > - <file-icon - :file-name="file.name" - :loading="file.loading" - :folder="isTree" - :opened="file.opened" - :size="16" - /> - {{ file.name }} - <file-status-icon - :file="file" - /> - </span> - <span class="float-right ide-file-icon-holder"> - <mr-file-icon - v-if="file.mrChange" - /> - <span - v-if="showTreeChangesCount" - class="ide-tree-changes" - > - {{ changesCount }} - <icon - v-tooltip - :title="folderChangesTooltip" - :size="12" - data-container="body" - data-placement="right" - name="file-modified" - css-classes="prepend-left-5 ide-file-modified" - /> - </span> - <changed-file-icon - v-else-if="showChangedFileIcon" - :file="file" - :show-tooltip="true" - :show-staged-icon="true" - :force-modified-icon="true" - class="float-right" - /> - </span> - <new-dropdown - :type="file.type" - :path="file.path" - :mouse-over="mouseOver" - class="float-right prepend-left-8" - /> - </div> - </div> - <template v-if="file.opened"> - <repo-file - v-for="childFile in file.tree" - :key="childFile.key" - :file="childFile" - :level="level + 1" - /> - </template> - </div> -</template> diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue new file mode 100644 index 00000000000..6f7bdbc2c4d --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/file_row.vue @@ -0,0 +1,210 @@ +<script> +import Icon from '~/vue_shared/components/icon.vue'; +import FileIcon from '~/vue_shared/components/file_icon.vue'; + +export default { + name: 'FileRow', + components: { + FileIcon, + Icon, + }, + props: { + file: { + type: Object, + required: true, + }, + level: { + type: Number, + required: true, + }, + extraComponent: { + type: Object, + required: false, + default: null, + }, + }, + data() { + return { + mouseOver: false, + }; + }, + computed: { + isTree() { + return this.file.type === 'tree'; + }, + isBlob() { + return this.file.type === 'blob'; + }, + levelIndentation() { + return { + marginLeft: `${this.level * 16}px`, + }; + }, + fileClass() { + return { + 'file-open': this.isBlob && this.file.opened, + 'is-active': this.isBlob && this.file.active, + folder: this.isTree, + 'is-open': this.file.opened, + }; + }, + }, + watch: { + 'file.active': function fileActiveWatch(active) { + if (this.file.type === 'blob' && active) { + this.scrollIntoView(); + } + }, + }, + mounted() { + if (this.hasPathAtCurrentRoute()) { + this.scrollIntoView(true); + } + }, + methods: { + toggleTreeOpen(path) { + this.$emit('toggleTreeOpen', path); + }, + clickFile() { + // Manual Action if a tree is selected/opened + if (this.isTree && this.hasUrlAtCurrentRoute()) { + this.toggleTreeOpen(this.file.path); + } + + if (this.$router) this.$router.push(`/project${this.file.url}`); + }, + scrollIntoView(isInit = false) { + const block = isInit && this.isTree ? 'center' : 'nearest'; + + this.$el.scrollIntoView({ + behavior: 'smooth', + block, + }); + }, + hasPathAtCurrentRoute() { + if (!this.$router || !this.$router.currentRoute) { + return false; + } + + // - strip route up to "/-/" and ending "/" + const routePath = this.$router.currentRoute.path + .replace(/^.*?[/]-[/]/g, '') + .replace(/[/]$/g, ''); + + // - strip ending "/" + const filePath = this.file.path.replace(/[/]$/g, ''); + + return filePath === routePath; + }, + hasUrlAtCurrentRoute() { + if (!this.$router || !this.$router.currentRoute) return true; + + return this.$router.currentRoute.path === `/project${this.file.url}`; + }, + toggleHover(over) { + this.mouseOver = over; + }, + }, +}; +</script> + +<template> + <div> + <div + :class="fileClass" + class="file-row" + role="button" + @click="clickFile" + @mouseover="toggleHover(true)" + @mouseout="toggleHover(false)" + > + <div + class="file-row-name-container" + > + <span + :style="levelIndentation" + class="file-row-name str-truncated" + > + <file-icon + :file-name="file.name" + :loading="file.loading" + :folder="isTree" + :opened="file.opened" + :size="16" + /> + {{ file.name }} + </span> + <component + v-if="extraComponent" + :is="extraComponent" + :file="file" + :mouse-over="mouseOver" + /> + </div> + </div> + <template v-if="file.opened"> + <file-row + v-for="childFile in file.tree" + :key="childFile.key" + :file="childFile" + :level="level + 1" + :extra-component="extraComponent" + @toggleTreeOpen="toggleTreeOpen" + /> + </template> + </div> +</template> + +<style> +.file-row { + display: flex; + align-items: center; + height: 32px; + padding: 4px 8px; + margin-left: -8px; + margin-right: -8px; + border-radius: 3px; + text-align: left; + cursor: pointer; +} + +.file-row:hover, +.file-row:focus { + background: #f2f2f2; +} + +.file-row:active { + background: #dfdfdf; +} + +.file-row.is-active { + background: #f2f2f2; +} + +.file-row-name-container { + display: flex; + width: 100%; + align-items: center; + overflow: visible; +} + +.file-row-name { + display: inline-block; + flex: 1; + max-width: inherit; + height: 18px; + line-height: 16px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.file-row-name svg { + margin-right: 2px; + vertical-align: middle; +} + +.file-row-name .loading-container { + display: inline-block; + margin-right: 4px; +} +</style> diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss index 45df8391f9a..65f0a0d18e2 100644 --- a/app/assets/stylesheets/page_bundles/ide.scss +++ b/app/assets/stylesheets/page_bundles/ide.scss @@ -53,83 +53,9 @@ $ide-commit-header-height: 48px; flex: 1; min-height: 0; // firefox fix - .file { - height: 32px; - cursor: pointer; - - &.file-active { - background: $theme-gray-100; - } - - .ide-file-name { - flex: 1; - white-space: nowrap; - text-overflow: ellipsis; - max-width: inherit; - line-height: 16px; - display: inline-block; - height: 18px; - - svg { - vertical-align: middle; - margin-right: 2px; - } - - .loading-container { - margin-right: 4px; - display: inline-block; - } - } - - .ide-file-icon-holder { - display: flex; - align-items: center; - color: $theme-gray-700; - } - - .ide-file-changed-icon { - margin-left: auto; - - > svg { - display: block; - } - } - - .ide-new-btn { - display: none; - - .btn { - padding: 2px 5px; - } - } - - &:hover, - &:focus { - .ide-new-btn { - display: block; - } - } - - .folder-icon { - fill: $gl-text-color-secondary; - } - } - a { color: $gl-text-color; } - - th { - position: sticky; - top: 0; - } -} - -.file-name { - display: flex; - overflow: visible; - align-items: center; - width: 100%; } .multi-file-loading-container { @@ -625,8 +551,7 @@ $ide-commit-header-height: 48px; } } -.multi-file-commit-list-path, -.ide-file-list .file { +.multi-file-commit-list-path { display: flex; align-items: center; margin-left: -$grid-size; @@ -634,28 +559,14 @@ $ide-commit-header-height: 48px; padding: $grid-size / 2 $grid-size; border-radius: $border-radius-default; text-align: left; - - &:hover, - &:focus { - background: $theme-gray-100; - } - - &:active { - background: $theme-gray-200; - } -} - -.multi-file-commit-list-path { cursor: pointer; height: $ide-commit-row-height; padding-right: 0; - &.is-active { - background-color: $white-normal; - } - &:hover, &:focus { + background: $theme-gray-100; + outline: 0; .multi-file-discard-btn { @@ -665,6 +576,14 @@ $ide-commit-header-height: 48px; } } + &:active { + background: $theme-gray-200; + } + + &.is-active { + background-color: $white-normal; + } + svg { min-width: 16px; vertical-align: middle; @@ -1398,9 +1317,17 @@ $ide-commit-header-height: 48px; } } -.ide-new-btn .dropdown.show .ide-entry-dropdown-toggle { - color: $white-normal; - background-color: $blue-500; +.ide-new-btn { + display: none; + + .btn { + padding: 2px 5px; + } + + .dropdown.show .ide-entry-dropdown-toggle { + color: $white-normal; + background-color: $blue-500; + } } .ide-preview-header { @@ -1465,3 +1392,28 @@ $ide-commit-header-height: 48px; width: $ide-commit-row-height; height: $ide-commit-row-height; } + +.ide-file-icon-holder { + display: flex; + align-items: center; + color: $theme-gray-700; +} + +.ide-file-changed-icon { + margin-left: auto; + + > svg { + display: block; + } +} + +.file-row:hover, +.file-row:focus { + .ide-new-btn { + display: block; + } + + .folder-icon { + fill: $gl-text-color-secondary; + } +} diff --git a/spec/javascripts/ide/components/file_row_extra_spec.js b/spec/javascripts/ide/components/file_row_extra_spec.js new file mode 100644 index 00000000000..60dabe28045 --- /dev/null +++ b/spec/javascripts/ide/components/file_row_extra_spec.js @@ -0,0 +1,159 @@ +import Vue from 'vue'; +import { createStore } from '~/ide/stores'; +import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import FileRowExtra from '~/ide/components/file_row_extra.vue'; +import { file, resetStore } from '../helpers'; + +describe('IDE extra file row component', () => { + let Component; + let vm; + let unstagedFilesCount = 0; + let stagedFilesCount = 0; + let changesCount = 0; + + beforeAll(() => { + Component = Vue.extend(FileRowExtra); + }); + + beforeEach(() => { + vm = createComponentWithStore(Component, createStore(), { + file: { + ...file('test'), + }, + mouseOver: false, + }); + + spyOnProperty(vm, 'getUnstagedFilesCountForPath').and.returnValue(() => unstagedFilesCount); + spyOnProperty(vm, 'getStagedFilesCountForPath').and.returnValue(() => stagedFilesCount); + spyOnProperty(vm, 'getChangesInFolder').and.returnValue(() => changesCount); + + vm.$mount(); + }); + + afterEach(() => { + vm.$destroy(); + resetStore(vm.$store); + + stagedFilesCount = 0; + unstagedFilesCount = 0; + changesCount = 0; + }); + + describe('folderChangesTooltip', () => { + it('returns undefined when changes count is 0', () => { + expect(vm.folderChangesTooltip).toBe(undefined); + }); + + it('returns unstaged changes text', () => { + changesCount = 1; + unstagedFilesCount = 1; + + expect(vm.folderChangesTooltip).toBe('1 unstaged change'); + }); + + it('returns staged changes text', () => { + changesCount = 1; + stagedFilesCount = 1; + + expect(vm.folderChangesTooltip).toBe('1 staged change'); + }); + + it('returns staged and unstaged changes text', () => { + changesCount = 1; + stagedFilesCount = 1; + unstagedFilesCount = 1; + + expect(vm.folderChangesTooltip).toBe('1 unstaged and 1 staged changes'); + }); + }); + + describe('show tree changes count', () => { + it('does not show for blobs', () => { + vm.file.type = 'blob'; + + expect(vm.$el.querySelector('.ide-tree-changes')).toBe(null); + }); + + it('does not show when changes count is 0', () => { + vm.file.type = 'tree'; + + expect(vm.$el.querySelector('.ide-tree-changes')).toBe(null); + }); + + it('does not show when tree is open', done => { + vm.file.type = 'tree'; + vm.file.opened = true; + changesCount = 1; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.ide-tree-changes')).toBe(null); + + done(); + }); + }); + + it('shows for trees with changes', done => { + vm.file.type = 'tree'; + vm.file.opened = false; + changesCount = 1; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.ide-tree-changes')).not.toBe(null); + + done(); + }); + }); + }); + + describe('changes file icon', () => { + it('hides when file is not changed', () => { + expect(vm.$el.querySelector('.ide-file-changed-icon')).toBe(null); + }); + + it('shows when file is changed', done => { + vm.file.changed = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.ide-file-changed-icon')).not.toBe(null); + + done(); + }); + }); + + it('shows when file is staged', done => { + vm.file.staged = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.ide-file-changed-icon')).not.toBe(null); + + done(); + }); + }); + + it('shows when file is a tempFile', done => { + vm.file.tempFile = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.ide-file-changed-icon')).not.toBe(null); + + done(); + }); + }); + }); + + describe('merge request icon', () => { + it('hides when not a merge request change', () => { + expect(vm.$el.querySelector('.ic-git-merge')).toBe(null); + }); + + it('shows when a merge request change', done => { + vm.file.mrChange = true; + + vm.$nextTick(() => { + expect(vm.$el.querySelector('.ic-git-merge')).not.toBe(null); + + done(); + }); + }); + }); +}); diff --git a/spec/javascripts/ide/components/repo_file_spec.js b/spec/javascripts/ide/components/repo_file_spec.js deleted file mode 100644 index fc639a672e2..00000000000 --- a/spec/javascripts/ide/components/repo_file_spec.js +++ /dev/null @@ -1,145 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import repoFile from '~/ide/components/repo_file.vue'; -import router from '~/ide/ide_router'; -import { createComponentWithStore } from '../../helpers/vue_mount_component_helper'; -import { file } from '../helpers'; - -describe('RepoFile', () => { - let vm; - - function createComponent(propsData) { - const RepoFile = Vue.extend(repoFile); - - vm = createComponentWithStore(RepoFile, store, propsData); - - vm.$mount(); - } - - afterEach(() => { - vm.$destroy(); - }); - - it('renders link, icon and name', () => { - createComponent({ - file: file('t4'), - level: 0, - }); - - const name = vm.$el.querySelector('.ide-file-name'); - - expect(name.href).toMatch(''); - expect(name.textContent.trim()).toEqual(vm.file.name); - }); - - it('fires clickFile when the link is clicked', done => { - spyOn(router, 'push'); - createComponent({ - file: file('t3'), - level: 0, - }); - - vm.$el.querySelector('.file-name').click(); - - setTimeout(() => { - expect(router.push).toHaveBeenCalledWith(`/project${vm.file.url}`); - - done(); - }); - }); - - describe('folder', () => { - it('renders changes count inside folder', () => { - const f = { - ...file('folder'), - path: 'testing', - type: 'tree', - branchId: 'master', - projectId: 'project', - }; - - store.state.changedFiles.push({ - ...file('fileName'), - path: 'testing/fileName', - }); - - createComponent({ - file: f, - level: 0, - }); - - const treeChangesEl = vm.$el.querySelector('.ide-tree-changes'); - - expect(treeChangesEl).not.toBeNull(); - expect(treeChangesEl.textContent).toContain('1'); - }); - - it('renders action dropdown', done => { - createComponent({ - file: { - ...file('t4'), - type: 'tree', - branchId: 'master', - projectId: 'project', - }, - level: 0, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.ide-new-btn')).not.toBeNull(); - - done(); - }); - }); - }); - - describe('locked file', () => { - let f; - - beforeEach(() => { - f = file('locked file'); - f.file_lock = { - user: { - name: 'testuser', - updated_at: new Date(), - }, - }; - - createComponent({ - file: f, - level: 0, - }); - }); - - it('renders lock icon', () => { - expect(vm.$el.querySelector('.file-status-icon')).not.toBeNull(); - }); - - it('renders a tooltip', () => { - expect( - vm.$el.querySelector('.ide-file-name span:nth-child(2)').dataset.originalTitle, - ).toContain('Locked by testuser'); - }); - }); - - it('calls scrollIntoView if made active', done => { - createComponent({ - file: { - ...file(), - type: 'blob', - active: false, - }, - level: 0, - }); - - spyOn(vm, 'scrollIntoView'); - - vm.file.active = true; - - vm.$nextTick(() => { - expect(vm.scrollIntoView).toHaveBeenCalled(); - - done(); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/file_row_spec.js b/spec/javascripts/vue_shared/components/file_row_spec.js new file mode 100644 index 00000000000..9914c0b70f3 --- /dev/null +++ b/spec/javascripts/vue_shared/components/file_row_spec.js @@ -0,0 +1,74 @@ +import Vue from 'vue'; +import FileRow from '~/vue_shared/components/file_row.vue'; +import { file } from 'spec/ide/helpers'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('RepoFile', () => { + let vm; + + function createComponent(propsData) { + const FileRowComponent = Vue.extend(FileRow); + + vm = mountComponent(FileRowComponent, propsData); + } + + afterEach(() => { + vm.$destroy(); + }); + + it('renders name', () => { + createComponent({ + file: file('t4'), + level: 0, + }); + + const name = vm.$el.querySelector('.file-row-name'); + + expect(name.textContent.trim()).toEqual(vm.file.name); + }); + + it('emits toggleTreeOpen on click', () => { + createComponent({ + file: { + ...file('t3'), + type: 'tree', + }, + level: 0, + }); + spyOn(vm, '$emit').and.stub(); + + vm.$el.querySelector('.file-row').click(); + + expect(vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', vm.file.path); + }); + + it('calls scrollIntoView if made active', done => { + createComponent({ + file: { + ...file(), + type: 'blob', + active: false, + }, + level: 0, + }); + + spyOn(vm, 'scrollIntoView').and.stub(); + + vm.file.active = true; + + vm.$nextTick(() => { + expect(vm.scrollIntoView).toHaveBeenCalled(); + + done(); + }); + }); + + it('indents row based on level', () => { + createComponent({ + file: file('t4'), + level: 2, + }); + + expect(vm.$el.querySelector('.file-row-name').style.marginLeft).toBe('32px'); + }); +}); |