summaryrefslogtreecommitdiff
path: root/spec/javascripts/diffs
diff options
context:
space:
mode:
authorPhil Hughes <me@iamphill.com>2018-10-03 10:05:43 +0100
committerPhil Hughes <me@iamphill.com>2018-10-03 10:05:43 +0100
commit33c4c5b8f30c07ff30de4cd26494becd3ad058c0 (patch)
treeb8c380912b47b697d8e2d2c7e41149e69be32040 /spec/javascripts/diffs
parent974fe0797079f4f7ddc57b45d15ee7d39a06e78a (diff)
downloadgitlab-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.js18
-rw-r--r--spec/javascripts/diffs/components/changed_files_spec.js105
-rw-r--r--spec/javascripts/diffs/components/file_row_stats_spec.js33
-rw-r--r--spec/javascripts/diffs/components/tree_list_spec.js120
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js87
-rw-r--r--spec/javascripts/diffs/store/getters_spec.js27
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js41
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js109
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',
+ ]);
+ });
+ });
});