diff --git a/README.md b/README.md index b8ced9aa..f52876d7 100644 --- a/README.md +++ b/README.md @@ -360,11 +360,12 @@ data: [ ... ], pagination: { + total: 20, next: 4, current: 2, - perPage: 3, previous: 1, - total: 3, + perPage: 3, + totalPages: 3, } ``` diff --git a/src/infrastructure/RequestHelper.js b/src/infrastructure/RequestHelper.js index adda7151..768475e8 100644 --- a/src/infrastructure/RequestHelper.js +++ b/src/infrastructure/RequestHelper.js @@ -22,12 +22,16 @@ function defaultRequest( }; if (body) params.body = Humps.decamelizeKeys(body); + if (qs) { if (useXMLHttpRequest) { // The xhr package doesn't have a way of passing in a qs object until v3 params.url = URLJoin(params.url, `?${QS.stringify(Humps.decamelizeKeys(qs))}`); - } else params.qs = Humps.decamelizeKeys(qs); + } else { + params.qs = Humps.decamelizeKeys(qs); + } } + if (formData) params.formData = formData; params.resolveWithFullResponse = resolveWithFullResponse; @@ -61,24 +65,29 @@ async function getPaginated(service, endpoint, options = {}) { const page = response.headers['x-page']; const underMaxPageLimit = maxPages ? page < maxPages : true; let more = []; + let data; // If not looking for a singular page and still under the max pages limit // AND their is a next page, paginate if (!queryOptions.page && underMaxPageLimit && links.next) { more = await getPaginated(service, links.next.url.replace(service.url, ''), options); + data = [...response.body, ...more]; + } else { + data = response.body; } const data = Array.isArray(response.body) ? [...response.body, ...more] : response.body; - if (!queryOptions.page && showPagination) { + if (queryOptions.page && showPagination) { return { data, pagination: { + total: response.headers['x-total'], + next: response.headers['x-next-page'] || null, + current: response.headers['x-page'] || null, + previous: response.headers['x-prev-page'] || null, perPage: response.headers['x-per-page'], - next: response.headers['x-next-page'], - current: response.headers['x-page'], - previous: response.headers['x-prev-page'], - total: response.headers['x-total-pages'], + totalPages: response.headers['x-total-pages'], }, }; } diff --git a/test/tests/infrastructure/RequestHelper.js b/test/tests/infrastructure/RequestHelper.js new file mode 100644 index 00000000..d5618f43 --- /dev/null +++ b/test/tests/infrastructure/RequestHelper.js @@ -0,0 +1,160 @@ +import { RequestHelper } from '../../../src/infrastructure'; + +const mockService = { + url: 'testing', + headers: {}, + requester: { + get: () => ({ + body: { + prop1: 5, + prop2: 'test property', + }, + headers: { + 'X-Page': 1, + 'X-Total-Pages': 1, + }, + }), + }, +}; + +const mockService2 = { + url: 'testing', + headers: {}, + requester: { + get: ({ url, qs }) => { + let u = url; + + if (qs.page) u += `page=${qs.page}`; + + const page1 = { + body: [ + { + prop1: 1, + prop2: 'test property1', + }, + { + prop1: 2, + prop2: 'test property2', + }, + ], + headers: { + link: "<'https://www.test.com/api/v3/projects/8?page=2&per_page=2>; rel='next', <'https://www.test.com/api/v3/projects/8?page=1&per_page=2>; rel='first', <'https://www.test.com/api/v3/projects/8?page=2&per_page=2>; rel='last'", + 'x-next-page': 2, + 'x-page': 1, + 'x-per-page': 2, + 'x-prev-page': '', + 'x-total': 4, + 'x-total-pages': 2, + }, + }; + + const page2 = { + body: [ + { + prop1: 3, + prop2: 'test property3', + }, + { + prop1: 4, + prop2: 'test property4', + }, + ], + headers: { + link: "<'https://www.test.com/api/v3/projects/8?page=1&per_page=2>; rel='prev', <'https://www.test.com/api/v3/projects/8?page=1&per_page=2>; rel='first', <'https://www.test.com/api/v3/projects/8?page=2&per_page=2>; rel='last'", + 'x-next-page': '', + 'x-page': 2, + 'x-per-page': 2, + 'x-prev-page': 1, + 'x-total': 4, + 'x-total-pages': 2, + }, + }; + + if (u.includes('page=2')) return page2; + + return page1; + }, + }, +}; + +describe('RequestHelper.get()', () => { + it('Should respond with an object', async () => { + const response = await RequestHelper.get( + mockService, + 'https://www.test.com', + ); + + expect(response.prop1).toBe(5); + expect(response.prop2).toBe('test property'); + }); + + it('Should be paginated when links are present', async () => { + const response = await RequestHelper.get( + mockService2, + 'https://www.test.com', + ); + + response.forEach((l, index) => { + expect(l.prop1).toBe(1 + index); + expect(l.prop2).toBe(`test property${1 + index}`); + }); + }); + + it('Should be paginated but limited by the maxPages option', async () => { + const response = await RequestHelper.get( + mockService2, + 'https://www.test.com', + { maxPages: 1 }, + ); + + expect(response.length).toBe(2); + expect(response[0].prop1).toBe(1); + + response.forEach((l, index) => { + expect(l.prop1).toBe(1 + index); + expect(l.prop2).toBe(`test property${1 + index}`); + }); + }); + + it('Should be paginated but limited by the page option', async () => { + const response = await RequestHelper.get( + mockService2, + 'https://www.test.com', + { page: 2 }, + ); + + expect(response.length).toBe(2); + expect(response[0].prop1).toBe(3); + + response.forEach((l, index) => { + expect(l.prop1).toBe(3 + index); + expect(l.prop2).toBe(`test property${3 + index}`); + }); + }); + + it('Should show the pagination information when the page option is given', async () => { + const response = await RequestHelper.get( + mockService2, + 'https://www.test.com', + { page: 2, showPagination: true }, + ); + + expect(response.data).toBeDefined(); + expect(response.data.length).toBe(2); + expect(response.data[0].prop1).toBe(3); + + response.data.forEach((l, index) => { + expect(l.prop1).toBe(3 + index); + expect(l.prop2).toBe(`test property${3 + index}`); + }); + + expect(response.pagination).toMatchObject({ + total: 4, + previous: 1, + current: 2, + next: null, + perPage: 2, + totalPages: 2, + }); + }); +});