diff options
| author | Phil Hughes <me@iamphill.com> | 2018-10-03 10:05:43 +0100 |
|---|---|---|
| committer | Phil Hughes <me@iamphill.com> | 2018-10-03 10:05:43 +0100 |
| commit | 33c4c5b8f30c07ff30de4cd26494becd3ad058c0 (patch) | |
| tree | b8c380912b47b697d8e2d2c7e41149e69be32040 /spec/javascripts/diffs | |
| parent | 974fe0797079f4f7ddc57b45d15ee7d39a06e78a (diff) | |
| download | gitlab-ce-33c4c5b8f30c07ff30de4cd26494becd3ad058c0.tar.gz | |
Added file tree to merge request diffs
This file tree displays all the diff files in a tree like format
Each file is taken and converted into a tree with folders
Each folder can be toggled open & closed
Clicking a file will scroll to the diff file & highlight with a glow affect
Searching the tree list will search only files & return a list of the
files without any folders
Each file row contains an icon to show changed, new file or deleted
Each row will also contain the added & removed lines count
Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/14249
Diffstat (limited to 'spec/javascripts/diffs')
| -rw-r--r-- | spec/javascripts/diffs/components/app_spec.js | 18 | ||||
| -rw-r--r-- | spec/javascripts/diffs/components/changed_files_spec.js | 105 | ||||
| -rw-r--r-- | spec/javascripts/diffs/components/file_row_stats_spec.js | 33 | ||||
| -rw-r--r-- | spec/javascripts/diffs/components/tree_list_spec.js | 120 | ||||
| -rw-r--r-- | spec/javascripts/diffs/store/actions_spec.js | 87 | ||||
| -rw-r--r-- | spec/javascripts/diffs/store/getters_spec.js | 27 | ||||
| -rw-r--r-- | spec/javascripts/diffs/store/mutations_spec.js | 41 | ||||
| -rw-r--r-- | spec/javascripts/diffs/store/utils_spec.js | 109 |
8 files changed, 430 insertions, 110 deletions
diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js index cf7d8df5405..a3a714678af 100644 --- a/spec/javascripts/diffs/components/app_spec.js +++ b/spec/javascripts/diffs/components/app_spec.js @@ -44,7 +44,8 @@ describe('diffs/components/app', () => { it('shows comments message, with commit', done => { vm.$store.state.diffs.commit = getDiffWithCommit().commit; - vm.$nextTick() + vm + .$nextTick() .then(() => { expect(vm.$el).toContainText('Only comments from the following commit are shown below'); expect(vm.$el).toContainElement('.blob-commit-info'); @@ -55,10 +56,14 @@ describe('diffs/components/app', () => { it('shows comments message, with old mergeRequestDiff', done => { vm.$store.state.diffs.mergeRequestDiff = { latest: false }; + vm.$store.state.diffs.targetBranch = 'master'; - vm.$nextTick() + vm + .$nextTick() .then(() => { - expect(vm.$el).toContainText("Not all comments are displayed because you're viewing an old version of the diff."); + expect(vm.$el).toContainText( + "Not all comments are displayed because you're viewing an old version of the diff.", + ); }) .then(done) .catch(done.fail); @@ -67,9 +72,12 @@ describe('diffs/components/app', () => { it('shows comments message, with startVersion', done => { vm.$store.state.diffs.startVersion = 'test'; - vm.$nextTick() + vm + .$nextTick() .then(() => { - expect(vm.$el).toContainText("Not all comments are displayed because you're comparing two versions of the diff."); + expect(vm.$el).toContainText( + "Not all comments are displayed because you're comparing two versions of the diff.", + ); }) .then(done) .catch(done.fail); diff --git a/spec/javascripts/diffs/components/changed_files_spec.js b/spec/javascripts/diffs/components/changed_files_spec.js deleted file mode 100644 index 7f21273a991..00000000000 --- a/spec/javascripts/diffs/components/changed_files_spec.js +++ /dev/null @@ -1,105 +0,0 @@ -import Vue from 'vue'; -import Vuex from 'vuex'; -import { mountComponentWithStore } from 'spec/helpers'; -import diffsModule from '~/diffs/store/modules'; -import changedFiles from '~/diffs/components/changed_files.vue'; - -describe('ChangedFiles', () => { - const Component = Vue.extend(changedFiles); - const store = new Vuex.Store({ - modules: { - diffs: diffsModule(), - }, - }); - - let vm; - - beforeEach(() => { - setFixtures(` - <div id="dummy-element"></div> - <div class="js-tabs-affix"></div> - `); - - const props = { - diffFiles: [ - { - addedLines: 10, - removedLines: 20, - blob: { - path: 'some/code.txt', - }, - filePath: 'some/code.txt', - }, - ], - }; - - vm = mountComponentWithStore(Component, { props, store }); - }); - - describe('with single file added', () => { - it('shows files changes', () => { - expect(vm.$el).toContainText('1 changed file'); - }); - - it('shows file additions and deletions', () => { - expect(vm.$el).toContainText('10 additions'); - expect(vm.$el).toContainText('20 deletions'); - }); - }); - - describe('diff view mode buttons', () => { - let inlineButton; - let parallelButton; - - beforeEach(() => { - inlineButton = vm.$el.querySelector('.js-inline-diff-button'); - parallelButton = vm.$el.querySelector('.js-parallel-diff-button'); - }); - - it('should have Inline and Side-by-side buttons', () => { - expect(inlineButton).toBeDefined(); - expect(parallelButton).toBeDefined(); - }); - - it('should add active class to Inline button', done => { - vm.$store.state.diffs.diffViewType = 'inline'; - - vm.$nextTick(() => { - expect(inlineButton.classList.contains('active')).toEqual(true); - expect(parallelButton.classList.contains('active')).toEqual(false); - - done(); - }); - }); - - it('should toggle active state of buttons when diff view type changed', done => { - vm.$store.state.diffs.diffViewType = 'parallel'; - - vm.$nextTick(() => { - expect(inlineButton.classList.contains('active')).toEqual(false); - expect(parallelButton.classList.contains('active')).toEqual(true); - - done(); - }); - }); - - describe('clicking them', () => { - it('should toggle the diff view type', done => { - parallelButton.click(); - - vm.$nextTick(() => { - expect(inlineButton.classList.contains('active')).toEqual(false); - expect(parallelButton.classList.contains('active')).toEqual(true); - - inlineButton.click(); - - vm.$nextTick(() => { - expect(inlineButton.classList.contains('active')).toEqual(true); - expect(parallelButton.classList.contains('active')).toEqual(false); - done(); - }); - }); - }); - }); - }); -}); diff --git a/spec/javascripts/diffs/components/file_row_stats_spec.js b/spec/javascripts/diffs/components/file_row_stats_spec.js new file mode 100644 index 00000000000..a8a7f3f1d82 --- /dev/null +++ b/spec/javascripts/diffs/components/file_row_stats_spec.js @@ -0,0 +1,33 @@ +import Vue from 'vue'; +import FileRowStats from '~/diffs/components/file_row_stats.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; + +describe('Diff file row stats', () => { + let Component; + let vm; + + beforeAll(() => { + Component = Vue.extend(FileRowStats); + }); + + beforeEach(() => { + vm = mountComponent(Component, { + file: { + addedLines: 20, + removedLines: 10, + }, + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders added lines count', () => { + expect(vm.$el.querySelector('.cgreen').textContent).toContain('+20'); + }); + + it('renders removed lines count', () => { + expect(vm.$el.querySelector('.cred').textContent).toContain('-10'); + }); +}); diff --git a/spec/javascripts/diffs/components/tree_list_spec.js b/spec/javascripts/diffs/components/tree_list_spec.js new file mode 100644 index 00000000000..08e25d2004e --- /dev/null +++ b/spec/javascripts/diffs/components/tree_list_spec.js @@ -0,0 +1,120 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import TreeList from '~/diffs/components/tree_list.vue'; +import createStore from '~/diffs/store/modules'; +import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; + +describe('Diffs tree list component', () => { + let Component; + let vm; + + beforeAll(() => { + Component = Vue.extend(TreeList); + }); + + beforeEach(() => { + Vue.use(Vuex); + + const store = new Vuex.Store({ + modules: { + diffs: createStore(), + }, + }); + + // Setup initial state + store.state.diffs.addedLines = 10; + store.state.diffs.removedLines = 20; + store.state.diffs.diffFiles.push('test'); + + vm = mountComponentWithStore(Component, { store }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders diff stats', () => { + expect(vm.$el.textContent).toContain('1 changed file'); + expect(vm.$el.textContent).toContain('10 additions'); + expect(vm.$el.textContent).toContain('20 deletions'); + }); + + it('renders empty text', () => { + expect(vm.$el.textContent).toContain('No files found'); + }); + + describe('with files', () => { + beforeEach(done => { + Object.assign(vm.$store.state.diffs.treeEntries, { + 'index.js': { + addedLines: 0, + changed: true, + deleted: false, + fileHash: 'test', + key: 'index.js', + name: 'index.js', + path: 'index.js', + removedLines: 0, + tempFile: true, + type: 'blob', + }, + app: { + key: 'app', + path: 'app', + name: 'app', + type: 'tree', + tree: [], + }, + }); + vm.$store.state.diffs.tree = [ + vm.$store.state.diffs.treeEntries['index.js'], + vm.$store.state.diffs.treeEntries.app, + ]; + + vm.$nextTick(done); + }); + + it('renders tree', () => { + expect(vm.$el.querySelectorAll('.file-row').length).toBe(2); + expect(vm.$el.querySelectorAll('.file-row')[0].textContent).toContain('index.js'); + expect(vm.$el.querySelectorAll('.file-row')[1].textContent).toContain('app'); + }); + + it('filters tree list to blobs matching search', done => { + vm.search = 'index'; + + vm.$nextTick(() => { + expect(vm.$el.querySelectorAll('.file-row').length).toBe(1); + expect(vm.$el.querySelectorAll('.file-row')[0].textContent).toContain('index.js'); + + done(); + }); + }); + + it('calls toggleTreeOpen when clicking folder', () => { + spyOn(vm.$store, 'dispatch').and.stub(); + + vm.$el.querySelectorAll('.file-row')[1].click(); + + expect(vm.$store.dispatch).toHaveBeenCalledWith('diffs/toggleTreeOpen', 'app'); + }); + + it('calls scrollToFile when clicking blob', () => { + spyOn(vm.$store, 'dispatch').and.stub(); + + vm.$el.querySelector('.file-row').click(); + + expect(vm.$store.dispatch).toHaveBeenCalledWith('diffs/scrollToFile', 'index.js'); + }); + }); + + describe('clearSearch', () => { + it('resets search', () => { + vm.search = 'test'; + + vm.$el.querySelector('.tree-list-clear-icon').click(); + + expect(vm.search).toBe(''); + }); + }); +}); diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js index 05b39bad6ea..aacad7a479b 100644 --- a/spec/javascripts/diffs/store/actions_spec.js +++ b/spec/javascripts/diffs/store/actions_spec.js @@ -22,6 +22,9 @@ import actions, { expandAllFiles, toggleFileDiscussions, saveDiffDiscussion, + toggleTreeOpen, + scrollToFile, + toggleShowTreeList, } from '~/diffs/store/actions'; import * as types from '~/diffs/store/mutation_types'; import { reduceDiscussionsToLineCodes } from '~/notes/stores/utils'; @@ -608,4 +611,88 @@ describe('DiffsStoreActions', () => { .catch(done.fail); }); }); + + describe('toggleTreeOpen', () => { + it('commits TOGGLE_FOLDER_OPEN', done => { + testAction( + toggleTreeOpen, + 'path', + {}, + [{ type: types.TOGGLE_FOLDER_OPEN, payload: 'path' }], + [], + done, + ); + }); + }); + + describe('scrollToFile', () => { + let commit; + + beforeEach(() => { + commit = jasmine.createSpy(); + jasmine.clock().install(); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + }); + + it('updates location hash', () => { + const state = { + treeEntries: { + path: { + fileHash: 'test', + }, + }, + }; + + scrollToFile({ state, commit }, 'path'); + + expect(document.location.hash).toBe('#test'); + }); + + it('commits UPDATE_CURRENT_DIFF_FILE_ID', () => { + const state = { + treeEntries: { + path: { + fileHash: 'test', + }, + }, + }; + + scrollToFile({ state, commit }, 'path'); + + expect(commit).toHaveBeenCalledWith(types.UPDATE_CURRENT_DIFF_FILE_ID, 'test'); + }); + + it('resets currentDiffId after timeout', () => { + const state = { + treeEntries: { + path: { + fileHash: 'test', + }, + }, + }; + + scrollToFile({ state, commit }, 'path'); + + jasmine.clock().tick(1000); + + expect(commit.calls.argsFor(1)).toEqual([types.UPDATE_CURRENT_DIFF_FILE_ID, '']); + }); + }); + + describe('toggleShowTreeList', () => { + it('commits toggle', done => { + testAction(toggleShowTreeList, null, {}, [{ type: types.TOGGLE_SHOW_TREE_LIST }], [], done); + }); + + it('updates localStorage', () => { + spyOn(localStorage, 'setItem'); + + toggleShowTreeList({ commit() {}, state: { showTreeList: true } }); + + expect(localStorage.setItem).toHaveBeenCalledWith('mr_tree_show', true); + }); + }); }); diff --git a/spec/javascripts/diffs/store/getters_spec.js b/spec/javascripts/diffs/store/getters_spec.js index 4747e437c4e..cfeaaec6980 100644 --- a/spec/javascripts/diffs/store/getters_spec.js +++ b/spec/javascripts/diffs/store/getters_spec.js @@ -291,4 +291,31 @@ describe('Diffs Module Getters', () => { expect(getters.getDiffFileByHash(localState)('123')).toBeUndefined(); }); }); + + describe('allBlobs', () => { + it('returns an array of blobs', () => { + localState.treeEntries = { + file: { + type: 'blob', + }, + tree: { + type: 'tree', + }, + }; + + expect(getters.allBlobs(localState)).toEqual([ + { + type: 'blob', + }, + ]); + }); + }); + + describe('diffFilesLength', () => { + it('returns length of diff files', () => { + localState.diffFiles.push('test', 'test 2'); + + expect(getters.diffFilesLength(localState)).toBe(2); + }); + }); }); diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js index 9a5d8dfbd15..cc8d5dc4bac 100644 --- a/spec/javascripts/diffs/store/mutations_spec.js +++ b/spec/javascripts/diffs/store/mutations_spec.js @@ -1,3 +1,4 @@ +import createState from '~/diffs/store/modules/diff_state'; import mutations from '~/diffs/store/mutations'; import * as types from '~/diffs/store/mutation_types'; import { INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants'; @@ -356,4 +357,44 @@ describe('DiffsStoreMutations', () => { expect(state.diffFiles[0].highlightedDiffLines[0].discussions.length).toEqual(0); }); }); + + describe('TOGGLE_FOLDER_OPEN', () => { + it('toggles entry opened prop', () => { + const state = { + treeEntries: { + path: { + opened: false, + }, + }, + }; + + mutations[types.TOGGLE_FOLDER_OPEN](state, 'path'); + + expect(state.treeEntries.path.opened).toBe(true); + }); + }); + + describe('TOGGLE_SHOW_TREE_LIST', () => { + it('toggles showTreeList', () => { + const state = createState(); + + mutations[types.TOGGLE_SHOW_TREE_LIST](state); + + expect(state.showTreeList).toBe(false, 'Failed to toggle showTreeList to false'); + + mutations[types.TOGGLE_SHOW_TREE_LIST](state); + + expect(state.showTreeList).toBe(true, 'Failed to toggle showTreeList to true'); + }); + }); + + describe('UPDATE_CURRENT_DIFF_FILE_ID', () => { + it('updates currentDiffFileId', () => { + const state = createState(); + + mutations[types.UPDATE_CURRENT_DIFF_FILE_ID](state, 'somefileid'); + + expect(state.currentDiffFileId).toBe('somefileid'); + }); + }); }); diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js index 897cd1483aa..e660f94c72e 100644 --- a/spec/javascripts/diffs/store/utils_spec.js +++ b/spec/javascripts/diffs/store/utils_spec.js @@ -421,4 +421,113 @@ describe('DiffsStoreUtils', () => { ).toBe(false); }); }); + + describe('generateTreeList', () => { + let files; + + beforeAll(() => { + files = [ + { + newPath: 'app/index.js', + deletedFile: false, + newFile: false, + removedLines: 10, + addedLines: 0, + fileHash: 'test', + }, + { + newPath: 'app/test/index.js', + deletedFile: false, + newFile: true, + removedLines: 0, + addedLines: 0, + fileHash: 'test', + }, + { + newPath: 'package.json', + deletedFile: true, + newFile: false, + removedLines: 0, + addedLines: 0, + fileHash: 'test', + }, + ]; + }); + + it('creates a tree of files', () => { + const { tree } = utils.generateTreeList(files); + + expect(tree).toEqual([ + { + key: 'app', + path: 'app', + name: 'app', + type: 'tree', + tree: [ + { + addedLines: 0, + changed: true, + deleted: false, + fileHash: 'test', + key: 'app/index.js', + name: 'index.js', + path: 'app/index.js', + removedLines: 10, + tempFile: false, + type: 'blob', + tree: [], + }, + { + key: 'app/test', + path: 'app/test', + name: 'test', + type: 'tree', + opened: true, + tree: [ + { + addedLines: 0, + changed: true, + deleted: false, + fileHash: 'test', + key: 'app/test/index.js', + name: 'index.js', + path: 'app/test/index.js', + removedLines: 0, + tempFile: true, + type: 'blob', + tree: [], + }, + ], + }, + ], + opened: true, + }, + { + key: 'package.json', + path: 'package.json', + name: 'package.json', + type: 'blob', + changed: true, + tempFile: false, + deleted: true, + fileHash: 'test', + addedLines: 0, + removedLines: 0, + tree: [], + }, + ]); + }); + + it('creates flat list of blobs & folders', () => { + const { treeEntries } = utils.generateTreeList(files); + + expect(Object.keys(treeEntries)).toEqual([ + 'app', + 'app/index.js', + 'app/test', + 'app/test/index.js', + 'package.json', + ]); + }); + }); }); |
