@youversion/platform-react-hooks 1.11.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +11 -0
- package/dist/__tests__/mocks/bibles.d.ts +22 -0
- package/dist/__tests__/mocks/bibles.d.ts.map +1 -0
- package/dist/__tests__/mocks/bibles.js +52 -0
- package/dist/__tests__/mocks/bibles.js.map +1 -0
- package/dist/useVersions.d.ts +11 -1
- package/dist/useVersions.d.ts.map +1 -1
- package/dist/useVersions.js +25 -1
- package/dist/useVersions.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/mocks/bibles.ts +56 -0
- package/src/context/ReaderProvider.test.tsx +264 -0
- package/src/context/VerseSelectionProvider.test.tsx +362 -0
- package/src/useVersion.test.tsx +242 -0
- package/src/useVersions.test.tsx +641 -0
- package/src/useVersions.ts +42 -4
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
import { renderHook, waitFor, act } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
import { useVersions } from './useVersions';
|
|
5
|
+
import { YouVersionContext } from './context';
|
|
6
|
+
import { type BibleClient, type Collection, type BibleVersion } from '@youversion/platform-core';
|
|
7
|
+
import { useBibleClient } from './useBibleClient';
|
|
8
|
+
|
|
9
|
+
vi.mock('./useBibleClient');
|
|
10
|
+
|
|
11
|
+
describe('useVersions', () => {
|
|
12
|
+
const mockAppKey = 'test-app-key';
|
|
13
|
+
const mockGetVersions = vi.fn();
|
|
14
|
+
|
|
15
|
+
const mockVersions: Collection<BibleVersion> = {
|
|
16
|
+
data: [
|
|
17
|
+
{
|
|
18
|
+
id: 111,
|
|
19
|
+
title: 'New International Version',
|
|
20
|
+
abbreviation: 'NIV',
|
|
21
|
+
localized_title: 'New International Version',
|
|
22
|
+
localized_abbreviation: 'NIV',
|
|
23
|
+
language_tag: 'en',
|
|
24
|
+
books: ['GEN', 'EXO', 'LEV'],
|
|
25
|
+
youversion_deep_link: 'https://bible.com/versions/111',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 1,
|
|
29
|
+
title: 'King James Version',
|
|
30
|
+
abbreviation: 'KJV',
|
|
31
|
+
localized_title: 'King James Version',
|
|
32
|
+
localized_abbreviation: 'KJV',
|
|
33
|
+
language_tag: 'en',
|
|
34
|
+
books: ['GEN', 'EXO', 'LEV'],
|
|
35
|
+
youversion_deep_link: 'https://bible.com/versions/1',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
next_page_token: null,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const createWrapper = (contextValue: { appKey: string }) => {
|
|
42
|
+
return ({ children }: { children: ReactNode }) => (
|
|
43
|
+
<YouVersionContext.Provider value={contextValue}>{children}</YouVersionContext.Provider>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
vi.resetAllMocks();
|
|
49
|
+
|
|
50
|
+
mockGetVersions.mockResolvedValue(mockVersions);
|
|
51
|
+
|
|
52
|
+
const mockClient: Partial<BibleClient> = { getVersions: mockGetVersions };
|
|
53
|
+
vi.mocked(useBibleClient).mockReturnValue(mockClient as BibleClient);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('fetching versions', () => {
|
|
57
|
+
it('should fetch versions with default language range', async () => {
|
|
58
|
+
const wrapper = createWrapper({
|
|
59
|
+
appKey: mockAppKey,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const { result } = renderHook(() => useVersions(), { wrapper });
|
|
63
|
+
|
|
64
|
+
expect(result.current.loading).toBe(true);
|
|
65
|
+
expect(result.current.versions).toBe(null);
|
|
66
|
+
|
|
67
|
+
await waitFor(() => {
|
|
68
|
+
expect(result.current.loading).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(mockGetVersions).toHaveBeenCalledWith('en', undefined, undefined);
|
|
72
|
+
expect(result.current.versions).toEqual(mockVersions);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should fetch versions with custom language range string', async () => {
|
|
76
|
+
const wrapper = createWrapper({
|
|
77
|
+
appKey: mockAppKey,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const { result } = renderHook(() => useVersions('es'), { wrapper });
|
|
81
|
+
|
|
82
|
+
await waitFor(() => {
|
|
83
|
+
expect(result.current.loading).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(mockGetVersions).toHaveBeenCalledWith('es', undefined, undefined);
|
|
87
|
+
expect(result.current.versions).toEqual(mockVersions);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should fetch versions with language range array', async () => {
|
|
91
|
+
const wrapper = createWrapper({
|
|
92
|
+
appKey: mockAppKey,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const { result } = renderHook(() => useVersions(['en', 'es', 'fr']), { wrapper });
|
|
96
|
+
|
|
97
|
+
await waitFor(() => {
|
|
98
|
+
expect(result.current.loading).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(mockGetVersions).toHaveBeenCalledWith(['en', 'es', 'fr'], undefined, undefined);
|
|
102
|
+
expect(result.current.versions).toEqual(mockVersions);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should fetch versions with license ID', async () => {
|
|
106
|
+
const wrapper = createWrapper({
|
|
107
|
+
appKey: mockAppKey,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const { result } = renderHook(() => useVersions('en', 123), { wrapper });
|
|
111
|
+
|
|
112
|
+
await waitFor(() => {
|
|
113
|
+
expect(result.current.loading).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
expect(mockGetVersions).toHaveBeenCalledWith('en', 123, undefined);
|
|
117
|
+
expect(result.current.versions).toEqual(mockVersions);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should fetch versions with string license ID', async () => {
|
|
121
|
+
const wrapper = createWrapper({
|
|
122
|
+
appKey: mockAppKey,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const { result } = renderHook(() => useVersions('en', 'license-abc'), { wrapper });
|
|
126
|
+
|
|
127
|
+
await waitFor(() => {
|
|
128
|
+
expect(result.current.loading).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(mockGetVersions).toHaveBeenCalledWith('en', 'license-abc', undefined);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('fetching with options', () => {
|
|
136
|
+
it('should fetch versions with page_size option', async () => {
|
|
137
|
+
const wrapper = createWrapper({
|
|
138
|
+
appKey: mockAppKey,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const { result } = renderHook(() => useVersions('en', undefined, { page_size: 10 }), {
|
|
142
|
+
wrapper,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
await waitFor(() => {
|
|
146
|
+
expect(result.current.loading).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(mockGetVersions).toHaveBeenCalledWith('en', undefined, {
|
|
150
|
+
page_size: 10,
|
|
151
|
+
page_token: undefined,
|
|
152
|
+
fields: undefined,
|
|
153
|
+
all_available: undefined,
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should fetch versions with page_size "*" and fields', async () => {
|
|
158
|
+
const wrapper = createWrapper({
|
|
159
|
+
appKey: mockAppKey,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const { result } = renderHook(
|
|
163
|
+
() => useVersions('en', undefined, { page_size: '*', fields: ['id', 'abbreviation'] }),
|
|
164
|
+
{ wrapper },
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
await waitFor(() => {
|
|
168
|
+
expect(result.current.loading).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
expect(mockGetVersions).toHaveBeenCalledWith('en', undefined, {
|
|
172
|
+
page_size: '*',
|
|
173
|
+
page_token: undefined,
|
|
174
|
+
fields: ['id', 'abbreviation'],
|
|
175
|
+
all_available: undefined,
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should fetch versions with page_token option', async () => {
|
|
180
|
+
const wrapper = createWrapper({
|
|
181
|
+
appKey: mockAppKey,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const { result } = renderHook(
|
|
185
|
+
() => useVersions('en', undefined, { page_token: 'next-page-token' }),
|
|
186
|
+
{ wrapper },
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
await waitFor(() => {
|
|
190
|
+
expect(result.current.loading).toBe(false);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(mockGetVersions).toHaveBeenCalledWith('en', undefined, {
|
|
194
|
+
page_size: undefined,
|
|
195
|
+
page_token: 'next-page-token',
|
|
196
|
+
fields: undefined,
|
|
197
|
+
all_available: undefined,
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should fetch versions with fields option', async () => {
|
|
202
|
+
const wrapper = createWrapper({
|
|
203
|
+
appKey: mockAppKey,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const { result } = renderHook(
|
|
207
|
+
() => useVersions('en', undefined, { fields: ['id', 'title', 'abbreviation'] }),
|
|
208
|
+
{ wrapper },
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
await waitFor(() => {
|
|
212
|
+
expect(result.current.loading).toBe(false);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(mockGetVersions).toHaveBeenCalledWith('en', undefined, {
|
|
216
|
+
page_size: undefined,
|
|
217
|
+
page_token: undefined,
|
|
218
|
+
fields: ['id', 'title', 'abbreviation'],
|
|
219
|
+
all_available: undefined,
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should fetch versions with all_available option', async () => {
|
|
224
|
+
const wrapper = createWrapper({
|
|
225
|
+
appKey: mockAppKey,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const { result } = renderHook(() => useVersions('en', undefined, { all_available: true }), {
|
|
229
|
+
wrapper,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
await waitFor(() => {
|
|
233
|
+
expect(result.current.loading).toBe(false);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
expect(mockGetVersions).toHaveBeenCalledWith('en', undefined, {
|
|
237
|
+
page_size: undefined,
|
|
238
|
+
page_token: undefined,
|
|
239
|
+
fields: undefined,
|
|
240
|
+
all_available: true,
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should fetch versions with all options combined', async () => {
|
|
245
|
+
const wrapper = createWrapper({
|
|
246
|
+
appKey: mockAppKey,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const { result } = renderHook(
|
|
250
|
+
() =>
|
|
251
|
+
useVersions('en', 456, {
|
|
252
|
+
page_size: 50,
|
|
253
|
+
page_token: 'token-123',
|
|
254
|
+
fields: ['id', 'title'],
|
|
255
|
+
all_available: true,
|
|
256
|
+
}),
|
|
257
|
+
{ wrapper },
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
await waitFor(() => {
|
|
261
|
+
expect(result.current.loading).toBe(false);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
expect(mockGetVersions).toHaveBeenCalledWith('en', 456, {
|
|
265
|
+
page_size: 50,
|
|
266
|
+
page_token: 'token-123',
|
|
267
|
+
fields: ['id', 'title'],
|
|
268
|
+
all_available: true,
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe('refetch behavior', () => {
|
|
274
|
+
it('should refetch when languageRanges changes', async () => {
|
|
275
|
+
const wrapper = createWrapper({
|
|
276
|
+
appKey: mockAppKey,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
const { result, rerender } = renderHook(({ lang }) => useVersions(lang), {
|
|
280
|
+
wrapper,
|
|
281
|
+
initialProps: { lang: 'en' as string | string[] },
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
await waitFor(() => {
|
|
285
|
+
expect(result.current.loading).toBe(false);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
expect(mockGetVersions).toHaveBeenCalledTimes(1);
|
|
289
|
+
expect(mockGetVersions).toHaveBeenNthCalledWith(1, 'en', undefined, undefined);
|
|
290
|
+
|
|
291
|
+
rerender({ lang: 'es' });
|
|
292
|
+
|
|
293
|
+
await waitFor(() => {
|
|
294
|
+
expect(result.current.loading).toBe(false);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
expect(mockGetVersions).toHaveBeenCalledTimes(2);
|
|
298
|
+
expect(mockGetVersions).toHaveBeenNthCalledWith(2, 'es', undefined, undefined);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it('should refetch when languageRanges array changes', async () => {
|
|
302
|
+
const wrapper = createWrapper({
|
|
303
|
+
appKey: mockAppKey,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const { result, rerender } = renderHook(({ lang }) => useVersions(lang), {
|
|
307
|
+
wrapper,
|
|
308
|
+
initialProps: { lang: ['en', 'es'] as string | string[] },
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
await waitFor(() => {
|
|
312
|
+
expect(result.current.loading).toBe(false);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
expect(mockGetVersions).toHaveBeenCalledTimes(1);
|
|
316
|
+
expect(mockGetVersions).toHaveBeenNthCalledWith(1, ['en', 'es'], undefined, undefined);
|
|
317
|
+
|
|
318
|
+
rerender({ lang: ['en', 'fr'] });
|
|
319
|
+
|
|
320
|
+
await waitFor(() => {
|
|
321
|
+
expect(result.current.loading).toBe(false);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
expect(mockGetVersions).toHaveBeenCalledTimes(2);
|
|
325
|
+
expect(mockGetVersions).toHaveBeenNthCalledWith(2, ['en', 'fr'], undefined, undefined);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('should refetch when licenseId changes', async () => {
|
|
329
|
+
const wrapper = createWrapper({
|
|
330
|
+
appKey: mockAppKey,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const { result, rerender } = renderHook(({ license }) => useVersions('en', license), {
|
|
334
|
+
wrapper,
|
|
335
|
+
initialProps: { license: 123 as string | number | undefined },
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
await waitFor(() => {
|
|
339
|
+
expect(result.current.loading).toBe(false);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
expect(mockGetVersions).toHaveBeenCalledTimes(1);
|
|
343
|
+
expect(mockGetVersions).toHaveBeenNthCalledWith(1, 'en', 123, undefined);
|
|
344
|
+
|
|
345
|
+
rerender({ license: 456 });
|
|
346
|
+
|
|
347
|
+
await waitFor(() => {
|
|
348
|
+
expect(result.current.loading).toBe(false);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
expect(mockGetVersions).toHaveBeenCalledTimes(2);
|
|
352
|
+
expect(mockGetVersions).toHaveBeenNthCalledWith(2, 'en', 456, undefined);
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('should refetch when page_size changes', async () => {
|
|
356
|
+
const wrapper = createWrapper({
|
|
357
|
+
appKey: mockAppKey,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const { result, rerender } = renderHook(
|
|
361
|
+
({ options }) => useVersions('en', undefined, options),
|
|
362
|
+
{
|
|
363
|
+
wrapper,
|
|
364
|
+
initialProps: { options: { page_size: 10 } },
|
|
365
|
+
},
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
await waitFor(() => {
|
|
369
|
+
expect(result.current.loading).toBe(false);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
expect(mockGetVersions).toHaveBeenCalledTimes(1);
|
|
373
|
+
expect(mockGetVersions).toHaveBeenNthCalledWith(1, 'en', undefined, {
|
|
374
|
+
page_size: 10,
|
|
375
|
+
page_token: undefined,
|
|
376
|
+
fields: undefined,
|
|
377
|
+
all_available: undefined,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
rerender({ options: { page_size: 20 } });
|
|
381
|
+
|
|
382
|
+
await waitFor(() => {
|
|
383
|
+
expect(result.current.loading).toBe(false);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
expect(mockGetVersions).toHaveBeenCalledTimes(2);
|
|
387
|
+
expect(mockGetVersions).toHaveBeenNthCalledWith(2, 'en', undefined, {
|
|
388
|
+
page_size: 20,
|
|
389
|
+
page_token: undefined,
|
|
390
|
+
fields: undefined,
|
|
391
|
+
all_available: undefined,
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('should refetch when page_token changes', async () => {
|
|
396
|
+
const wrapper = createWrapper({
|
|
397
|
+
appKey: mockAppKey,
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
const { result, rerender } = renderHook(
|
|
401
|
+
({ options }) => useVersions('en', undefined, options),
|
|
402
|
+
{
|
|
403
|
+
wrapper,
|
|
404
|
+
initialProps: { options: { page_token: 'token-1' } },
|
|
405
|
+
},
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
await waitFor(() => {
|
|
409
|
+
expect(result.current.loading).toBe(false);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
expect(mockGetVersions).toHaveBeenCalledTimes(1);
|
|
413
|
+
expect(mockGetVersions).toHaveBeenNthCalledWith(1, 'en', undefined, {
|
|
414
|
+
page_size: undefined,
|
|
415
|
+
page_token: 'token-1',
|
|
416
|
+
fields: undefined,
|
|
417
|
+
all_available: undefined,
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
rerender({ options: { page_token: 'token-2' } });
|
|
421
|
+
|
|
422
|
+
await waitFor(() => {
|
|
423
|
+
expect(result.current.loading).toBe(false);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
expect(mockGetVersions).toHaveBeenCalledTimes(2);
|
|
427
|
+
expect(mockGetVersions).toHaveBeenNthCalledWith(2, 'en', undefined, {
|
|
428
|
+
page_size: undefined,
|
|
429
|
+
page_token: 'token-2',
|
|
430
|
+
fields: undefined,
|
|
431
|
+
all_available: undefined,
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('should refetch when fields changes', async () => {
|
|
436
|
+
const wrapper = createWrapper({
|
|
437
|
+
appKey: mockAppKey,
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const { result, rerender } = renderHook(
|
|
441
|
+
({ options }) => useVersions('en', undefined, options),
|
|
442
|
+
{
|
|
443
|
+
wrapper,
|
|
444
|
+
initialProps: { options: { fields: ['id'] as (keyof BibleVersion)[] } },
|
|
445
|
+
},
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
await waitFor(() => {
|
|
449
|
+
expect(result.current.loading).toBe(false);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
expect(mockGetVersions).toHaveBeenCalledTimes(1);
|
|
453
|
+
expect(mockGetVersions).toHaveBeenNthCalledWith(1, 'en', undefined, {
|
|
454
|
+
page_size: undefined,
|
|
455
|
+
page_token: undefined,
|
|
456
|
+
fields: ['id'],
|
|
457
|
+
all_available: undefined,
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
rerender({ options: { fields: ['id', 'title'] as (keyof BibleVersion)[] } });
|
|
461
|
+
|
|
462
|
+
await waitFor(() => {
|
|
463
|
+
expect(result.current.loading).toBe(false);
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
expect(mockGetVersions).toHaveBeenCalledTimes(2);
|
|
467
|
+
expect(mockGetVersions).toHaveBeenNthCalledWith(2, 'en', undefined, {
|
|
468
|
+
page_size: undefined,
|
|
469
|
+
page_token: undefined,
|
|
470
|
+
fields: ['id', 'title'],
|
|
471
|
+
all_available: undefined,
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('should refetch when all_available changes', async () => {
|
|
476
|
+
const wrapper = createWrapper({
|
|
477
|
+
appKey: mockAppKey,
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
const { result, rerender } = renderHook(
|
|
481
|
+
({ options }) => useVersions('en', undefined, options),
|
|
482
|
+
{
|
|
483
|
+
wrapper,
|
|
484
|
+
initialProps: { options: { all_available: false } },
|
|
485
|
+
},
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
await waitFor(() => {
|
|
489
|
+
expect(result.current.loading).toBe(false);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
expect(mockGetVersions).toHaveBeenCalledTimes(1);
|
|
493
|
+
expect(mockGetVersions).toHaveBeenNthCalledWith(1, 'en', undefined, {
|
|
494
|
+
page_size: undefined,
|
|
495
|
+
page_token: undefined,
|
|
496
|
+
fields: undefined,
|
|
497
|
+
all_available: false,
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
rerender({ options: { all_available: true } });
|
|
501
|
+
|
|
502
|
+
await waitFor(() => {
|
|
503
|
+
expect(result.current.loading).toBe(false);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
expect(mockGetVersions).toHaveBeenCalledTimes(2);
|
|
507
|
+
expect(mockGetVersions).toHaveBeenNthCalledWith(2, 'en', undefined, {
|
|
508
|
+
page_size: undefined,
|
|
509
|
+
page_token: undefined,
|
|
510
|
+
fields: undefined,
|
|
511
|
+
all_available: true,
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
describe('enabled option', () => {
|
|
517
|
+
it('should not fetch when enabled is false', async () => {
|
|
518
|
+
const wrapper = createWrapper({
|
|
519
|
+
appKey: mockAppKey,
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
const { result } = renderHook(() => useVersions('en', undefined, { enabled: false }), {
|
|
523
|
+
wrapper,
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
await waitFor(() => {
|
|
527
|
+
expect(result.current.loading).toBe(false);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
expect(mockGetVersions).not.toHaveBeenCalled();
|
|
531
|
+
expect(result.current.versions).toBe(null);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it('should fetch when enabled is true', async () => {
|
|
535
|
+
const wrapper = createWrapper({
|
|
536
|
+
appKey: mockAppKey,
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
const { result } = renderHook(() => useVersions('en', undefined, { enabled: true }), {
|
|
540
|
+
wrapper,
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
await waitFor(() => {
|
|
544
|
+
expect(result.current.loading).toBe(false);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
expect(mockGetVersions).toHaveBeenCalled();
|
|
548
|
+
expect(result.current.versions).toEqual(mockVersions);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it('should fetch when enabled is not specified', async () => {
|
|
552
|
+
const wrapper = createWrapper({
|
|
553
|
+
appKey: mockAppKey,
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
const { result } = renderHook(() => useVersions('en', undefined, { page_size: 10 }), {
|
|
557
|
+
wrapper,
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
await waitFor(() => {
|
|
561
|
+
expect(result.current.loading).toBe(false);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
expect(mockGetVersions).toHaveBeenCalled();
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
describe('error handling', () => {
|
|
569
|
+
it('should handle fetch errors', async () => {
|
|
570
|
+
const error = new Error('Failed to fetch versions');
|
|
571
|
+
mockGetVersions.mockRejectedValueOnce(error);
|
|
572
|
+
|
|
573
|
+
const wrapper = createWrapper({
|
|
574
|
+
appKey: mockAppKey,
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
const { result } = renderHook(() => useVersions('en'), { wrapper });
|
|
578
|
+
|
|
579
|
+
await waitFor(() => {
|
|
580
|
+
expect(result.current.loading).toBe(false);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
expect(result.current.error).toEqual(error);
|
|
584
|
+
expect(result.current.versions).toBe(null);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it('should clear error on successful refetch', async () => {
|
|
588
|
+
const error = new Error('Failed to fetch versions');
|
|
589
|
+
mockGetVersions.mockRejectedValueOnce(error).mockResolvedValueOnce(mockVersions);
|
|
590
|
+
|
|
591
|
+
const wrapper = createWrapper({
|
|
592
|
+
appKey: mockAppKey,
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
const { result } = renderHook(() => useVersions('en'), { wrapper });
|
|
596
|
+
|
|
597
|
+
await waitFor(() => {
|
|
598
|
+
expect(result.current.loading).toBe(false);
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
expect(result.current.error).toEqual(error);
|
|
602
|
+
|
|
603
|
+
act(() => {
|
|
604
|
+
result.current.refetch();
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
await waitFor(() => {
|
|
608
|
+
expect(result.current.error).toBe(null);
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
expect(result.current.versions).toEqual(mockVersions);
|
|
612
|
+
});
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
describe('manual refetch', () => {
|
|
616
|
+
it('should support manual refetch', async () => {
|
|
617
|
+
const wrapper = createWrapper({
|
|
618
|
+
appKey: mockAppKey,
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
const { result } = renderHook(() => useVersions('en'), { wrapper });
|
|
622
|
+
|
|
623
|
+
await waitFor(() => {
|
|
624
|
+
expect(result.current.loading).toBe(false);
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
expect(mockGetVersions).toHaveBeenCalledTimes(1);
|
|
628
|
+
expect(mockGetVersions).toHaveBeenNthCalledWith(1, 'en', undefined, undefined);
|
|
629
|
+
|
|
630
|
+
act(() => {
|
|
631
|
+
result.current.refetch();
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
await waitFor(() => {
|
|
635
|
+
expect(mockGetVersions).toHaveBeenCalledTimes(2);
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
expect(mockGetVersions).toHaveBeenNthCalledWith(2, 'en', undefined, undefined);
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
});
|
package/src/useVersions.ts
CHANGED
|
@@ -4,10 +4,21 @@ import { useBibleClient } from './useBibleClient';
|
|
|
4
4
|
import { useApiData, type UseApiDataOptions } from './useApiData';
|
|
5
5
|
import type { Collection, BibleVersion } from '@youversion/platform-core';
|
|
6
6
|
|
|
7
|
+
export interface UseVersionsOptions extends UseApiDataOptions {
|
|
8
|
+
/** Maximum number of results per page, or '*' for all (requires 1-3 fields) */
|
|
9
|
+
page_size?: number | '*';
|
|
10
|
+
/** Token for pagination */
|
|
11
|
+
page_token?: string;
|
|
12
|
+
/** Specific fields to return (required when page_size is '*', must be 1-3 fields) */
|
|
13
|
+
fields?: (keyof BibleVersion)[];
|
|
14
|
+
/** Include all available versions regardless of license */
|
|
15
|
+
all_available?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
export function useVersions(
|
|
8
|
-
languageRanges: string = 'en',
|
|
19
|
+
languageRanges: string | string[] = 'en',
|
|
9
20
|
licenseId?: string | number,
|
|
10
|
-
options?:
|
|
21
|
+
options?: UseVersionsOptions,
|
|
11
22
|
): {
|
|
12
23
|
versions: Collection<BibleVersion> | null;
|
|
13
24
|
loading: boolean;
|
|
@@ -16,14 +27,41 @@ export function useVersions(
|
|
|
16
27
|
} {
|
|
17
28
|
const bibleClient = useBibleClient();
|
|
18
29
|
|
|
30
|
+
const getVersionsOptions =
|
|
31
|
+
options?.page_size !== undefined ||
|
|
32
|
+
options?.page_token !== undefined ||
|
|
33
|
+
options?.fields !== undefined ||
|
|
34
|
+
options?.all_available !== undefined
|
|
35
|
+
? {
|
|
36
|
+
page_size: options?.page_size,
|
|
37
|
+
page_token: options?.page_token,
|
|
38
|
+
fields: options?.fields,
|
|
39
|
+
all_available: options?.all_available,
|
|
40
|
+
}
|
|
41
|
+
: undefined;
|
|
42
|
+
|
|
43
|
+
// Create stable keys for arrays to avoid unnecessary refetches
|
|
44
|
+
const languageRangesKey = Array.isArray(languageRanges)
|
|
45
|
+
? languageRanges.join(',')
|
|
46
|
+
: languageRanges;
|
|
47
|
+
const fieldsKey = options?.fields?.join(',');
|
|
48
|
+
|
|
19
49
|
const {
|
|
20
50
|
data: versions,
|
|
21
51
|
loading,
|
|
22
52
|
error,
|
|
23
53
|
refetch,
|
|
24
54
|
} = useApiData<Collection<BibleVersion>>(
|
|
25
|
-
() => bibleClient.getVersions(languageRanges, licenseId),
|
|
26
|
-
[
|
|
55
|
+
() => bibleClient.getVersions(languageRanges, licenseId, getVersionsOptions),
|
|
56
|
+
[
|
|
57
|
+
bibleClient,
|
|
58
|
+
languageRangesKey,
|
|
59
|
+
licenseId,
|
|
60
|
+
options?.page_size,
|
|
61
|
+
options?.page_token,
|
|
62
|
+
fieldsKey,
|
|
63
|
+
options?.all_available,
|
|
64
|
+
],
|
|
27
65
|
{
|
|
28
66
|
enabled: options?.enabled !== false,
|
|
29
67
|
},
|