Commit bc4ad61f authored by Phil Hughes's avatar Phil Hughes

Merge branch '33070-gl-pagination-table-pagination' into 'master'

Use GitLab UI's pagination component in table_pagination.vue

See merge request gitlab-org/gitlab!18602
parents e1ba2dd2 5d401e2f
...@@ -4,6 +4,7 @@ import 'core-js/es/array/find'; ...@@ -4,6 +4,7 @@ import 'core-js/es/array/find';
import 'core-js/es/array/find-index'; import 'core-js/es/array/find-index';
import 'core-js/es/array/from'; import 'core-js/es/array/from';
import 'core-js/es/array/includes'; import 'core-js/es/array/includes';
import 'core-js/es/number/is-integer';
import 'core-js/es/object/assign'; import 'core-js/es/object/assign';
import 'core-js/es/object/values'; import 'core-js/es/object/values';
import 'core-js/es/object/entries'; import 'core-js/es/object/entries';
......
<script> <script>
import { GlPagination } from '@gitlab/ui';
import { import {
PAGINATION_UI_BUTTON_LIMIT,
UI_LIMIT,
SPREAD,
PREV, PREV,
NEXT, NEXT,
FIRST, LABEL_FIRST_PAGE,
LAST, LABEL_PREV_PAGE,
LABEL_NEXT_PAGE,
LABEL_LAST_PAGE,
} from '~/vue_shared/components/pagination/constants'; } from '~/vue_shared/components/pagination/constants';
export default { export default {
components: {
GlPagination,
},
props: { props: {
/** /**
This function will take the information given by the pagination component This function will take the information given by the pagination component
...@@ -46,113 +49,34 @@ export default { ...@@ -46,113 +49,34 @@ export default {
}, },
}, },
computed: { computed: {
prev() {
return this.pageInfo.previousPage;
},
next() {
return this.pageInfo.nextPage;
},
getItems() {
const { totalPages, nextPage, previousPage, page } = this.pageInfo;
const items = [];
if (page > 1) {
items.push({ title: FIRST, first: true });
}
if (previousPage) {
items.push({ title: PREV, prev: true });
} else {
items.push({ title: PREV, disabled: true, prev: true });
}
if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true });
if (totalPages) {
const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1);
const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, totalPages);
for (let i = start; i <= end; i += 1) {
const isActive = i === page;
items.push({ title: i, active: isActive, page: true });
}
if (totalPages - page > PAGINATION_UI_BUTTON_LIMIT) {
items.push({ title: SPREAD, separator: true, page: true });
}
}
if (nextPage) {
items.push({ title: NEXT, next: true });
} else {
items.push({ title: NEXT, disabled: true, next: true });
}
if (totalPages && totalPages - page >= 1) {
items.push({ title: LAST, last: true });
}
return items;
},
showPagination() { showPagination() {
return this.pageInfo.nextPage || this.pageInfo.previousPage; return this.pageInfo.nextPage || this.pageInfo.previousPage;
}, },
}, },
methods: { prevText: PREV,
changePage(text, isDisabled) { nextText: NEXT,
if (isDisabled) return; labelFirstPage: LABEL_FIRST_PAGE,
labelPrevPage: LABEL_PREV_PAGE,
const { totalPages, nextPage, previousPage } = this.pageInfo; labelNextPage: LABEL_NEXT_PAGE,
labelLastPage: LABEL_LAST_PAGE,
switch (text) {
case SPREAD:
break;
case LAST:
this.change(totalPages);
break;
case NEXT:
this.change(nextPage);
break;
case PREV:
this.change(previousPage);
break;
case FIRST:
this.change(1);
break;
default:
this.change(Number(text));
break;
}
},
hideOnSmallScreen(item) {
return !item.first && !item.last && !item.next && !item.prev && !item.active;
},
},
}; };
</script> </script>
<template> <template>
<div v-if="showPagination" class="gl-pagination prepend-top-default"> <gl-pagination
<ul class="pagination justify-content-center"> v-if="showPagination"
<li class="justify-content-center prepend-top-default"
v-for="(item, index) in getItems" v-bind="$attrs"
:key="index" :value="pageInfo.page"
:class="{ :per-page="pageInfo.perPage"
page: item.page, :total-items="pageInfo.total"
'js-previous-button': item.prev, :prev-page="pageInfo.previousPage"
'js-next-button': item.next, :prev-text="$options.prevText"
'js-last-button': item.last, :next-page="pageInfo.nextPage"
'js-first-button': item.first, :next-text="$options.nextText"
'd-none d-md-block': hideOnSmallScreen(item), :label-first-page="$options.labelFirstPage"
separator: item.separator, :label-prev-page="$options.labelPrevPage"
active: item.active, :label-next-page="$options.labelNextPage"
disabled: item.disabled || item.separator, :label-last-page="$options.labelLastPage"
}" @input="change"
class="page-item" />
>
<button type="button" class="page-link" @click="changePage(item.title, item.disabled)">
{{ item.title }}
</button>
</li>
</ul>
</div>
</template> </template>
...@@ -92,13 +92,13 @@ describe('Environment', () => { ...@@ -92,13 +92,13 @@ describe('Environment', () => {
describe('pagination', () => { describe('pagination', () => {
it('should render pagination', () => { it('should render pagination', () => {
expect(component.$el.querySelectorAll('.gl-pagination li').length).toEqual(5); expect(component.$el.querySelectorAll('.gl-pagination li').length).toEqual(9);
}); });
it('should make an API request when page is clicked', done => { it('should make an API request when page is clicked', done => {
spyOn(component, 'updateContent'); spyOn(component, 'updateContent');
setTimeout(() => { setTimeout(() => {
component.$el.querySelector('.gl-pagination li:nth-child(5) .page-link').click(); component.$el.querySelector('.gl-pagination li:nth-child(3) .page-link').click();
expect(component.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' }); expect(component.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' });
done(); done();
......
...@@ -49,11 +49,11 @@ describe 'Environments page', :js do ...@@ -49,11 +49,11 @@ describe 'Environments page', :js do
it 'renders second page of pipelines' do it 'renders second page of pipelines' do
visit_environments(project, scope: 'available') visit_environments(project, scope: 'available')
find('.js-next-button').click find('.page-link.next-page-item').click
wait_for_requests wait_for_requests
expect(page).to have_selector('.gl-pagination .page', count: 2) expect(page).to have_selector('.gl-pagination .page-link', count: 4)
expect(find('.gl-pagination .page-item.active .page-link').text).to eq("2") expect(find('.gl-pagination .page-link.active').text).to eq("2")
end end
end end
......
...@@ -592,15 +592,15 @@ describe 'Pipelines', :js do ...@@ -592,15 +592,15 @@ describe 'Pipelines', :js do
visit project_pipelines_path(project, page: '2') visit project_pipelines_path(project, page: '2')
wait_for_requests wait_for_requests
expect(page).to have_selector('.gl-pagination .page', count: 2) expect(page).to have_selector('.gl-pagination .page-link', count: 4)
end end
it 'shows updated content' do it 'shows updated content' do
visit project_pipelines_path(project) visit project_pipelines_path(project)
wait_for_requests wait_for_requests
page.find('.js-next-button .page-link').click page.find('.page-link.next-page-item').click
expect(page).to have_selector('.gl-pagination .page', count: 2) expect(page).to have_selector('.gl-pagination .page-link', count: 4)
end end
end end
end end
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import { GlPagination } from '@gitlab/ui';
describe('Pagination component', () => { describe('Pagination component', () => {
let wrapper; let wrapper;
...@@ -12,15 +13,6 @@ describe('Pagination component', () => { ...@@ -12,15 +13,6 @@ describe('Pagination component', () => {
}); });
}; };
const findFirstButtonLink = () => wrapper.find('.js-first-button .page-link');
const findPreviousButton = () => wrapper.find('.js-previous-button');
const findPreviousButtonLink = () => wrapper.find('.js-previous-button .page-link');
const findNextButton = () => wrapper.find('.js-next-button');
const findNextButtonLink = () => wrapper.find('.js-next-button .page-link');
const findLastButtonLink = () => wrapper.find('.js-last-button .page-link');
const findPages = () => wrapper.findAll('.page');
const findSeparator = () => wrapper.find('.separator');
beforeEach(() => { beforeEach(() => {
spy = jest.fn(); spy = jest.fn();
}); });
...@@ -46,290 +38,54 @@ describe('Pagination component', () => { ...@@ -46,290 +38,54 @@ describe('Pagination component', () => {
expect(wrapper.isEmpty()).toBe(true); expect(wrapper.isEmpty()).toBe(true);
}); });
describe('prev button', () => { it('renders if there is a next page', () => {
it('should be disabled and non clickable', () => { mountComponent({
mountComponent({ pageInfo: {
pageInfo: { nextPage: 2,
nextPage: 2, page: 1,
page: 1, perPage: 20,
perPage: 20, previousPage: NaN,
previousPage: NaN, total: 15,
total: 84, totalPages: 1,
totalPages: 5, },
}, change: spy,
change: spy,
});
expect(findPreviousButton().classes()).toContain('disabled');
findPreviousButtonLink().trigger('click');
expect(spy).not.toHaveBeenCalled();
});
it('should be disabled and non clickable when total and totalPages are NaN', () => {
mountComponent({
pageInfo: {
nextPage: 2,
page: 1,
perPage: 20,
previousPage: NaN,
total: NaN,
totalPages: NaN,
},
change: spy,
});
expect(findPreviousButton().classes()).toContain('disabled');
findPreviousButtonLink().trigger('click');
expect(spy).not.toHaveBeenCalled();
});
it('should be enabled and clickable', () => {
mountComponent({
pageInfo: {
nextPage: 3,
page: 2,
perPage: 20,
previousPage: 1,
total: 84,
totalPages: 5,
},
change: spy,
});
findPreviousButtonLink().trigger('click');
expect(spy).toHaveBeenCalledWith(1);
});
it('should be enabled and clickable when total and totalPages are NaN', () => {
mountComponent({
pageInfo: {
nextPage: 3,
page: 2,
perPage: 20,
previousPage: 1,
total: NaN,
totalPages: NaN,
},
change: spy,
});
findPreviousButtonLink().trigger('click');
expect(spy).toHaveBeenCalledWith(1);
});
});
describe('first button', () => {
it('should call the change callback with the first page', () => {
mountComponent({
pageInfo: {
nextPage: 3,
page: 2,
perPage: 20,
previousPage: 1,
total: 84,
totalPages: 5,
},
change: spy,
});
const button = findFirstButtonLink();
expect(button.text().trim()).toEqual('« First');
button.trigger('click');
expect(spy).toHaveBeenCalledWith(1);
});
it('should call the change callback with the first page when total and totalPages are NaN', () => {
mountComponent({
pageInfo: {
nextPage: 3,
page: 2,
perPage: 20,
previousPage: 1,
total: NaN,
totalPages: NaN,
},
change: spy,
});
const button = findFirstButtonLink();
expect(button.text().trim()).toEqual('« First');
button.trigger('click');
expect(spy).toHaveBeenCalledWith(1);
});
});
describe('last button', () => {
it('should call the change callback with the last page', () => {
mountComponent({
pageInfo: {
nextPage: 3,
page: 2,
perPage: 20,
previousPage: 1,
total: 84,
totalPages: 5,
},
change: spy,
});
const button = findLastButtonLink();
expect(button.text().trim()).toEqual('Last »');
button.trigger('click');
expect(spy).toHaveBeenCalledWith(5);
});
it('should not render', () => {
mountComponent({
pageInfo: {
nextPage: 3,
page: 2,
perPage: 20,
previousPage: 1,
total: NaN,
totalPages: NaN,
},
change: spy,
});
expect(findLastButtonLink().exists()).toBe(false);
});
});
describe('next button', () => {
it('should be disabled and non clickable', () => {
mountComponent({
pageInfo: {
nextPage: NaN,
page: 5,
perPage: 20,
previousPage: 4,
total: 84,
totalPages: 5,
},
change: spy,
});
expect(
findNextButton()
.text()
.trim(),
).toEqual('Next ›');
findNextButtonLink().trigger('click');
expect(spy).not.toHaveBeenCalled();
});
it('should be disabled and non clickable when total and totalPages are NaN', () => {
mountComponent({
pageInfo: {
nextPage: NaN,
page: 5,
perPage: 20,
previousPage: 4,
total: NaN,
totalPages: NaN,
},
change: spy,
});
expect(
findNextButton()
.text()
.trim(),
).toEqual('Next ›');
findNextButtonLink().trigger('click');
expect(spy).not.toHaveBeenCalled();
});
it('should be enabled and clickable', () => {
mountComponent({
pageInfo: {
nextPage: 4,
page: 3,
perPage: 20,
previousPage: 2,
total: 84,
totalPages: 5,
},
change: spy,
});
findNextButtonLink().trigger('click');
expect(spy).toHaveBeenCalledWith(4);
}); });
it('should be enabled and clickable when total and totalPages are NaN', () => { expect(wrapper.isEmpty()).toBe(false);
mountComponent({
pageInfo: {
nextPage: 4,
page: 3,
perPage: 20,
previousPage: 2,
total: NaN,
totalPages: NaN,
},
change: spy,
});
findNextButtonLink().trigger('click');
expect(spy).toHaveBeenCalledWith(4);
});
}); });
describe('numbered buttons', () => { it('renders if there is a prev page', () => {
it('should render 5 pages', () => { mountComponent({
mountComponent({ pageInfo: {
pageInfo: { nextPage: NaN,
nextPage: 4, page: 2,
page: 3, perPage: 20,
perPage: 20, previousPage: 1,
previousPage: 2, total: 15,
total: 84, totalPages: 1,
totalPages: 5, },
}, change: spy,
change: spy,
});
expect(findPages().length).toEqual(5);
}); });
it('should not render any page', () => { expect(wrapper.isEmpty()).toBe(false);
mountComponent({
pageInfo: {
nextPage: 4,
page: 3,
perPage: 20,
previousPage: 2,
total: NaN,
totalPages: NaN,
},
change: spy,
});
expect(findPages().length).toEqual(0);
});
}); });
});
describe('spread operator', () => { describe('events', () => {
it('should render', () => { it('calls change method when page changes', () => {
mountComponent({ mountComponent({
pageInfo: { pageInfo: {
nextPage: 4, nextPage: NaN,
page: 3, page: 2,
perPage: 20, perPage: 20,
previousPage: 2, previousPage: 1,
total: 84, total: 15,
totalPages: 10, totalPages: 1,
}, },
change: spy, change: spy,
});
expect(
findSeparator()
.text()
.trim(),
).toEqual('...');
});
it('should not render', () => {
mountComponent({
pageInfo: {
nextPage: 4,
page: 3,
perPage: 20,
previousPage: 2,
total: NaN,
totalPages: NaN,
},
change: spy,
});
expect(findSeparator().exists()).toBe(false);
}); });
wrapper.find(GlPagination).vm.$emit('input', 3);
expect(spy).toHaveBeenCalledWith(3);
}); });
}); });
}); });
...@@ -83,7 +83,7 @@ describe('Pipelines table in Commits and Merge requests', function() { ...@@ -83,7 +83,7 @@ describe('Pipelines table in Commits and Merge requests', function() {
}; };
vm.$nextTick(() => { vm.$nextTick(() => {
vm.$el.querySelector('.js-next-button .page-link').click(); vm.$el.querySelector('.next-page-item').click();
expect(vm.updateContent).toHaveBeenCalledWith({ page: '2' }); expect(vm.updateContent).toHaveBeenCalledWith({ page: '2' });
done(); done();
......
...@@ -92,13 +92,13 @@ describe('Environment', () => { ...@@ -92,13 +92,13 @@ describe('Environment', () => {
describe('pagination', () => { describe('pagination', () => {
it('should render pagination', () => { it('should render pagination', () => {
expect(component.$el.querySelectorAll('.gl-pagination li').length).toEqual(5); expect(component.$el.querySelectorAll('.gl-pagination li').length).toEqual(9);
}); });
it('should make an API request when page is clicked', done => { it('should make an API request when page is clicked', done => {
spyOn(component, 'updateContent'); spyOn(component, 'updateContent');
setTimeout(() => { setTimeout(() => {
component.$el.querySelector('.gl-pagination li:nth-child(5) .page-link').click(); component.$el.querySelector('.gl-pagination li:nth-child(3) .page-link').click();
expect(component.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' }); expect(component.updateContent).toHaveBeenCalledWith({ scope: 'available', page: '2' });
done(); done();
......
...@@ -115,7 +115,9 @@ describe('Environments Folder View', () => { ...@@ -115,7 +115,9 @@ describe('Environments Folder View', () => {
it('should make an API request when changing page', done => { it('should make an API request when changing page', done => {
spyOn(component, 'updateContent'); spyOn(component, 'updateContent');
setTimeout(() => { setTimeout(() => {
component.$el.querySelector('.gl-pagination .js-last-button .page-link').click(); component.$el
.querySelector('.gl-pagination .page-item:nth-last-of-type(2) .page-link')
.click();
expect(component.updateContent).toHaveBeenCalledWith({ expect(component.updateContent).toHaveBeenCalledWith({
scope: component.scope, scope: component.scope,
......
...@@ -446,7 +446,7 @@ describe('Pipelines', () => { ...@@ -446,7 +446,7 @@ describe('Pipelines', () => {
}; };
vm.$nextTick(() => { vm.$nextTick(() => {
vm.$el.querySelector('.js-next-button .page-link').click(); vm.$el.querySelector('.next-page-item').click();
expect(vm.updateContent).toHaveBeenCalledWith({ scope: 'all', page: '2' }); expect(vm.updateContent).toHaveBeenCalledWith({ scope: 'all', page: '2' });
......
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