Merge branch '57991-frontend-pagination-needs-to-handle-cases-where-the-x-total-pages-header-isn-t-present' into 'master'
Improve the JS pagination to handle the case when the `X-Total` and `X-Total-Pages` headers aren't present Closes #57991 See merge request gitlab-org/gitlab-ce!25601
This commit is contained in:
commit
8c2a5b75d2
|
@ -27,11 +27,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
shouldRenderPagination() {
|
||||
return (
|
||||
!this.isLoading &&
|
||||
this.state.pipelines.length &&
|
||||
this.state.pageInfo.total > this.state.pageInfo.perPage
|
||||
);
|
||||
return !this.isLoading;
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
|
|
|
@ -54,15 +54,14 @@ export default {
|
|||
return this.pageInfo.nextPage;
|
||||
},
|
||||
getItems() {
|
||||
const total = this.pageInfo.totalPages;
|
||||
const { page } = this.pageInfo;
|
||||
const { totalPages, nextPage, previousPage, page } = this.pageInfo;
|
||||
const items = [];
|
||||
|
||||
if (page > 1) {
|
||||
items.push({ title: FIRST, first: true });
|
||||
}
|
||||
|
||||
if (page > 1) {
|
||||
if (previousPage) {
|
||||
items.push({ title: PREV, prev: true });
|
||||
} else {
|
||||
items.push({ title: PREV, disabled: true, prev: true });
|
||||
|
@ -70,32 +69,34 @@ export default {
|
|||
|
||||
if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true });
|
||||
|
||||
const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1);
|
||||
const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total);
|
||||
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 });
|
||||
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 (total - page > PAGINATION_UI_BUTTON_LIMIT) {
|
||||
items.push({ title: SPREAD, separator: true, page: true });
|
||||
}
|
||||
|
||||
if (page === total) {
|
||||
items.push({ title: NEXT, disabled: true, next: true });
|
||||
} else if (total - page >= 1) {
|
||||
if (nextPage) {
|
||||
items.push({ title: NEXT, next: true });
|
||||
} else {
|
||||
items.push({ title: NEXT, disabled: true, next: true });
|
||||
}
|
||||
|
||||
if (total - page >= 1) {
|
||||
if (totalPages && totalPages - page >= 1) {
|
||||
items.push({ title: LAST, last: true });
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
showPagination() {
|
||||
return this.pageInfo.totalPages > 1;
|
||||
return this.pageInfo.nextPage || this.pageInfo.previousPage;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: "Improve the JS pagination to handle the case when the `X-Total` and `X-Total-Pages` headers aren't present"
|
||||
merge_request: 25601
|
||||
author:
|
||||
type: fixed
|
|
@ -22,10 +22,10 @@ describe('Pagination component', () => {
|
|||
it('should not render anything', () => {
|
||||
component = mountComponent({
|
||||
pageInfo: {
|
||||
nextPage: 1,
|
||||
nextPage: NaN,
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
previousPage: null,
|
||||
previousPage: NaN,
|
||||
total: 15,
|
||||
totalPages: 1,
|
||||
},
|
||||
|
@ -58,6 +58,28 @@ describe('Pagination component', () => {
|
|||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should be disabled and non clickable when total and totalPages are NaN', () => {
|
||||
component = mountComponent({
|
||||
pageInfo: {
|
||||
nextPage: 2,
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
previousPage: NaN,
|
||||
total: NaN,
|
||||
totalPages: NaN,
|
||||
},
|
||||
change: spy,
|
||||
});
|
||||
|
||||
expect(
|
||||
component.$el.querySelector('.js-previous-button').classList.contains('disabled'),
|
||||
).toEqual(true);
|
||||
|
||||
component.$el.querySelector('.js-previous-button .page-link').click();
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should be enabled and clickable', () => {
|
||||
component = mountComponent({
|
||||
pageInfo: {
|
||||
|
@ -75,6 +97,24 @@ describe('Pagination component', () => {
|
|||
|
||||
expect(spy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('should be enabled and clickable when total and totalPages are NaN', () => {
|
||||
component = mountComponent({
|
||||
pageInfo: {
|
||||
nextPage: 3,
|
||||
page: 2,
|
||||
perPage: 20,
|
||||
previousPage: 1,
|
||||
total: NaN,
|
||||
totalPages: NaN,
|
||||
},
|
||||
change: spy,
|
||||
});
|
||||
|
||||
component.$el.querySelector('.js-previous-button .page-link').click();
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('first button', () => {
|
||||
|
@ -99,6 +139,28 @@ describe('Pagination component', () => {
|
|||
|
||||
expect(spy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it('should call the change callback with the first page when total and totalPages are NaN', () => {
|
||||
component = mountComponent({
|
||||
pageInfo: {
|
||||
nextPage: 3,
|
||||
page: 2,
|
||||
perPage: 20,
|
||||
previousPage: 1,
|
||||
total: NaN,
|
||||
totalPages: NaN,
|
||||
},
|
||||
change: spy,
|
||||
});
|
||||
|
||||
const button = component.$el.querySelector('.js-first-button .page-link');
|
||||
|
||||
expect(button.textContent.trim()).toEqual('« First');
|
||||
|
||||
button.click();
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('last button', () => {
|
||||
|
@ -123,16 +185,32 @@ describe('Pagination component', () => {
|
|||
|
||||
expect(spy).toHaveBeenCalledWith(5);
|
||||
});
|
||||
|
||||
it('should not render', () => {
|
||||
component = mountComponent({
|
||||
pageInfo: {
|
||||
nextPage: 3,
|
||||
page: 2,
|
||||
perPage: 20,
|
||||
previousPage: 1,
|
||||
total: NaN,
|
||||
totalPages: NaN,
|
||||
},
|
||||
change: spy,
|
||||
});
|
||||
|
||||
expect(component.$el.querySelector('.js-last-button .page-link')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('next button', () => {
|
||||
it('should be disabled and non clickable', () => {
|
||||
component = mountComponent({
|
||||
pageInfo: {
|
||||
nextPage: 5,
|
||||
nextPage: NaN,
|
||||
page: 5,
|
||||
perPage: 20,
|
||||
previousPage: 1,
|
||||
previousPage: 4,
|
||||
total: 84,
|
||||
totalPages: 5,
|
||||
},
|
||||
|
@ -146,6 +224,26 @@ describe('Pagination component', () => {
|
|||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should be disabled and non clickable when total and totalPages are NaN', () => {
|
||||
component = mountComponent({
|
||||
pageInfo: {
|
||||
nextPage: NaN,
|
||||
page: 5,
|
||||
perPage: 20,
|
||||
previousPage: 4,
|
||||
total: NaN,
|
||||
totalPages: NaN,
|
||||
},
|
||||
change: spy,
|
||||
});
|
||||
|
||||
expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next');
|
||||
|
||||
component.$el.querySelector('.js-next-button .page-link').click();
|
||||
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should be enabled and clickable', () => {
|
||||
component = mountComponent({
|
||||
pageInfo: {
|
||||
|
@ -163,6 +261,24 @@ describe('Pagination component', () => {
|
|||
|
||||
expect(spy).toHaveBeenCalledWith(4);
|
||||
});
|
||||
|
||||
it('should be enabled and clickable when total and totalPages are NaN', () => {
|
||||
component = mountComponent({
|
||||
pageInfo: {
|
||||
nextPage: 4,
|
||||
page: 3,
|
||||
perPage: 20,
|
||||
previousPage: 2,
|
||||
total: NaN,
|
||||
totalPages: NaN,
|
||||
},
|
||||
change: spy,
|
||||
});
|
||||
|
||||
component.$el.querySelector('.js-next-button .page-link').click();
|
||||
|
||||
expect(spy).toHaveBeenCalledWith(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('numbered buttons', () => {
|
||||
|
@ -181,22 +297,56 @@ describe('Pagination component', () => {
|
|||
|
||||
expect(component.$el.querySelectorAll('.page').length).toEqual(5);
|
||||
});
|
||||
|
||||
it('should not render any page', () => {
|
||||
component = mountComponent({
|
||||
pageInfo: {
|
||||
nextPage: 4,
|
||||
page: 3,
|
||||
perPage: 20,
|
||||
previousPage: 2,
|
||||
total: NaN,
|
||||
totalPages: NaN,
|
||||
},
|
||||
change: spy,
|
||||
});
|
||||
|
||||
expect(component.$el.querySelectorAll('.page').length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should render the spread operator', () => {
|
||||
component = mountComponent({
|
||||
pageInfo: {
|
||||
nextPage: 4,
|
||||
page: 3,
|
||||
perPage: 20,
|
||||
previousPage: 2,
|
||||
total: 84,
|
||||
totalPages: 10,
|
||||
},
|
||||
change: spy,
|
||||
describe('spread operator', () => {
|
||||
it('should render', () => {
|
||||
component = mountComponent({
|
||||
pageInfo: {
|
||||
nextPage: 4,
|
||||
page: 3,
|
||||
perPage: 20,
|
||||
previousPage: 2,
|
||||
total: 84,
|
||||
totalPages: 10,
|
||||
},
|
||||
change: spy,
|
||||
});
|
||||
|
||||
expect(component.$el.querySelector('.separator').textContent.trim()).toEqual('...');
|
||||
});
|
||||
|
||||
expect(component.$el.querySelector('.separator').textContent.trim()).toEqual('...');
|
||||
it('should not render', () => {
|
||||
component = mountComponent({
|
||||
pageInfo: {
|
||||
nextPage: 4,
|
||||
page: 3,
|
||||
perPage: 20,
|
||||
previousPage: 2,
|
||||
total: NaN,
|
||||
totalPages: NaN,
|
||||
},
|
||||
change: spy,
|
||||
});
|
||||
|
||||
expect(component.$el.querySelector('.separator')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue