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