Commit d9dd5209 authored by Rémy Coutable's avatar Rémy Coutable

Make JS pagination handle missing 'X-Total-Pages' header

Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parent 239beb5c
...@@ -27,11 +27,7 @@ export default { ...@@ -27,11 +27,7 @@ export default {
}, },
computed: { computed: {
shouldRenderPagination() { shouldRenderPagination() {
return ( return !this.isLoading;
!this.isLoading &&
this.state.pipelines.length &&
this.state.pageInfo.total > this.state.pageInfo.perPage
);
}, },
}, },
beforeMount() { beforeMount() {
......
...@@ -54,15 +54,14 @@ export default { ...@@ -54,15 +54,14 @@ export default {
return this.pageInfo.nextPage; return this.pageInfo.nextPage;
}, },
getItems() { getItems() {
const total = this.pageInfo.totalPages; const { totalPages, nextPage, previousPage, page } = this.pageInfo;
const { page } = this.pageInfo;
const items = []; const items = [];
if (page > 1) { if (page > 1) {
items.push({ title: FIRST, first: true }); items.push({ title: FIRST, first: true });
} }
if (page > 1) { if (previousPage) {
items.push({ title: PREV, prev: true }); items.push({ title: PREV, prev: true });
} else { } else {
items.push({ title: PREV, disabled: true, prev: true }); items.push({ title: PREV, disabled: true, prev: true });
...@@ -70,32 +69,34 @@ export default { ...@@ -70,32 +69,34 @@ export default {
if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true }); if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true });
const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1); if (totalPages) {
const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total); 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) { for (let i = start; i <= end; i += 1) {
const isActive = i === page; const isActive = i === page;
items.push({ title: i, active: isActive, page: true }); items.push({ title: i, active: isActive, page: true });
} }
if (total - page > PAGINATION_UI_BUTTON_LIMIT) { if (totalPages - page > PAGINATION_UI_BUTTON_LIMIT) {
items.push({ title: SPREAD, separator: true, page: true }); items.push({ title: SPREAD, separator: true, page: true });
}
} }
if (page === total) { if (nextPage) {
items.push({ title: NEXT, disabled: true, next: true });
} else if (total - page >= 1) {
items.push({ title: NEXT, next: true }); 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 }); items.push({ title: LAST, last: true });
} }
return items; return items;
}, },
showPagination() { showPagination() {
return this.pageInfo.totalPages > 1; return this.pageInfo.nextPage || this.pageInfo.previousPage;
}, },
}, },
methods: { methods: {
......
---
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', () => { ...@@ -22,10 +22,10 @@ describe('Pagination component', () => {
it('should not render anything', () => { it('should not render anything', () => {
component = mountComponent({ component = mountComponent({
pageInfo: { pageInfo: {
nextPage: 1, nextPage: NaN,
page: 1, page: 1,
perPage: 20, perPage: 20,
previousPage: null, previousPage: NaN,
total: 15, total: 15,
totalPages: 1, totalPages: 1,
}, },
...@@ -58,6 +58,28 @@ describe('Pagination component', () => { ...@@ -58,6 +58,28 @@ describe('Pagination component', () => {
expect(spy).not.toHaveBeenCalled(); 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', () => { it('should be enabled and clickable', () => {
component = mountComponent({ component = mountComponent({
pageInfo: { pageInfo: {
...@@ -75,6 +97,24 @@ describe('Pagination component', () => { ...@@ -75,6 +97,24 @@ describe('Pagination component', () => {
expect(spy).toHaveBeenCalledWith(1); 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', () => { describe('first button', () => {
...@@ -99,6 +139,28 @@ describe('Pagination component', () => { ...@@ -99,6 +139,28 @@ describe('Pagination component', () => {
expect(spy).toHaveBeenCalledWith(1); 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', () => { describe('last button', () => {
...@@ -123,16 +185,32 @@ describe('Pagination component', () => { ...@@ -123,16 +185,32 @@ describe('Pagination component', () => {
expect(spy).toHaveBeenCalledWith(5); 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', () => { describe('next button', () => {
it('should be disabled and non clickable', () => { it('should be disabled and non clickable', () => {
component = mountComponent({ component = mountComponent({
pageInfo: { pageInfo: {
nextPage: 5, nextPage: NaN,
page: 5, page: 5,
perPage: 20, perPage: 20,
previousPage: 1, previousPage: 4,
total: 84, total: 84,
totalPages: 5, totalPages: 5,
}, },
...@@ -146,6 +224,26 @@ describe('Pagination component', () => { ...@@ -146,6 +224,26 @@ describe('Pagination component', () => {
expect(spy).not.toHaveBeenCalled(); 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', () => { it('should be enabled and clickable', () => {
component = mountComponent({ component = mountComponent({
pageInfo: { pageInfo: {
...@@ -163,6 +261,24 @@ describe('Pagination component', () => { ...@@ -163,6 +261,24 @@ describe('Pagination component', () => {
expect(spy).toHaveBeenCalledWith(4); 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', () => { describe('numbered buttons', () => {
...@@ -181,22 +297,56 @@ describe('Pagination component', () => { ...@@ -181,22 +297,56 @@ describe('Pagination component', () => {
expect(component.$el.querySelectorAll('.page').length).toEqual(5); 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', () => { describe('spread operator', () => {
component = mountComponent({ it('should render', () => {
pageInfo: { component = mountComponent({
nextPage: 4, pageInfo: {
page: 3, nextPage: 4,
perPage: 20, page: 3,
previousPage: 2, perPage: 20,
total: 84, previousPage: 2,
totalPages: 10, total: 84,
}, totalPages: 10,
change: spy, },
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();
});
}); });
}); });
}); });
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