Commit e1361e31 authored by Samantha Ming's avatar Samantha Ming Committed by Paul Slaughter

Refactored recursive part of file_row to file_tree

**Note:**
- Also, introduces some components which will be used for
  IDE and diff row specific things.

https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23720
parent e342328a
<script>
/**
* This component is an iterative step towards refactoring and simplifying `vue_shared/components/file_row.vue`
* https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23720
*/
import FileRow from '~/vue_shared/components/file_row.vue';
export default {
components: {
FileRow,
},
};
</script>
<template>
<file-row v-bind="$attrs" v-on="$listeners" />
</template>
...@@ -3,7 +3,8 @@ import { mapActions, mapGetters, mapState } from 'vuex'; ...@@ -3,7 +3,8 @@ import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui'; import { GlTooltipDirective } from '@gitlab/ui';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import FileRow from '~/vue_shared/components/file_row.vue'; import FileTree from '~/vue_shared/components/file_tree.vue';
import DiffFileRow from './diff_file_row.vue';
import FileRowStats from './file_row_stats.vue'; import FileRowStats from './file_row_stats.vue';
export default { export default {
...@@ -12,7 +13,7 @@ export default { ...@@ -12,7 +13,7 @@ export default {
}, },
components: { components: {
Icon, Icon,
FileRow, FileTree,
}, },
props: { props: {
hideFileStats: { hideFileStats: {
...@@ -61,6 +62,7 @@ export default { ...@@ -61,6 +62,7 @@ export default {
searchPlaceholder: sprintf(s__('MergeRequest|Search files (%{modifier_key}P)'), { searchPlaceholder: sprintf(s__('MergeRequest|Search files (%{modifier_key}P)'), {
modifier_key: /Mac/i.test(navigator.userAgent) ? '' : 'Ctrl+', modifier_key: /Mac/i.test(navigator.userAgent) ? '' : 'Ctrl+',
}), }),
DiffFileRow,
}; };
</script> </script>
...@@ -91,7 +93,7 @@ export default { ...@@ -91,7 +93,7 @@ export default {
</div> </div>
<div :class="{ 'pt-0 tree-list-blobs': !renderTreeList }" class="tree-list-scroll"> <div :class="{ 'pt-0 tree-list-blobs': !renderTreeList }" class="tree-list-scroll">
<template v-if="filteredTreeList.length"> <template v-if="filteredTreeList.length">
<file-row <file-tree
v-for="file in filteredTreeList" v-for="file in filteredTreeList"
:key="file.key" :key="file.key"
:file="file" :file="file"
...@@ -99,6 +101,7 @@ export default { ...@@ -99,6 +101,7 @@ export default {
:hide-extra-on-tree="true" :hide-extra-on-tree="true"
:extra-component="fileRowExtraComponent" :extra-component="fileRowExtraComponent"
:show-changed-icon="true" :show-changed-icon="true"
:file-row-component="$options.DiffFileRow"
@toggleTreeOpen="toggleTreeOpen" @toggleTreeOpen="toggleTreeOpen"
@clickFile="scrollToFile" @clickFile="scrollToFile"
/> />
......
<script>
/**
* This component is an iterative step towards refactoring and simplifying `vue_shared/components/file_row.vue`
* https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23720
*/
import FileRow from '~/vue_shared/components/file_row.vue';
export default {
components: {
FileRow,
},
};
</script>
<template>
<file-row v-bind="$attrs" v-on="$listeners" />
</template>
<script> <script>
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { GlSkeletonLoading } from '@gitlab/ui'; import { GlSkeletonLoading } from '@gitlab/ui';
import FileRow from '~/vue_shared/components/file_row.vue'; import FileTree from '~/vue_shared/components/file_tree.vue';
import IdeFileRow from './ide_file_row.vue';
import NavDropdown from './nav_dropdown.vue'; import NavDropdown from './nav_dropdown.vue';
import FileRowExtra from './file_row_extra.vue'; import FileRowExtra from './file_row_extra.vue';
...@@ -9,7 +10,7 @@ export default { ...@@ -9,7 +10,7 @@ export default {
components: { components: {
GlSkeletonLoading, GlSkeletonLoading,
NavDropdown, NavDropdown,
FileRow, FileTree,
}, },
props: { props: {
viewerType: { viewerType: {
...@@ -36,6 +37,7 @@ export default { ...@@ -36,6 +37,7 @@ export default {
...mapActions(['updateViewer', 'toggleTreeOpen']), ...mapActions(['updateViewer', 'toggleTreeOpen']),
}, },
FileRowExtra, FileRowExtra,
IdeFileRow,
}; };
</script> </script>
...@@ -53,12 +55,13 @@ export default { ...@@ -53,12 +55,13 @@ export default {
</header> </header>
<div class="ide-tree-body h-100"> <div class="ide-tree-body h-100">
<template v-if="currentTree.tree.length"> <template v-if="currentTree.tree.length">
<file-row <file-tree
v-for="file in currentTree.tree" v-for="file in currentTree.tree"
:key="file.key" :key="file.key"
:file="file" :file="file"
:level="0" :level="0"
:extra-component="$options.FileRowExtra" :extra-component="$options.FileRowExtra"
:file-row-component="$options.IdeFileRow"
@toggleTreeOpen="toggleTreeOpen" @toggleTreeOpen="toggleTreeOpen"
/> />
</template> </template>
......
...@@ -62,9 +62,6 @@ export default { ...@@ -62,9 +62,6 @@ export default {
'is-open': this.file.opened, 'is-open': this.file.opened,
}; };
}, },
childFilesLevel() {
return this.file.isHeader ? 0 : this.level + 1;
},
}, },
watch: { watch: {
'file.active': function fileActiveWatch(active) { 'file.active': function fileActiveWatch(active) {
...@@ -131,53 +128,38 @@ export default { ...@@ -131,53 +128,38 @@ export default {
</script> </script>
<template> <template>
<div> <file-header v-if="file.isHeader" :path="file.path" />
<file-header v-if="file.isHeader" :path="file.path" /> <div
<div v-else
v-else :class="fileClass"
:class="fileClass" :title="file.name"
:title="file.name" class="file-row"
class="file-row" role="button"
role="button" @click="clickFile"
@click="clickFile" @mouseleave="toggleDropdown(false)"
@mouseleave="toggleDropdown(false)" >
> <div class="file-row-name-container">
<div class="file-row-name-container"> <span ref="textOutput" :style="levelIndentation" class="file-row-name str-truncated">
<span ref="textOutput" :style="levelIndentation" class="file-row-name str-truncated"> <file-icon
<file-icon v-if="!showChangedIcon || file.type === 'tree'"
v-if="!showChangedIcon || file.type === 'tree'" class="file-row-icon"
class="file-row-icon" :file-name="file.name"
:file-name="file.name" :loading="file.loading"
:loading="file.loading" :folder="isTree"
:folder="isTree" :opened="file.opened"
:opened="file.opened" :size="16"
:size="16"
/>
<changed-file-icon v-else :file="file" :size="16" class="append-right-5" />
{{ file.name }}
</span>
<component
:is="extraComponent"
v-if="extraComponent && !(hideExtraOnTree && file.type === 'tree')"
:file="file"
:dropdown-open="dropdownOpen"
@toggle="toggleDropdown($event)"
/> />
</div> <changed-file-icon v-else :file="file" :size="16" class="append-right-5" />
</div> {{ file.name }}
<template v-if="file.opened || file.isHeader"> </span>
<file-row <component
v-for="childFile in file.tree" :is="extraComponent"
:key="childFile.key" v-if="extraComponent && !(hideExtraOnTree && file.type === 'tree')"
:file="childFile" :file="file"
:level="childFilesLevel" :dropdown-open="dropdownOpen"
:hide-extra-on-tree="hideExtraOnTree" @toggle="toggleDropdown($event)"
:extra-component="extraComponent"
:show-changed-icon="showChangedIcon"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="clickedFile"
/> />
</template> </div>
</div> </div>
</template> </template>
......
<script>
export default {
name: 'FileTree',
props: {
fileRowComponent: {
type: Object,
required: true,
},
level: {
type: Number,
required: true,
},
file: {
type: Object,
required: true,
},
},
computed: {
childFilesLevel() {
return this.file.isHeader ? 0 : this.level + 1;
},
},
};
</script>
<template>
<div>
<component
:is="fileRowComponent"
:level="level"
:file="file"
v-bind="$attrs"
v-on="$listeners"
/>
<template v-if="file.opened || file.isHeader">
<file-tree
v-for="childFile in file.tree"
:key="childFile.key"
:file-row-component="fileRowComponent"
:level="childFilesLevel"
:file="childFile"
v-bind="$attrs"
v-on="$listeners"
/>
</template>
</div>
</template>
import { shallowMount } from '@vue/test-utils';
import DiffFileRow from '~/diffs/components/diff_file_row.vue';
import FileRow from '~/vue_shared/components/file_row.vue';
describe('Diff File Row component', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(DiffFileRow, {
propsData: { ...props },
});
};
afterEach(() => {
wrapper.destroy();
});
it('renders file row component', () => {
createComponent({
level: 4,
file: {},
});
expect(wrapper.find(FileRow).exists()).toEqual(true);
});
});
import { shallowMount } from '@vue/test-utils';
import IdeFileRow from '~/ide/components/ide_file_row.vue';
import FileRow from '~/vue_shared/components/file_row.vue';
describe('Ide File Row component', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(IdeFileRow, {
propsData: { ...props },
});
};
afterEach(() => {
wrapper.destroy();
});
it('renders file row component', () => {
createComponent({
level: 4,
file: {},
});
expect(wrapper.find(FileRow).exists()).toEqual(true);
});
});
import { pick } from 'lodash';
import { shallowMount } from '@vue/test-utils';
import FileTree from '~/vue_shared/components/file_tree.vue';
const MockFileRow = {
name: 'MockFileRow',
render() {
return this.$slots.default;
},
};
const TEST_LEVEL = 4;
const TEST_EXTA_ARGS = {
foo: 'lorem-ipsum',
bar: 'zoo',
};
describe('File Tree component', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(FileTree, {
propsData: { level: TEST_LEVEL, fileRowComponent: MockFileRow, ...props },
attrs: { ...TEST_EXTA_ARGS },
});
};
const findFileRow = () => wrapper.find(MockFileRow);
const findChildrenTrees = () => wrapper.findAll(FileTree).wrappers.slice(1);
const findChildrenTreeProps = () =>
findChildrenTrees().map(x => ({
...x.props(),
...pick(x.attributes(), Object.keys(TEST_EXTA_ARGS)),
}));
afterEach(() => {
wrapper.destroy();
});
describe('file row component', () => {
beforeEach(() => {
createComponent({ file: {} });
});
it('renders file row component', () => {
expect(findFileRow().exists()).toEqual(true);
});
it('contains the required attribute keys', () => {
const fileRow = findFileRow();
// Checking strings b/c value in attributes are always strings
expect(fileRow.attributes()).toEqual({
file: {}.toString(),
level: TEST_LEVEL.toString(),
...TEST_EXTA_ARGS,
});
});
});
describe('file tree', () => {
const createChildren = () => [{ id: 1 }, { id: 2 }];
const createChildrenExpectation = (props = {}) =>
createChildren().map(file => ({
fileRowComponent: MockFileRow,
file,
...TEST_EXTA_ARGS,
...props,
}));
it.each`
key | value | desc | expectedChildren
${'isHeader'} | ${true} | ${'is shown if file is header'} | ${createChildrenExpectation({ level: 0 })}
${'opened'} | ${true} | ${'is shown if file is open'} | ${createChildrenExpectation({ level: TEST_LEVEL + 1 })}
${'isHeader'} | ${false} | ${'is hidden if file is header'} | ${[]}
${'opened'} | ${false} | ${'is hidden if file is open'} | ${[]}
`('$desc', ({ key, value, expectedChildren }) => {
createComponent({
file: {
[key]: value,
tree: createChildren(),
},
});
expect(findChildrenTreeProps()).toEqual(expectedChildren);
});
});
});
...@@ -19,7 +19,6 @@ describe('File row component', () => { ...@@ -19,7 +19,6 @@ describe('File row component', () => {
const findNewDropdown = () => vm.$el.querySelector('.ide-new-btn .dropdown'); const findNewDropdown = () => vm.$el.querySelector('.ide-new-btn .dropdown');
const findNewDropdownButton = () => vm.$el.querySelector('.ide-new-btn .dropdown button'); const findNewDropdownButton = () => vm.$el.querySelector('.ide-new-btn .dropdown button');
const findFileRow = () => vm.$el.querySelector('.file-row');
it('renders name', () => { it('renders name', () => {
createComponent({ createComponent({
...@@ -42,7 +41,7 @@ describe('File row component', () => { ...@@ -42,7 +41,7 @@ describe('File row component', () => {
}); });
spyOn(vm, '$emit').and.stub(); spyOn(vm, '$emit').and.stub();
vm.$el.querySelector('.file-row').click(); vm.$el.click();
expect(vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', vm.file.path); expect(vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', vm.file.path);
}); });
...@@ -87,7 +86,7 @@ describe('File row component', () => { ...@@ -87,7 +86,7 @@ describe('File row component', () => {
level: 0, level: 0,
}); });
expect(vm.$el.querySelector('.js-file-row-header')).not.toBe(null); expect(vm.$el.classList).toContain('js-file-row-header');
}); });
describe('new dropdown', () => { describe('new dropdown', () => {
...@@ -138,7 +137,7 @@ describe('File row component', () => { ...@@ -138,7 +137,7 @@ describe('File row component', () => {
}); });
it('closes when row triggers mouseleave', () => { it('closes when row triggers mouseleave', () => {
findFileRow().dispatchEvent(new Event('mouseleave')); vm.$el.dispatchEvent(new Event('mouseleave'));
expect(vm.dropdownOpen).toBe(false); expect(vm.dropdownOpen).toBe(false);
}); });
......
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