Skip to content
Merged
12 changes: 12 additions & 0 deletions .changeset/clear-pigs-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@youversion/platform-core': minor
'@youversion/platform-react-ui': minor
'@youversion/platform-react-hooks': minor
---

feat(core): add pagination to the getVersions endpoint

- update tests to ensure that proper amount of responses are returned
based on the page_size query param.
- add the ability to specify a page_size to fetch a specific number of
items at a time.
4 changes: 2 additions & 2 deletions packages/core/src/__tests__/MockBibles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ const baseBibleBooks = [
{
id: 'GEN',
title: 'Genesis',
full_title: 'The First Book of Moses, Called Genesis',
abbreviation: 'Gen',
full_title: 'Genesis',
abbreviation: 'Gen.',
canon: 'old_testament',
chapters: mockGenesisChapters,
},
Expand Down
31 changes: 26 additions & 5 deletions packages/core/src/__tests__/bible.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,27 @@ describe('BibleClient', () => {
});

describe('getVersions', () => {
it('should fetch number of Bible versions specified by page_size param', async () => {
const versions = await bibleClient.getVersions('en*', undefined, { page_size: 11 });

expect(versions.data.length).toEqual(11);
});

it('should fetch next page using page_token from first response', async () => {
const firstPage = await bibleClient.getVersions('en*', undefined, { page_size: 5 });

expect(firstPage.data.length).toEqual(5);
expect(firstPage.next_page_token).toBeDefined();

const secondPage = await bibleClient.getVersions('en*', undefined, {
page_size: 5,
page_token: firstPage.next_page_token!,
});

expect(secondPage.data.length).toEqual(5);
expect(secondPage.data[0]?.id).not.toEqual(firstPage.data[0]?.id);
});

it('should fetch Bible versions with language ranges', async () => {
const versions = await bibleClient.getVersions('en*');

Expand Down Expand Up @@ -117,16 +138,16 @@ describe('BibleClient', () => {

describe('getBooks', () => {
it('should fetch all books for a version', async () => {
const books = await bibleClient.getBooks(1);
const books = await bibleClient.getBooks(111);

const { success } = BibleBookSchema.safeParse(books.data[0]);
expect(success).toBe(true);

expect(books.data).toHaveLength(66);
expect(books.data[0]).toHaveProperty('id', 'GEN');
expect(books.data[0]).toHaveProperty('title', 'Genesis');
expect(books.data[0]).toHaveProperty('full_title', 'The First Book of Moses, Called Genesis');
expect(books.data[0]).toHaveProperty('abbreviation', 'Gen');
expect(books.data[0]).toHaveProperty('full_title', 'Genesis');
expect(books.data[0]).toHaveProperty('abbreviation', 'Gen.');
expect(books.data[0]?.intro).toEqual({
id: 'INTRO',
passage_id: 'GEN.INTRO',
Expand All @@ -139,15 +160,15 @@ describe('BibleClient', () => {

describe('getBook', () => {
it('should fetch a specific book', async () => {
const book = await bibleClient.getBook(1, 'GEN');
const book = await bibleClient.getBook(111, 'GEN');

const { success } = BibleBookSchema.safeParse(book);
expect(success).toBe(true);

expect(book.chapters).toHaveLength(50);
expect(book).toHaveProperty('id', 'GEN');
expect(book).toHaveProperty('title', 'Genesis');
expect(book).toHaveProperty('abbreviation', 'Gen');
expect(book).toHaveProperty('abbreviation', 'Gen.');
expect(book).toHaveProperty('intro');
expect(book.intro).toEqual({
id: 'INTRO',
Expand Down
31 changes: 26 additions & 5 deletions packages/core/src/__tests__/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,32 @@ export const handlers = [
}),

// Versions endpoints
http.get(`https://${apiHost}/v1/bibles`, () => {
http.get(`https://${apiHost}/v1/bibles`, ({ request }) => {
const url = new URL(request.url);
const pageSize = url.searchParams.get('page_size');
const pageToken = url.searchParams.get('page_token');

const defaultPageSize = 25;
const size = pageSize ? parseInt(pageSize, 10) : defaultPageSize;
let start = 0;

if (pageToken) {
try {
const decoded = JSON.parse(atob(pageToken)) as { start?: number };
start = decoded.start || 0;
} catch {
start = 0;
}
}

const end = start + size;
const paginatedVersions = mockVersions.slice(start, end);
const hasMore = end < mockVersions.length;

return HttpResponse.json({
data: mockVersions,
next_page_token: null,
total_size: 14,
data: paginatedVersions,
next_page_token: hasMore ? btoa(JSON.stringify({ start: end })) : null,
total_size: mockVersions.length,
});
}),

Expand All @@ -127,7 +148,7 @@ export const handlers = [
return HttpResponse.json(mockBibleGenesis);
}),

// Chaper endpoints
// Chapter endpoints
http.get(`https://${apiHost}/v1/bibles/:bible_id/books/:book_id/chapters`, () => {
return HttpResponse.json({ data: mockGenesisChapters });
}),
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/__tests__/languages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ describe('LanguagesClient', () => {
const { success } = LanguageSchema.safeParse(languages.data[0]);
expect(success).toBe(true);
expect(languages.data).toHaveLength(20);
expect(languages.data.every((language) => language.countries?.includes('US'))).toBe(true);
/** This is not true, it will always return the number provided in page_size
* and it will put the ones that have the country in their countries array
* at the top of the list.
*/
// expect(languages.data.every((language) => language.countries?.includes('US'))).toBe(true);
expect(languages.next_page_token).not.toBeNull();
});

Expand Down
12 changes: 12 additions & 0 deletions packages/core/src/bible.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export class BibleClient {
async getVersions(
language_ranges: string | string[],
license_id?: string | number,
options?: { page_size?: number; page_token?: string },
): Promise<Collection<BibleVersion>> {
const languageRangeArray = Array.isArray(language_ranges) ? language_ranges : [language_ranges];

Expand All @@ -68,9 +69,20 @@ export class BibleClient {
const params: Record<string, string | number | string[]> = {
'language_ranges[]': parsedLanguageRanges,
};

if (license_id !== undefined) {
params.license_id = license_id;
}

if (options?.page_size !== undefined) {
const pageSizeSchema = z.number().int().positive();
pageSizeSchema.parse(options.page_size);
params.page_size = options.page_size;
}

if (options?.page_token !== undefined) {
params.page_token = options.page_token;
}
return this.client.get<Collection<BibleVersion>>(`/v1/bibles`, params);
}

Expand Down