Commit d906bc28 authored by Mark Florian's avatar Mark Florian

Merge branch '228909-improve-file-by-file-diffs-prev-next-pagination' into 'master'

Improve file-by-file navigation

See merge request gitlab-org/gitlab!39719
parents 387f7c3d 6d61940d
<script> <script>
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import { GlLoadingIcon, GlButtonGroup, GlButton, GlAlert } from '@gitlab/ui'; import { GlLoadingIcon, GlButton, GlAlert, GlPagination, GlSprintf } from '@gitlab/ui';
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { getParameterByName, parseBoolean } from '~/lib/utils/common_utils'; import { getParameterByName, parseBoolean } from '~/lib/utils/common_utils';
...@@ -37,9 +37,10 @@ export default { ...@@ -37,9 +37,10 @@ export default {
TreeList, TreeList,
GlLoadingIcon, GlLoadingIcon,
PanelResizer, PanelResizer,
GlButtonGroup, GlPagination,
GlButton, GlButton,
GlAlert, GlAlert,
GlSprintf,
}, },
mixins: [glFeatureFlagsMixin()], mixins: [glFeatureFlagsMixin()],
props: { props: {
...@@ -169,6 +170,22 @@ export default { ...@@ -169,6 +170,22 @@ export default {
isDiffHead() { isDiffHead() {
return parseBoolean(getParameterByName('diff_head')); return parseBoolean(getParameterByName('diff_head'));
}, },
showFileByFileNavigation() {
return this.diffFiles.length > 1 && this.viewDiffsFileByFile;
},
currentFileNumber() {
return this.currentDiffIndex + 1;
},
previousFileNumber() {
const { currentDiffIndex } = this;
return currentDiffIndex >= 1 ? currentDiffIndex : null;
},
nextFileNumber() {
const { currentFileNumber, diffFiles } = this;
return currentFileNumber < diffFiles.length ? currentFileNumber + 1 : null;
},
}, },
watch: { watch: {
commit(newCommit, oldCommit) { commit(newCommit, oldCommit) {
...@@ -274,6 +291,9 @@ export default { ...@@ -274,6 +291,9 @@ export default {
'toggleShowTreeList', 'toggleShowTreeList',
'navigateToDiffFileIndex', 'navigateToDiffFileIndex',
]), ]),
navigateToDiffFileNumber(number) {
this.navigateToDiffFileIndex(number - 1);
},
refetchDiffData() { refetchDiffData() {
this.fetchData(false); this.fetchData(false);
}, },
...@@ -509,23 +529,22 @@ export default { ...@@ -509,23 +529,22 @@ export default {
:can-current-user-fork="canCurrentUserFork" :can-current-user-fork="canCurrentUserFork"
:view-diffs-file-by-file="viewDiffsFileByFile" :view-diffs-file-by-file="viewDiffsFileByFile"
/> />
<div v-if="viewDiffsFileByFile" class="d-flex gl-justify-content-center"> <div
<gl-button-group> v-if="showFileByFileNavigation"
<gl-button data-testid="file-by-file-navigation"
:disabled="currentDiffIndex === 0" class="gl-display-grid gl-text-center"
data-testid="singleFilePrevious" >
@click="navigateToDiffFileIndex(currentDiffIndex - 1)" <gl-pagination
> class="gl-mx-auto"
{{ __('Prev') }} :value="currentFileNumber"
</gl-button> :prev-page="previousFileNumber"
<gl-button :next-page="nextFileNumber"
:disabled="currentDiffIndex === diffFiles.length - 1" @input="navigateToDiffFileNumber"
data-testid="singleFileNext" />
@click="navigateToDiffFileIndex(currentDiffIndex + 1)" <gl-sprintf :message="__('File %{current} of %{total}')">
> <template #current>{{ currentFileNumber }}</template>
{{ __('Next') }} <template #total>{{ diffFiles.length }}</template>
</gl-button> </gl-sprintf>
</gl-button-group>
</div> </div>
</template> </template>
<no-changes v-else :changes-empty-state-illustration="changesEmptyStateIllustration" /> <no-changes v-else :changes-empty-state-illustration="changesEmptyStateIllustration" />
......
---
title: Tweak file-by-file display and add file current/total display
merge_request: 39719
author:
type: changed
...@@ -10660,6 +10660,9 @@ msgstr "" ...@@ -10660,6 +10660,9 @@ msgstr ""
msgid "File" msgid "File"
msgstr "" msgstr ""
msgid "File %{current} of %{total}"
msgstr ""
msgid "File Hooks" msgid "File Hooks"
msgstr "" msgstr ""
......
...@@ -27,9 +27,10 @@ RSpec.describe 'User views diffs file-by-file', :js do ...@@ -27,9 +27,10 @@ RSpec.describe 'User views diffs file-by-file', :js do
page.within('#diffs') do page.within('#diffs') do
expect(page).not_to have_content('This diff is collapsed') expect(page).not_to have_content('This diff is collapsed')
click_button 'Next' find('.page-link.next-page-item').click
expect(page).not_to have_content('This diff is collapsed') expect(page).not_to have_content('This diff is collapsed')
expect(page).to have_selector('.diff-file .file-title', text: 'large_diff_renamed.md')
end end
end end
end end
...@@ -25,7 +25,7 @@ RSpec.describe 'User views diffs file-by-file', :js do ...@@ -25,7 +25,7 @@ RSpec.describe 'User views diffs file-by-file', :js do
expect(page).to have_selector('.file-holder', count: 1) expect(page).to have_selector('.file-holder', count: 1)
expect(page).to have_selector('.diff-file .file-title', text: '.DS_Store') expect(page).to have_selector('.diff-file .file-title', text: '.DS_Store')
click_button 'Next' find('.page-link.next-page-item').click
expect(page).to have_selector('.file-holder', count: 1) expect(page).to have_selector('.file-holder', count: 1)
expect(page).to have_selector('.diff-file .file-title', text: '.gitignore') expect(page).to have_selector('.diff-file .file-title', text: '.gitignore')
......
import Vuex from 'vuex'; import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon, GlPagination } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { TEST_HOST } from 'spec/test_constants'; import { TEST_HOST } from 'spec/test_constants';
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
...@@ -843,13 +843,16 @@ describe('diffs/components/app', () => { ...@@ -843,13 +843,16 @@ describe('diffs/components/app', () => {
}); });
describe('pagination', () => { describe('pagination', () => {
const fileByFileNav = () => wrapper.find('[data-testid="file-by-file-navigation"]');
const paginator = () => fileByFileNav().find(GlPagination);
it('sets previous button as disabled', () => { it('sets previous button as disabled', () => {
createComponent({ viewDiffsFileByFile: true }, ({ state }) => { createComponent({ viewDiffsFileByFile: true }, ({ state }) => {
state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' }); state.diffs.diffFiles.push({ file_hash: '123' }, { file_hash: '312' });
}); });
expect(wrapper.find('[data-testid="singleFilePrevious"]').props('disabled')).toBe(true); expect(paginator().attributes('prevpage')).toBe(undefined);
expect(wrapper.find('[data-testid="singleFileNext"]').props('disabled')).toBe(false); expect(paginator().attributes('nextpage')).toBe('2');
}); });
it('sets next button as disabled', () => { it('sets next button as disabled', () => {
...@@ -858,17 +861,26 @@ describe('diffs/components/app', () => { ...@@ -858,17 +861,26 @@ describe('diffs/components/app', () => {
state.diffs.currentDiffFileId = '312'; state.diffs.currentDiffFileId = '312';
}); });
expect(wrapper.find('[data-testid="singleFilePrevious"]').props('disabled')).toBe(false); expect(paginator().attributes('prevpage')).toBe('1');
expect(wrapper.find('[data-testid="singleFileNext"]').props('disabled')).toBe(true); expect(paginator().attributes('nextpage')).toBe(undefined);
});
it("doesn't display when there's fewer than 2 files", () => {
createComponent({ viewDiffsFileByFile: true }, ({ state }) => {
state.diffs.diffFiles.push({ file_hash: '123' });
state.diffs.currentDiffFileId = '123';
});
expect(fileByFileNav().exists()).toBe(false);
}); });
it.each` it.each`
currentDiffFileId | button | index currentDiffFileId | targetFile
${'123'} | ${'singleFileNext'} | ${1} ${'123'} | ${2}
${'312'} | ${'singleFilePrevious'} | ${0} ${'312'} | ${1}
`( `(
'it calls navigateToDiffFileIndex with $index when $button is clicked', 'it calls navigateToDiffFileIndex with $index when $link is clicked',
({ currentDiffFileId, button, index }) => { async ({ currentDiffFileId, targetFile }) => {
createComponent({ viewDiffsFileByFile: true }, ({ state }) => { createComponent({ viewDiffsFileByFile: 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;
...@@ -876,11 +888,11 @@ describe('diffs/components/app', () => { ...@@ -876,11 +888,11 @@ describe('diffs/components/app', () => {
jest.spyOn(wrapper.vm, 'navigateToDiffFileIndex'); jest.spyOn(wrapper.vm, 'navigateToDiffFileIndex');
wrapper.find(`[data-testid="${button}"]`).vm.$emit('click'); paginator().vm.$emit('input', targetFile);
return wrapper.vm.$nextTick().then(() => { await wrapper.vm.$nextTick();
expect(wrapper.vm.navigateToDiffFileIndex).toHaveBeenCalledWith(index);
}); expect(wrapper.vm.navigateToDiffFileIndex).toHaveBeenCalledWith(targetFile - 1);
}, },
); );
}); });
......
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