Commit 06e65234 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch 'feature/mr-file-by-file' into 'master'

Toggle File-By-File setting from the MR settings dropdown

See merge request gitlab-org/gitlab!47726
parents 133fa2a8 143c0158
...@@ -10,7 +10,10 @@ import PanelResizer from '~/vue_shared/components/panel_resizer.vue'; ...@@ -10,7 +10,10 @@ import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { isSingleViewStyle } from '~/helpers/diffs_helper'; import { isSingleViewStyle } from '~/helpers/diffs_helper';
import { updateHistory } from '~/lib/utils/url_utility'; import { updateHistory } from '~/lib/utils/url_utility';
import eventHub from '../../notes/event_hub';
import notesEventHub from '../../notes/event_hub';
import eventHub from '../event_hub';
import CompareVersions from './compare_versions.vue'; import CompareVersions from './compare_versions.vue';
import DiffFile from './diff_file.vue'; import DiffFile from './diff_file.vue';
import NoChanges from './no_changes.vue'; import NoChanges from './no_changes.vue';
...@@ -22,6 +25,7 @@ import MergeConflictWarning from './merge_conflict_warning.vue'; ...@@ -22,6 +25,7 @@ import MergeConflictWarning from './merge_conflict_warning.vue';
import CollapsedFilesWarning from './collapsed_files_warning.vue'; import CollapsedFilesWarning from './collapsed_files_warning.vue';
import { diffsApp } from '../utils/performance'; import { diffsApp } from '../utils/performance';
import { fileByFile } from '../utils/preferences';
import { import {
TREE_LIST_WIDTH_STORAGE_KEY, TREE_LIST_WIDTH_STORAGE_KEY,
...@@ -34,6 +38,7 @@ import { ...@@ -34,6 +38,7 @@ import {
ALERT_OVERFLOW_HIDDEN, ALERT_OVERFLOW_HIDDEN,
ALERT_MERGE_CONFLICT, ALERT_MERGE_CONFLICT,
ALERT_COLLAPSED_FILES, ALERT_COLLAPSED_FILES,
EVT_VIEW_FILE_BY_FILE,
} from '../constants'; } from '../constants';
export default { export default {
...@@ -114,7 +119,7 @@ export default { ...@@ -114,7 +119,7 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
viewDiffsFileByFile: { fileByFileUserPreference: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
...@@ -154,6 +159,7 @@ export default { ...@@ -154,6 +159,7 @@ export default {
'conflictResolutionPath', 'conflictResolutionPath',
'canMerge', 'canMerge',
'hasConflicts', 'hasConflicts',
'viewDiffsFileByFile',
]), ]),
...mapGetters('diffs', ['whichCollapsedTypes', 'isParallelView', 'currentDiffIndex']), ...mapGetters('diffs', ['whichCollapsedTypes', 'isParallelView', 'currentDiffIndex']),
...mapGetters(['isNotesFetched', 'getNoteableData']), ...mapGetters(['isNotesFetched', 'getNoteableData']),
...@@ -254,7 +260,7 @@ export default { ...@@ -254,7 +260,7 @@ export default {
projectPath: this.projectPath, projectPath: this.projectPath,
dismissEndpoint: this.dismissEndpoint, dismissEndpoint: this.dismissEndpoint,
showSuggestPopover: this.showSuggestPopover, showSuggestPopover: this.showSuggestPopover,
viewDiffsFileByFile: this.viewDiffsFileByFile, viewDiffsFileByFile: fileByFile(this.fileByFileUserPreference),
}); });
if (this.shouldShow) { if (this.shouldShow) {
...@@ -278,8 +284,10 @@ export default { ...@@ -278,8 +284,10 @@ export default {
created() { created() {
this.adjustView(); this.adjustView();
eventHub.$once('fetchDiffData', this.fetchData); notesEventHub.$once('fetchDiffData', this.fetchData);
eventHub.$on('refetchDiffData', this.refetchDiffData); notesEventHub.$on('refetchDiffData', this.refetchDiffData);
eventHub.$on(EVT_VIEW_FILE_BY_FILE, this.fileByFileListener);
this.CENTERED_LIMITED_CONTAINER_CLASSES = CENTERED_LIMITED_CONTAINER_CLASSES; this.CENTERED_LIMITED_CONTAINER_CLASSES = CENTERED_LIMITED_CONTAINER_CLASSES;
this.unwatchDiscussions = this.$watch( this.unwatchDiscussions = this.$watch(
...@@ -300,8 +308,10 @@ export default { ...@@ -300,8 +308,10 @@ export default {
beforeDestroy() { beforeDestroy() {
diffsApp.deinstrument(); diffsApp.deinstrument();
eventHub.$off('fetchDiffData', this.fetchData); eventHub.$off(EVT_VIEW_FILE_BY_FILE, this.fileByFileListener);
eventHub.$off('refetchDiffData', this.refetchDiffData); notesEventHub.$off('refetchDiffData', this.refetchDiffData);
notesEventHub.$off('fetchDiffData', this.fetchData);
this.removeEventListeners(); this.removeEventListeners();
}, },
methods: { methods: {
...@@ -319,7 +329,11 @@ export default { ...@@ -319,7 +329,11 @@ export default {
'scrollToFile', 'scrollToFile',
'setShowTreeList', 'setShowTreeList',
'navigateToDiffFileIndex', 'navigateToDiffFileIndex',
'setFileByFile',
]), ]),
fileByFileListener({ setting } = {}) {
this.setFileByFile({ fileByFile: setting });
},
navigateToDiffFileNumber(number) { navigateToDiffFileNumber(number) {
this.navigateToDiffFileIndex(number - 1); this.navigateToDiffFileIndex(number - 1);
}, },
...@@ -371,7 +385,7 @@ export default { ...@@ -371,7 +385,7 @@ export default {
} }
if (!this.isNotesFetched) { if (!this.isNotesFetched) {
eventHub.$emit('fetchNotesData'); notesEventHub.$emit('fetchNotesData');
} }
}, },
setDiscussions() { setDiscussions() {
......
<script> <script>
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { GlButtonGroup, GlButton, GlDropdown } from '@gitlab/ui'; import { GlButtonGroup, GlButton, GlDropdown, GlFormCheckbox } from '@gitlab/ui';
import eventHub from '../event_hub';
import { EVT_VIEW_FILE_BY_FILE } from '../constants';
import { SETTINGS_DROPDOWN } from '../i18n';
export default { export default {
i18n: SETTINGS_DROPDOWN,
components: { components: {
GlButtonGroup, GlButtonGroup,
GlButton, GlButton,
GlDropdown, GlDropdown,
GlFormCheckbox,
},
data() {
return {
checked: false,
};
}, },
computed: { computed: {
...mapGetters('diffs', ['isInlineView', 'isParallelView']), ...mapGetters('diffs', ['isInlineView', 'isParallelView']),
...mapState('diffs', ['renderTreeList', 'showWhitespace']), ...mapState('diffs', ['renderTreeList', 'showWhitespace', 'viewDiffsFileByFile']),
},
watch: {
viewDiffsFileByFile() {
this.checked = this.viewDiffsFileByFile;
},
checked() {
eventHub.$emit(EVT_VIEW_FILE_BY_FILE, { setting: this.checked });
},
},
created() {
this.checked = this.viewDiffsFileByFile;
}, },
methods: { methods: {
...mapActions('diffs', [ ...mapActions('diffs', [
...@@ -19,6 +41,9 @@ export default { ...@@ -19,6 +41,9 @@ export default {
'setRenderTreeList', 'setRenderTreeList',
'setShowWhitespace', 'setShowWhitespace',
]), ]),
toggleFileByFile() {
eventHub.$emit(EVT_VIEW_FILE_BY_FILE, { setting: !this.viewDiffsFileByFile });
},
}, },
}; };
</script> </script>
...@@ -84,5 +109,10 @@ export default { ...@@ -84,5 +109,10 @@ export default {
{{ __('Show whitespace changes') }} {{ __('Show whitespace changes') }}
</label> </label>
</div> </div>
<div class="gl-mt-3 gl-px-3">
<gl-form-checkbox v-model="checked" data-testid="file-by-file" class="gl-mb-0">
{{ $options.i18n.fileByFile }}
</gl-form-checkbox>
</div>
</gl-dropdown> </gl-dropdown>
</template> </template>
...@@ -77,6 +77,11 @@ export const ALERT_COLLAPSED_FILES = 'collapsed'; ...@@ -77,6 +77,11 @@ export const ALERT_COLLAPSED_FILES = 'collapsed';
export const DIFF_FILE_AUTOMATIC_COLLAPSE = 'automatic'; export const DIFF_FILE_AUTOMATIC_COLLAPSE = 'automatic';
export const DIFF_FILE_MANUAL_COLLAPSE = 'manual'; export const DIFF_FILE_MANUAL_COLLAPSE = 'manual';
// Diff view single file mode
export const DIFF_FILE_BY_FILE_COOKIE_NAME = 'fileViewMode';
export const DIFF_VIEW_FILE_BY_FILE = 'single';
export const DIFF_VIEW_ALL_FILES = 'all';
// State machine states // State machine states
export const STATE_IDLING = 'idle'; export const STATE_IDLING = 'idle';
export const STATE_LOADING = 'loading'; export const STATE_LOADING = 'loading';
...@@ -98,6 +103,7 @@ export const RENAMED_DIFF_TRANSITIONS = { ...@@ -98,6 +103,7 @@ export const RENAMED_DIFF_TRANSITIONS = {
// MR Diffs known events // MR Diffs known events
export const EVT_EXPAND_ALL_FILES = 'mr:diffs:expandAllFiles'; export const EVT_EXPAND_ALL_FILES = 'mr:diffs:expandAllFiles';
export const EVT_VIEW_FILE_BY_FILE = 'mr:diffs:preference:fileByFile';
export const EVT_PERF_MARK_FILE_TREE_START = 'mr:diffs:perf:fileTreeStart'; export const EVT_PERF_MARK_FILE_TREE_START = 'mr:diffs:perf:fileTreeStart';
export const EVT_PERF_MARK_FILE_TREE_END = 'mr:diffs:perf:fileTreeEnd'; export const EVT_PERF_MARK_FILE_TREE_END = 'mr:diffs:perf:fileTreeEnd';
export const EVT_PERF_MARK_DIFF_FILES_START = 'mr:diffs:perf:filesStart'; export const EVT_PERF_MARK_DIFF_FILES_START = 'mr:diffs:perf:filesStart';
......
...@@ -16,3 +16,7 @@ export const DIFF_FILE = { ...@@ -16,3 +16,7 @@ export const DIFF_FILE = {
autoCollapsed: __('Files with large changes are collapsed by default.'), autoCollapsed: __('Files with large changes are collapsed by default.'),
expand: __('Expand file'), expand: __('Expand file'),
}; };
export const SETTINGS_DROPDOWN = {
fileByFile: __('Show one file at a time'),
};
...@@ -116,7 +116,7 @@ export default function initDiffsApp(store) { ...@@ -116,7 +116,7 @@ export default function initDiffsApp(store) {
isFluidLayout: this.isFluidLayout, isFluidLayout: this.isFluidLayout,
dismissEndpoint: this.dismissEndpoint, dismissEndpoint: this.dismissEndpoint,
showSuggestPopover: this.showSuggestPopover, showSuggestPopover: this.showSuggestPopover,
viewDiffsFileByFile: this.viewDiffsFileByFile, fileByFileUserPreference: this.viewDiffsFileByFile,
}, },
}); });
}, },
......
...@@ -44,6 +44,9 @@ import { ...@@ -44,6 +44,9 @@ import {
EVT_PERF_MARK_FILE_TREE_START, EVT_PERF_MARK_FILE_TREE_START,
EVT_PERF_MARK_FILE_TREE_END, EVT_PERF_MARK_FILE_TREE_END,
EVT_PERF_MARK_DIFF_FILES_START, EVT_PERF_MARK_DIFF_FILES_START,
DIFF_VIEW_FILE_BY_FILE,
DIFF_VIEW_ALL_FILES,
DIFF_FILE_BY_FILE_COOKIE_NAME,
} from '../constants'; } from '../constants';
import { diffViewerModes } from '~/ide/constants'; import { diffViewerModes } from '~/ide/constants';
import { isCollapsed } from '../diff_file'; import { isCollapsed } from '../diff_file';
...@@ -57,6 +60,7 @@ export const setBaseConfig = ({ commit }, options) => { ...@@ -57,6 +60,7 @@ export const setBaseConfig = ({ commit }, options) => {
projectPath, projectPath,
dismissEndpoint, dismissEndpoint,
showSuggestPopover, showSuggestPopover,
viewDiffsFileByFile,
} = options; } = options;
commit(types.SET_BASE_CONFIG, { commit(types.SET_BASE_CONFIG, {
endpoint, endpoint,
...@@ -66,6 +70,7 @@ export const setBaseConfig = ({ commit }, options) => { ...@@ -66,6 +70,7 @@ export const setBaseConfig = ({ commit }, options) => {
projectPath, projectPath,
dismissEndpoint, dismissEndpoint,
showSuggestPopover, showSuggestPopover,
viewDiffsFileByFile,
}); });
}; };
...@@ -694,3 +699,14 @@ export const navigateToDiffFileIndex = ({ commit, state }, index) => { ...@@ -694,3 +699,14 @@ export const navigateToDiffFileIndex = ({ commit, state }, index) => {
commit(types.VIEW_DIFF_FILE, fileHash); commit(types.VIEW_DIFF_FILE, fileHash);
}; };
export const setFileByFile = ({ commit }, { fileByFile }) => {
const fileViewMode = fileByFile ? DIFF_VIEW_FILE_BY_FILE : DIFF_VIEW_ALL_FILES;
commit(types.SET_FILE_BY_FILE, fileByFile);
Cookies.set(DIFF_FILE_BY_FILE_COOKIE_NAME, fileViewMode);
historyPushState(
mergeUrlParams({ [DIFF_FILE_BY_FILE_COOKIE_NAME]: fileViewMode }, window.location.href),
);
};
...@@ -5,6 +5,8 @@ import { ...@@ -5,6 +5,8 @@ import {
DIFF_VIEW_COOKIE_NAME, DIFF_VIEW_COOKIE_NAME,
DIFF_WHITESPACE_COOKIE_NAME, DIFF_WHITESPACE_COOKIE_NAME,
} from '../../constants'; } from '../../constants';
import { fileByFile } from '../../utils/preferences';
import { getDefaultWhitespace } from '../utils'; import { getDefaultWhitespace } from '../utils';
const viewTypeFromQueryString = getParameterValues('view')[0]; const viewTypeFromQueryString = getParameterValues('view')[0];
...@@ -39,6 +41,7 @@ export default () => ({ ...@@ -39,6 +41,7 @@ export default () => ({
highlightedRow: null, highlightedRow: null,
renderTreeList: true, renderTreeList: true,
showWhitespace: getDefaultWhitespace(whiteSpaceFromQueryString, whiteSpaceFromCookie), showWhitespace: getDefaultWhitespace(whiteSpaceFromQueryString, whiteSpaceFromCookie),
viewDiffsFileByFile: fileByFile(),
fileFinderVisible: false, fileFinderVisible: false,
dismissEndpoint: '', dismissEndpoint: '',
showSuggestPopover: true, showSuggestPopover: true,
......
...@@ -28,6 +28,7 @@ export const SET_HIGHLIGHTED_ROW = 'SET_HIGHLIGHTED_ROW'; ...@@ -28,6 +28,7 @@ export const SET_HIGHLIGHTED_ROW = 'SET_HIGHLIGHTED_ROW';
export const SET_TREE_DATA = 'SET_TREE_DATA'; export const SET_TREE_DATA = 'SET_TREE_DATA';
export const SET_RENDER_TREE_LIST = 'SET_RENDER_TREE_LIST'; export const SET_RENDER_TREE_LIST = 'SET_RENDER_TREE_LIST';
export const SET_SHOW_WHITESPACE = 'SET_SHOW_WHITESPACE'; export const SET_SHOW_WHITESPACE = 'SET_SHOW_WHITESPACE';
export const SET_FILE_BY_FILE = 'SET_FILE_BY_FILE';
export const TOGGLE_FILE_FINDER_VISIBLE = 'TOGGLE_FILE_FINDER_VISIBLE'; export const TOGGLE_FILE_FINDER_VISIBLE = 'TOGGLE_FILE_FINDER_VISIBLE';
export const REQUEST_FULL_DIFF = 'REQUEST_FULL_DIFF'; export const REQUEST_FULL_DIFF = 'REQUEST_FULL_DIFF';
......
...@@ -36,6 +36,7 @@ export default { ...@@ -36,6 +36,7 @@ export default {
projectPath, projectPath,
dismissEndpoint, dismissEndpoint,
showSuggestPopover, showSuggestPopover,
viewDiffsFileByFile,
} = options; } = options;
Object.assign(state, { Object.assign(state, {
endpoint, endpoint,
...@@ -45,6 +46,7 @@ export default { ...@@ -45,6 +46,7 @@ export default {
projectPath, projectPath,
dismissEndpoint, dismissEndpoint,
showSuggestPopover, showSuggestPopover,
viewDiffsFileByFile,
}); });
}, },
...@@ -352,4 +354,7 @@ export default { ...@@ -352,4 +354,7 @@ export default {
[types.SET_SHOW_SUGGEST_POPOVER](state) { [types.SET_SHOW_SUGGEST_POPOVER](state) {
state.showSuggestPopover = false; state.showSuggestPopover = false;
}, },
[types.SET_FILE_BY_FILE](state, fileByFile) {
state.viewDiffsFileByFile = fileByFile;
},
}; };
import Cookies from 'js-cookie';
import { getParameterValues } from '~/lib/utils/url_utility';
import { DIFF_FILE_BY_FILE_COOKIE_NAME, DIFF_VIEW_FILE_BY_FILE } from '../constants';
export function fileByFile(pref = false) {
const search = getParameterValues(DIFF_FILE_BY_FILE_COOKIE_NAME)?.[0];
const cookie = Cookies.get(DIFF_FILE_BY_FILE_COOKIE_NAME);
let viewFileByFile = pref;
// use the cookie first, if it exists
if (cookie) {
viewFileByFile = cookie === DIFF_VIEW_FILE_BY_FILE;
}
// the search parameter of the URL should override, if it exists
if (search) {
viewFileByFile = search === DIFF_VIEW_FILE_BY_FILE;
}
return viewFileByFile;
}
---
title: Toggle File-By-File setting from the MR settings dropdown
merge_request: 47726
author:
type: added
...@@ -92,6 +92,17 @@ From there, when reviewing merge requests' **Changes** tab, you will see only on ...@@ -92,6 +92,17 @@ From there, when reviewing merge requests' **Changes** tab, you will see only on
![File-by-file diff navigation](img/file_by_file_v13_2.png) ![File-by-file diff navigation](img/file_by_file_v13_2.png)
From [GitLab 13.7](https://gitlab.com/gitlab-org/gitlab/-/issues/233898) onwards, if you want to change
this behavior, you can do so from your **User preferences** (as explained above) or directly in a
merge request:
1. Go to the merge request's **Changes** tab.
1. Click the cog icon (**{settings}**) to reveal the merge request's settings dropdown.
1. Select or unselect the checkbox **Show one file at a time** to change the setting accordingly.
This change overrides the choice you made in your user preferences and persists until you clear your
browser's cookies or change this behavior again.
#### Enable or disable file-by-file diff navigation **(CORE ONLY)** #### Enable or disable file-by-file diff navigation **(CORE ONLY)**
File-by-file diff navigation is under development but ready for production use. It is File-by-file diff navigation is under development but ready for production use. It is
......
...@@ -25107,6 +25107,9 @@ msgstr "" ...@@ -25107,6 +25107,9 @@ msgstr ""
msgid "Show me the basics" msgid "Show me the basics"
msgstr "" msgstr ""
msgid "Show one file at a time"
msgstr ""
msgid "Show only direct members" msgid "Show only direct members"
msgstr "" msgstr ""
......
...@@ -17,10 +17,14 @@ import axios from '~/lib/utils/axios_utils'; ...@@ -17,10 +17,14 @@ import axios from '~/lib/utils/axios_utils';
import * as urlUtils from '~/lib/utils/url_utility'; import * as urlUtils from '~/lib/utils/url_utility';
import diffsMockData from '../mock_data/merge_request_diffs'; import diffsMockData from '../mock_data/merge_request_diffs';
import { EVT_VIEW_FILE_BY_FILE } from '~/diffs/constants';
import eventHub from '~/diffs/event_hub';
const mergeRequestDiff = { version_index: 1 }; const mergeRequestDiff = { version_index: 1 };
const TEST_ENDPOINT = `${TEST_HOST}/diff/endpoint`; const TEST_ENDPOINT = `${TEST_HOST}/diff/endpoint`;
const COMMIT_URL = '[BASE URL]/OLD'; const COMMIT_URL = `${TEST_HOST}/COMMIT/OLD`;
const UPDATED_COMMIT_URL = '[BASE URL]/NEW'; const UPDATED_COMMIT_URL = `${TEST_HOST}/COMMIT/NEW`;
function getCollapsedFilesWarning(wrapper) { function getCollapsedFilesWarning(wrapper) {
return wrapper.find(CollapsedFilesWarning); return wrapper.find(CollapsedFilesWarning);
...@@ -61,7 +65,7 @@ describe('diffs/components/app', () => { ...@@ -61,7 +65,7 @@ describe('diffs/components/app', () => {
changesEmptyStateIllustration: '', changesEmptyStateIllustration: '',
dismissEndpoint: '', dismissEndpoint: '',
showSuggestPopover: true, showSuggestPopover: true,
viewDiffsFileByFile: false, fileByFileUserPreference: false,
...props, ...props,
}, },
provide, provide,
...@@ -700,12 +704,14 @@ describe('diffs/components/app', () => { ...@@ -700,12 +704,14 @@ describe('diffs/components/app', () => {
}); });
describe('file-by-file', () => { describe('file-by-file', () => {
it('renders a single diff', () => { it('renders a single diff', async () => {
createComponent({ viewDiffsFileByFile: true }, ({ state }) => { createComponent({ fileByFileUserPreference: true }, ({ state }) => {
state.diffs.diffFiles.push({ file_hash: '123' }); state.diffs.diffFiles.push({ file_hash: '123' });
state.diffs.diffFiles.push({ file_hash: '312' }); state.diffs.diffFiles.push({ file_hash: '312' });
}); });
await wrapper.vm.$nextTick();
expect(wrapper.findAll(DiffFile).length).toBe(1); expect(wrapper.findAll(DiffFile).length).toBe(1);
}); });
...@@ -713,31 +719,37 @@ describe('diffs/components/app', () => { ...@@ -713,31 +719,37 @@ describe('diffs/components/app', () => {
const fileByFileNav = () => wrapper.find('[data-testid="file-by-file-navigation"]'); const fileByFileNav = () => wrapper.find('[data-testid="file-by-file-navigation"]');
const paginator = () => fileByFileNav().find(GlPagination); const paginator = () => fileByFileNav().find(GlPagination);
it('sets previous button as disabled', () => { it('sets previous button as disabled', async () => {
createComponent({ viewDiffsFileByFile: true }, ({ state }) => { createComponent({ fileByFileUserPreference: true }, ({ state }) => {
state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' }); state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
}); });
await wrapper.vm.$nextTick();
expect(paginator().attributes('prevpage')).toBe(undefined); expect(paginator().attributes('prevpage')).toBe(undefined);
expect(paginator().attributes('nextpage')).toBe('2'); expect(paginator().attributes('nextpage')).toBe('2');
}); });
it('sets next button as disabled', () => { it('sets next button as disabled', async () => {
createComponent({ viewDiffsFileByFile: true }, ({ state }) => { createComponent({ fileByFileUserPreference: true }, ({ state }) => {
state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' }); state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
state.diffs.currentDiffFileId = '312'; state.diffs.currentDiffFileId = '312';
}); });
await wrapper.vm.$nextTick();
expect(paginator().attributes('prevpage')).toBe('1'); expect(paginator().attributes('prevpage')).toBe('1');
expect(paginator().attributes('nextpage')).toBe(undefined); expect(paginator().attributes('nextpage')).toBe(undefined);
}); });
it("doesn't display when there's fewer than 2 files", () => { it("doesn't display when there's fewer than 2 files", async () => {
createComponent({ viewDiffsFileByFile: true }, ({ state }) => { createComponent({ fileByFileUserPreference: true }, ({ state }) => {
state.diffs.diffFiles.push({ file_hash: '123' }); state.diffs.diffFiles.push({ file_hash: '123' });
state.diffs.currentDiffFileId = '123'; state.diffs.currentDiffFileId = '123';
}); });
await wrapper.vm.$nextTick();
expect(fileByFileNav().exists()).toBe(false); expect(fileByFileNav().exists()).toBe(false);
}); });
...@@ -748,11 +760,13 @@ describe('diffs/components/app', () => { ...@@ -748,11 +760,13 @@ describe('diffs/components/app', () => {
`( `(
'it calls navigateToDiffFileIndex with $index when $link is clicked', 'it calls navigateToDiffFileIndex with $index when $link is clicked',
async ({ currentDiffFileId, targetFile }) => { async ({ currentDiffFileId, targetFile }) => {
createComponent({ viewDiffsFileByFile: true }, ({ state }) => { createComponent({ fileByFileUserPreference: true }, ({ state }) => {
state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' }); state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
state.diffs.currentDiffFileId = currentDiffFileId; state.diffs.currentDiffFileId = currentDiffFileId;
}); });
await wrapper.vm.$nextTick();
jest.spyOn(wrapper.vm, 'navigateToDiffFileIndex'); jest.spyOn(wrapper.vm, 'navigateToDiffFileIndex');
paginator().vm.$emit('input', targetFile); paginator().vm.$emit('input', targetFile);
...@@ -763,5 +777,24 @@ describe('diffs/components/app', () => { ...@@ -763,5 +777,24 @@ describe('diffs/components/app', () => {
}, },
); );
}); });
describe('control via event stream', () => {
it.each`
setting
${true}
${false}
`(
'triggers the action with the new fileByFile setting - $setting - when the event with that setting is received',
async ({ setting }) => {
createComponent();
await wrapper.vm.$nextTick();
eventHub.$emit(EVT_VIEW_FILE_BY_FILE, { setting });
await wrapper.vm.$nextTick();
expect(store.state.diffs.viewDiffsFileByFile).toBe(setting);
},
);
});
}); });
}); });
...@@ -2,12 +2,18 @@ import { mount, createLocalVue } from '@vue/test-utils'; ...@@ -2,12 +2,18 @@ import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import diffModule from '~/diffs/store/modules'; import diffModule from '~/diffs/store/modules';
import SettingsDropdown from '~/diffs/components/settings_dropdown.vue'; import SettingsDropdown from '~/diffs/components/settings_dropdown.vue';
import { PARALLEL_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE } from '~/diffs/constants'; import {
EVT_VIEW_FILE_BY_FILE,
PARALLEL_DIFF_VIEW_TYPE,
INLINE_DIFF_VIEW_TYPE,
} from '~/diffs/constants';
import eventHub from '~/diffs/event_hub';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
describe('Diff settings dropdown component', () => { describe('Diff settings dropdown component', () => {
let wrapper;
let vm; let vm;
let actions; let actions;
...@@ -25,10 +31,15 @@ describe('Diff settings dropdown component', () => { ...@@ -25,10 +31,15 @@ describe('Diff settings dropdown component', () => {
extendStore(store); extendStore(store);
vm = mount(SettingsDropdown, { wrapper = mount(SettingsDropdown, {
localVue, localVue,
store, store,
}); });
vm = wrapper.vm;
}
function getFileByFileCheckbox(vueWrapper) {
return vueWrapper.find('[data-testid="file-by-file"]');
} }
beforeEach(() => { beforeEach(() => {
...@@ -41,14 +52,14 @@ describe('Diff settings dropdown component', () => { ...@@ -41,14 +52,14 @@ describe('Diff settings dropdown component', () => {
}); });
afterEach(() => { afterEach(() => {
vm.destroy(); wrapper.destroy();
}); });
describe('tree view buttons', () => { describe('tree view buttons', () => {
it('list view button dispatches setRenderTreeList with false', () => { it('list view button dispatches setRenderTreeList with false', () => {
createComponent(); createComponent();
vm.find('.js-list-view').trigger('click'); wrapper.find('.js-list-view').trigger('click');
expect(actions.setRenderTreeList).toHaveBeenCalledWith(expect.anything(), false); expect(actions.setRenderTreeList).toHaveBeenCalledWith(expect.anything(), false);
}); });
...@@ -56,7 +67,7 @@ describe('Diff settings dropdown component', () => { ...@@ -56,7 +67,7 @@ describe('Diff settings dropdown component', () => {
it('tree view button dispatches setRenderTreeList with true', () => { it('tree view button dispatches setRenderTreeList with true', () => {
createComponent(); createComponent();
vm.find('.js-tree-view').trigger('click'); wrapper.find('.js-tree-view').trigger('click');
expect(actions.setRenderTreeList).toHaveBeenCalledWith(expect.anything(), true); expect(actions.setRenderTreeList).toHaveBeenCalledWith(expect.anything(), true);
}); });
...@@ -68,8 +79,8 @@ describe('Diff settings dropdown component', () => { ...@@ -68,8 +79,8 @@ describe('Diff settings dropdown component', () => {
}); });
}); });
expect(vm.find('.js-list-view').classes('selected')).toBe(true); expect(wrapper.find('.js-list-view').classes('selected')).toBe(true);
expect(vm.find('.js-tree-view').classes('selected')).toBe(false); expect(wrapper.find('.js-tree-view').classes('selected')).toBe(false);
}); });
it('sets tree button as selected when renderTreeList is true', () => { it('sets tree button as selected when renderTreeList is true', () => {
...@@ -79,8 +90,8 @@ describe('Diff settings dropdown component', () => { ...@@ -79,8 +90,8 @@ describe('Diff settings dropdown component', () => {
}); });
}); });
expect(vm.find('.js-list-view').classes('selected')).toBe(false); expect(wrapper.find('.js-list-view').classes('selected')).toBe(false);
expect(vm.find('.js-tree-view').classes('selected')).toBe(true); expect(wrapper.find('.js-tree-view').classes('selected')).toBe(true);
}); });
}); });
...@@ -92,8 +103,8 @@ describe('Diff settings dropdown component', () => { ...@@ -92,8 +103,8 @@ describe('Diff settings dropdown component', () => {
}); });
}); });
expect(vm.find('.js-inline-diff-button').classes('selected')).toBe(true); expect(wrapper.find('.js-inline-diff-button').classes('selected')).toBe(true);
expect(vm.find('.js-parallel-diff-button').classes('selected')).toBe(false); expect(wrapper.find('.js-parallel-diff-button').classes('selected')).toBe(false);
}); });
it('sets parallel button as selected', () => { it('sets parallel button as selected', () => {
...@@ -103,14 +114,14 @@ describe('Diff settings dropdown component', () => { ...@@ -103,14 +114,14 @@ describe('Diff settings dropdown component', () => {
}); });
}); });
expect(vm.find('.js-inline-diff-button').classes('selected')).toBe(false); expect(wrapper.find('.js-inline-diff-button').classes('selected')).toBe(false);
expect(vm.find('.js-parallel-diff-button').classes('selected')).toBe(true); expect(wrapper.find('.js-parallel-diff-button').classes('selected')).toBe(true);
}); });
it('calls setInlineDiffViewType when clicking inline button', () => { it('calls setInlineDiffViewType when clicking inline button', () => {
createComponent(); createComponent();
vm.find('.js-inline-diff-button').trigger('click'); wrapper.find('.js-inline-diff-button').trigger('click');
expect(actions.setInlineDiffViewType).toHaveBeenCalled(); expect(actions.setInlineDiffViewType).toHaveBeenCalled();
}); });
...@@ -118,7 +129,7 @@ describe('Diff settings dropdown component', () => { ...@@ -118,7 +129,7 @@ describe('Diff settings dropdown component', () => {
it('calls setParallelDiffViewType when clicking parallel button', () => { it('calls setParallelDiffViewType when clicking parallel button', () => {
createComponent(); createComponent();
vm.find('.js-parallel-diff-button').trigger('click'); wrapper.find('.js-parallel-diff-button').trigger('click');
expect(actions.setParallelDiffViewType).toHaveBeenCalled(); expect(actions.setParallelDiffViewType).toHaveBeenCalled();
}); });
...@@ -132,7 +143,7 @@ describe('Diff settings dropdown component', () => { ...@@ -132,7 +143,7 @@ describe('Diff settings dropdown component', () => {
}); });
}); });
expect(vm.find('#show-whitespace').element.checked).toBe(false); expect(wrapper.find('#show-whitespace').element.checked).toBe(false);
}); });
it('sets as checked when showWhitespace is true', () => { it('sets as checked when showWhitespace is true', () => {
...@@ -142,13 +153,13 @@ describe('Diff settings dropdown component', () => { ...@@ -142,13 +153,13 @@ describe('Diff settings dropdown component', () => {
}); });
}); });
expect(vm.find('#show-whitespace').element.checked).toBe(true); expect(wrapper.find('#show-whitespace').element.checked).toBe(true);
}); });
it('calls setShowWhitespace on change', () => { it('calls setShowWhitespace on change', () => {
createComponent(); createComponent();
const checkbox = vm.find('#show-whitespace'); const checkbox = wrapper.find('#show-whitespace');
checkbox.element.checked = true; checkbox.element.checked = true;
checkbox.trigger('change'); checkbox.trigger('change');
...@@ -159,4 +170,52 @@ describe('Diff settings dropdown component', () => { ...@@ -159,4 +170,52 @@ describe('Diff settings dropdown component', () => {
}); });
}); });
}); });
describe('file-by-file toggle', () => {
beforeEach(() => {
jest.spyOn(eventHub, '$emit');
});
it.each`
fileByFile | checked
${true} | ${true}
${false} | ${false}
`(
'sets { checked: $checked } if the fileByFile setting is $fileByFile',
async ({ fileByFile, checked }) => {
createComponent(store => {
Object.assign(store.state.diffs, {
viewDiffsFileByFile: fileByFile,
});
});
await vm.$nextTick();
expect(vm.checked).toBe(checked);
},
);
it.each`
start | emit
${true} | ${false}
${false} | ${true}
`(
'when the file by file setting starts as $start, toggling the checkbox should emit an event set to $emit',
async ({ start, emit }) => {
createComponent(store => {
Object.assign(store.state.diffs, {
viewDiffsFileByFile: start,
});
});
await vm.$nextTick();
getFileByFileCheckbox(wrapper).trigger('click');
await vm.$nextTick();
expect(eventHub.$emit).toHaveBeenCalledWith(EVT_VIEW_FILE_BY_FILE, { setting: emit });
},
);
});
}); });
...@@ -48,6 +48,7 @@ import { ...@@ -48,6 +48,7 @@ import {
moveToNeighboringCommit, moveToNeighboringCommit,
setCurrentDiffFileIdFromNote, setCurrentDiffFileIdFromNote,
navigateToDiffFileIndex, navigateToDiffFileIndex,
setFileByFile,
} from '~/diffs/store/actions'; } from '~/diffs/store/actions';
import eventHub from '~/notes/event_hub'; import eventHub from '~/notes/event_hub';
import * as types from '~/diffs/store/mutation_types'; import * as types from '~/diffs/store/mutation_types';
...@@ -1455,4 +1456,20 @@ describe('DiffsStoreActions', () => { ...@@ -1455,4 +1456,20 @@ describe('DiffsStoreActions', () => {
); );
}); });
}); });
describe('setFileByFile', () => {
it.each`
value
${true}
${false}
`('commits SET_FILE_BY_FILE with the new value $value', ({ value }) => {
return testAction(
setFileByFile,
{ fileByFile: value },
{ viewDiffsFileByFile: null },
[{ type: types.SET_FILE_BY_FILE, payload: value }],
[],
);
});
});
}); });
...@@ -892,4 +892,18 @@ describe('DiffsStoreMutations', () => { ...@@ -892,4 +892,18 @@ describe('DiffsStoreMutations', () => {
expect(state.showSuggestPopover).toBe(false); expect(state.showSuggestPopover).toBe(false);
}); });
}); });
describe('SET_FILE_BY_FILE', () => {
it.each`
value | opposite
${true} | ${false}
${false} | ${true}
`('sets viewDiffsFileByFile to $value', ({ value, opposite }) => {
const state = { viewDiffsFileByFile: opposite };
mutations[types.SET_FILE_BY_FILE](state, value);
expect(state.viewDiffsFileByFile).toBe(value);
});
});
}); });
import Cookies from 'js-cookie';
import { getParameterValues } from '~/lib/utils/url_utility';
import { fileByFile } from '~/diffs/utils/preferences';
import {
DIFF_FILE_BY_FILE_COOKIE_NAME,
DIFF_VIEW_FILE_BY_FILE,
DIFF_VIEW_ALL_FILES,
} from '~/diffs/constants';
jest.mock('~/lib/utils/url_utility');
describe('diffs preferences', () => {
describe('fileByFile', () => {
it.each`
result | preference | cookie | searchParam
${false} | ${false} | ${undefined} | ${undefined}
${true} | ${true} | ${undefined} | ${undefined}
${true} | ${false} | ${DIFF_VIEW_FILE_BY_FILE} | ${undefined}
${false} | ${true} | ${DIFF_VIEW_ALL_FILES} | ${undefined}
${true} | ${false} | ${undefined} | ${[DIFF_VIEW_FILE_BY_FILE]}
${false} | ${true} | ${undefined} | ${[DIFF_VIEW_ALL_FILES]}
${true} | ${false} | ${DIFF_VIEW_FILE_BY_FILE} | ${[DIFF_VIEW_FILE_BY_FILE]}
${true} | ${true} | ${DIFF_VIEW_ALL_FILES} | ${[DIFF_VIEW_FILE_BY_FILE]}
${false} | ${false} | ${DIFF_VIEW_ALL_FILES} | ${[DIFF_VIEW_ALL_FILES]}
${false} | ${true} | ${DIFF_VIEW_FILE_BY_FILE} | ${[DIFF_VIEW_ALL_FILES]}
`(
'should return $result when { preference: $preference, cookie: $cookie, search: $searchParam }',
({ result, preference, cookie, searchParam }) => {
if (cookie) {
Cookies.set(DIFF_FILE_BY_FILE_COOKIE_NAME, cookie);
}
getParameterValues.mockReturnValue(searchParam);
expect(fileByFile(preference)).toBe(result);
},
);
});
});
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