@youversion/platform-react-hooks 1.10.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 +22 -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/useLanguages.d.ts.map +1 -1
- package/dist/useLanguages.js +7 -1
- package/dist/useLanguages.js.map +1 -1
- 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/useLanguages.test.tsx +52 -2
- package/src/useLanguages.ts +7 -1
- package/src/useVersion.test.tsx +242 -0
- package/src/useVersions.test.tsx +641 -0
- package/src/useVersions.ts +42 -4
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { renderHook, act } from '@testing-library/react';
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
import { VerseSelectionProvider } from './VerseSelectionProvider';
|
|
5
|
+
import { useVerseSelection } from '../useVerseSelection';
|
|
6
|
+
|
|
7
|
+
// Wrapper for renderHook
|
|
8
|
+
const wrapper = ({ children }: { children: ReactNode }) => (
|
|
9
|
+
<VerseSelectionProvider>{children}</VerseSelectionProvider>
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
describe('VerseSelectionProvider', () => {
|
|
13
|
+
describe('initial state', () => {
|
|
14
|
+
it('should provide an empty Set instance', () => {
|
|
15
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
16
|
+
|
|
17
|
+
expect(result.current.selectedVerseUsfms).toBeInstanceOf(Set);
|
|
18
|
+
expect(result.current.selectedVerseUsfms.size).toBe(0);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should have selectedCount of 0', () => {
|
|
22
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
23
|
+
|
|
24
|
+
expect(result.current.selectedCount).toBe(0);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should return false for isSelected on any verse', () => {
|
|
28
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
29
|
+
|
|
30
|
+
expect(result.current.isSelected('MAT.1.1')).toBe(false);
|
|
31
|
+
expect(result.current.isSelected('GEN.1.1')).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('toggleVerse', () => {
|
|
36
|
+
it('should add verse to selection when not present', () => {
|
|
37
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
38
|
+
|
|
39
|
+
act(() => {
|
|
40
|
+
result.current.toggleVerse('MAT.1.1');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(result.current.selectedVerseUsfms.has('MAT.1.1')).toBe(true);
|
|
44
|
+
expect(result.current.selectedCount).toBe(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should remove verse from selection when already present', () => {
|
|
48
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
49
|
+
|
|
50
|
+
act(() => {
|
|
51
|
+
result.current.toggleVerse('MAT.1.1');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(result.current.selectedCount).toBe(1);
|
|
55
|
+
|
|
56
|
+
act(() => {
|
|
57
|
+
result.current.toggleVerse('MAT.1.1');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(result.current.selectedVerseUsfms.has('MAT.1.1')).toBe(false);
|
|
61
|
+
expect(result.current.selectedCount).toBe(0);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should support multiple verses selected simultaneously', () => {
|
|
65
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
66
|
+
|
|
67
|
+
act(() => {
|
|
68
|
+
result.current.toggleVerse('MAT.1.1');
|
|
69
|
+
result.current.toggleVerse('GEN.1.1');
|
|
70
|
+
result.current.toggleVerse('JHN.3.16');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(result.current.selectedCount).toBe(3);
|
|
74
|
+
expect(result.current.selectedVerseUsfms.has('MAT.1.1')).toBe(true);
|
|
75
|
+
expect(result.current.selectedVerseUsfms.has('GEN.1.1')).toBe(true);
|
|
76
|
+
expect(result.current.selectedVerseUsfms.has('JHN.3.16')).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should create new Set reference on each update (immutability)', () => {
|
|
80
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
81
|
+
|
|
82
|
+
const set1 = result.current.selectedVerseUsfms;
|
|
83
|
+
|
|
84
|
+
act(() => {
|
|
85
|
+
result.current.toggleVerse('MAT.1.1');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const set2 = result.current.selectedVerseUsfms;
|
|
89
|
+
|
|
90
|
+
expect(set1).not.toBe(set2);
|
|
91
|
+
expect(set1.size).toBe(0);
|
|
92
|
+
expect(set2.size).toBe(1);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should create new Set reference when removing a verse', () => {
|
|
96
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
97
|
+
|
|
98
|
+
act(() => {
|
|
99
|
+
result.current.toggleVerse('MAT.1.1');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const setWithVerse = result.current.selectedVerseUsfms;
|
|
103
|
+
|
|
104
|
+
act(() => {
|
|
105
|
+
result.current.toggleVerse('MAT.1.1');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const setWithoutVerse = result.current.selectedVerseUsfms;
|
|
109
|
+
|
|
110
|
+
expect(setWithVerse).not.toBe(setWithoutVerse);
|
|
111
|
+
expect(setWithVerse.size).toBe(1);
|
|
112
|
+
expect(setWithoutVerse.size).toBe(0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should not add duplicate verses', () => {
|
|
116
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
117
|
+
|
|
118
|
+
act(() => {
|
|
119
|
+
result.current.toggleVerse('MAT.1.1');
|
|
120
|
+
result.current.toggleVerse('MAT.1.1');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Should toggle off, not add twice
|
|
124
|
+
expect(result.current.selectedCount).toBe(0);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('isSelected', () => {
|
|
129
|
+
it('should return true for selected verses', () => {
|
|
130
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
131
|
+
|
|
132
|
+
act(() => {
|
|
133
|
+
result.current.toggleVerse('MAT.1.1');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(result.current.isSelected('MAT.1.1')).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should return false for unselected verses', () => {
|
|
140
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
141
|
+
|
|
142
|
+
act(() => {
|
|
143
|
+
result.current.toggleVerse('MAT.1.1');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(result.current.isSelected('GEN.1.1')).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should work correctly after toggle operations', () => {
|
|
150
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
151
|
+
|
|
152
|
+
// Initially not selected
|
|
153
|
+
expect(result.current.isSelected('MAT.1.1')).toBe(false);
|
|
154
|
+
|
|
155
|
+
// Add verse - now selected
|
|
156
|
+
act(() => {
|
|
157
|
+
result.current.toggleVerse('MAT.1.1');
|
|
158
|
+
});
|
|
159
|
+
expect(result.current.isSelected('MAT.1.1')).toBe(true);
|
|
160
|
+
|
|
161
|
+
// Remove verse - not selected again
|
|
162
|
+
act(() => {
|
|
163
|
+
result.current.toggleVerse('MAT.1.1');
|
|
164
|
+
});
|
|
165
|
+
expect(result.current.isSelected('MAT.1.1')).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('clearSelection', () => {
|
|
170
|
+
it('should clear all selected verses', () => {
|
|
171
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
172
|
+
|
|
173
|
+
act(() => {
|
|
174
|
+
result.current.toggleVerse('MAT.1.1');
|
|
175
|
+
result.current.toggleVerse('GEN.1.1');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(result.current.selectedCount).toBe(2);
|
|
179
|
+
|
|
180
|
+
act(() => {
|
|
181
|
+
result.current.clearSelection();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(result.current.selectedCount).toBe(0);
|
|
185
|
+
expect(result.current.selectedVerseUsfms.size).toBe(0);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should create new Set reference', () => {
|
|
189
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
190
|
+
|
|
191
|
+
act(() => {
|
|
192
|
+
result.current.toggleVerse('MAT.1.1');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const setBeforeClear = result.current.selectedVerseUsfms;
|
|
196
|
+
|
|
197
|
+
act(() => {
|
|
198
|
+
result.current.clearSelection();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const setAfterClear = result.current.selectedVerseUsfms;
|
|
202
|
+
|
|
203
|
+
expect(setBeforeClear).not.toBe(setAfterClear);
|
|
204
|
+
expect(setBeforeClear.size).toBe(1);
|
|
205
|
+
expect(setAfterClear.size).toBe(0);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should work when already empty', () => {
|
|
209
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
210
|
+
|
|
211
|
+
const setBeforeClear = result.current.selectedVerseUsfms;
|
|
212
|
+
|
|
213
|
+
act(() => {
|
|
214
|
+
result.current.clearSelection();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const setAfterClear = result.current.selectedVerseUsfms;
|
|
218
|
+
|
|
219
|
+
expect(setBeforeClear).not.toBe(setAfterClear);
|
|
220
|
+
expect(setAfterClear.size).toBe(0);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('selectedCount', () => {
|
|
225
|
+
it('should start at 0', () => {
|
|
226
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
227
|
+
|
|
228
|
+
expect(result.current.selectedCount).toBe(0);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should increment when verse added', () => {
|
|
232
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
233
|
+
|
|
234
|
+
act(() => {
|
|
235
|
+
result.current.toggleVerse('MAT.1.1');
|
|
236
|
+
});
|
|
237
|
+
expect(result.current.selectedCount).toBe(1);
|
|
238
|
+
|
|
239
|
+
act(() => {
|
|
240
|
+
result.current.toggleVerse('GEN.1.1');
|
|
241
|
+
});
|
|
242
|
+
expect(result.current.selectedCount).toBe(2);
|
|
243
|
+
|
|
244
|
+
act(() => {
|
|
245
|
+
result.current.toggleVerse('JHN.3.16');
|
|
246
|
+
});
|
|
247
|
+
expect(result.current.selectedCount).toBe(3);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should decrement when verse removed', () => {
|
|
251
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
252
|
+
|
|
253
|
+
act(() => {
|
|
254
|
+
result.current.toggleVerse('MAT.1.1');
|
|
255
|
+
result.current.toggleVerse('GEN.1.1');
|
|
256
|
+
});
|
|
257
|
+
expect(result.current.selectedCount).toBe(2);
|
|
258
|
+
|
|
259
|
+
act(() => {
|
|
260
|
+
result.current.toggleVerse('MAT.1.1');
|
|
261
|
+
});
|
|
262
|
+
expect(result.current.selectedCount).toBe(1);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should return to 0 after clear', () => {
|
|
266
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
267
|
+
|
|
268
|
+
act(() => {
|
|
269
|
+
result.current.toggleVerse('MAT.1.1');
|
|
270
|
+
result.current.toggleVerse('GEN.1.1');
|
|
271
|
+
result.current.toggleVerse('JHN.3.16');
|
|
272
|
+
});
|
|
273
|
+
expect(result.current.selectedCount).toBe(3);
|
|
274
|
+
|
|
275
|
+
act(() => {
|
|
276
|
+
result.current.clearSelection();
|
|
277
|
+
});
|
|
278
|
+
expect(result.current.selectedCount).toBe(0);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
describe('callback stability', () => {
|
|
283
|
+
it('should have stable toggleVerse reference across renders', () => {
|
|
284
|
+
const { result, rerender } = renderHook(() => useVerseSelection(), { wrapper });
|
|
285
|
+
|
|
286
|
+
const toggleRef1 = result.current.toggleVerse;
|
|
287
|
+
|
|
288
|
+
rerender();
|
|
289
|
+
|
|
290
|
+
const toggleRef2 = result.current.toggleVerse;
|
|
291
|
+
|
|
292
|
+
expect(toggleRef1).toBe(toggleRef2);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should have stable clearSelection reference across renders', () => {
|
|
296
|
+
const { result, rerender } = renderHook(() => useVerseSelection(), { wrapper });
|
|
297
|
+
|
|
298
|
+
const clearRef1 = result.current.clearSelection;
|
|
299
|
+
|
|
300
|
+
rerender();
|
|
301
|
+
|
|
302
|
+
const clearRef2 = result.current.clearSelection;
|
|
303
|
+
|
|
304
|
+
expect(clearRef1).toBe(clearRef2);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should have stable toggleVerse reference after state changes', () => {
|
|
308
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
309
|
+
|
|
310
|
+
const toggleRef1 = result.current.toggleVerse;
|
|
311
|
+
|
|
312
|
+
act(() => {
|
|
313
|
+
result.current.toggleVerse('MAT.1.1');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const toggleRef2 = result.current.toggleVerse;
|
|
317
|
+
|
|
318
|
+
expect(toggleRef1).toBe(toggleRef2);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should have stable clearSelection reference after state changes', () => {
|
|
322
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
323
|
+
|
|
324
|
+
const clearRef1 = result.current.clearSelection;
|
|
325
|
+
|
|
326
|
+
act(() => {
|
|
327
|
+
result.current.toggleVerse('MAT.1.1');
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const clearRef2 = result.current.clearSelection;
|
|
331
|
+
|
|
332
|
+
expect(clearRef1).toBe(clearRef2);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('should update isSelected reference when selectedVerseUsfms changes', () => {
|
|
336
|
+
const { result } = renderHook(() => useVerseSelection(), { wrapper });
|
|
337
|
+
|
|
338
|
+
const isSelectedRef1 = result.current.isSelected;
|
|
339
|
+
|
|
340
|
+
act(() => {
|
|
341
|
+
result.current.toggleVerse('MAT.1.1');
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const isSelectedRef2 = result.current.isSelected;
|
|
345
|
+
|
|
346
|
+
// isSelected has selectedVerseUsfms in its dependency array, so it should change
|
|
347
|
+
expect(isSelectedRef1).not.toBe(isSelectedRef2);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should have stable isSelected reference when no state changes', () => {
|
|
351
|
+
const { result, rerender } = renderHook(() => useVerseSelection(), { wrapper });
|
|
352
|
+
|
|
353
|
+
const isSelectedRef1 = result.current.isSelected;
|
|
354
|
+
|
|
355
|
+
rerender();
|
|
356
|
+
|
|
357
|
+
const isSelectedRef2 = result.current.isSelected;
|
|
358
|
+
|
|
359
|
+
expect(isSelectedRef1).toBe(isSelectedRef2);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { renderHook, waitFor } from '@testing-library/react';
|
|
1
|
+
import { renderHook, waitFor, act } from '@testing-library/react';
|
|
2
2
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
3
|
import type { ReactNode } from 'react';
|
|
4
4
|
import { useLanguages } from './useLanguages';
|
|
@@ -206,11 +206,61 @@ describe('useLanguages', () => {
|
|
|
206
206
|
|
|
207
207
|
expect(mockGetLanguages).toHaveBeenCalledTimes(1);
|
|
208
208
|
|
|
209
|
-
|
|
209
|
+
act(() => {
|
|
210
|
+
result.current.refetch();
|
|
211
|
+
});
|
|
210
212
|
|
|
211
213
|
await waitFor(() => {
|
|
212
214
|
expect(mockGetLanguages).toHaveBeenCalledTimes(2);
|
|
213
215
|
});
|
|
214
216
|
});
|
|
217
|
+
|
|
218
|
+
it('should fetch languages with fields filter', async () => {
|
|
219
|
+
const wrapper = createWrapper({
|
|
220
|
+
appKey: mockAppKey,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const options: GetLanguagesOptions = {
|
|
224
|
+
fields: ['id', 'language', 'script'],
|
|
225
|
+
page_size: '*',
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const { result } = renderHook(() => useLanguages(options), { wrapper });
|
|
229
|
+
|
|
230
|
+
await waitFor(() => {
|
|
231
|
+
expect(result.current.loading).toBe(false);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(mockGetLanguages).toHaveBeenCalledWith(options);
|
|
235
|
+
expect(result.current.languages).toEqual(mockLanguages);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should refetch when fields change', async () => {
|
|
239
|
+
const wrapper = createWrapper({
|
|
240
|
+
appKey: mockAppKey,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const { result, rerender } = renderHook(({ options }) => useLanguages(options), {
|
|
244
|
+
wrapper,
|
|
245
|
+
initialProps: { options: { fields: ['id', 'language'] } as GetLanguagesOptions },
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
await waitFor(() => {
|
|
249
|
+
expect(result.current.loading).toBe(false);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
expect(mockGetLanguages).toHaveBeenCalledTimes(1);
|
|
253
|
+
|
|
254
|
+
rerender({ options: { fields: ['id', 'language', 'script'] } as GetLanguagesOptions });
|
|
255
|
+
|
|
256
|
+
await waitFor(() => {
|
|
257
|
+
expect(result.current.loading).toBe(false);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
expect(mockGetLanguages).toHaveBeenCalledTimes(2);
|
|
261
|
+
expect(mockGetLanguages).toHaveBeenLastCalledWith({
|
|
262
|
+
fields: ['id', 'language', 'script'],
|
|
263
|
+
});
|
|
264
|
+
});
|
|
215
265
|
});
|
|
216
266
|
});
|
package/src/useLanguages.ts
CHANGED
|
@@ -21,7 +21,13 @@ export function useLanguages(
|
|
|
21
21
|
|
|
22
22
|
const { data, loading, error, refetch } = useApiData<Collection<Language>>(
|
|
23
23
|
() => languagesClient.getLanguages(options),
|
|
24
|
-
[
|
|
24
|
+
[
|
|
25
|
+
languagesClient,
|
|
26
|
+
JSON.stringify(options.fields),
|
|
27
|
+
options?.country,
|
|
28
|
+
options?.page_size,
|
|
29
|
+
options?.page_token,
|
|
30
|
+
],
|
|
25
31
|
{
|
|
26
32
|
enabled: apiOptions?.enabled !== false,
|
|
27
33
|
},
|
|
@@ -0,0 +1,242 @@
|
|
|
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 { useVersion } from './useVersion';
|
|
5
|
+
import { YouVersionContext } from './context';
|
|
6
|
+
import { type BibleClient, type BibleVersion } from '@youversion/platform-core';
|
|
7
|
+
import { useBibleClient } from './useBibleClient';
|
|
8
|
+
|
|
9
|
+
vi.mock('./useBibleClient');
|
|
10
|
+
|
|
11
|
+
describe('useVersion', () => {
|
|
12
|
+
const mockAppKey = 'test-app-key';
|
|
13
|
+
const mockGetVersion = vi.fn();
|
|
14
|
+
|
|
15
|
+
const mockVersion: BibleVersion = {
|
|
16
|
+
id: 111,
|
|
17
|
+
title: 'New International Version',
|
|
18
|
+
abbreviation: 'NIV',
|
|
19
|
+
localized_title: 'New International Version',
|
|
20
|
+
localized_abbreviation: 'NIV',
|
|
21
|
+
language_tag: 'en',
|
|
22
|
+
books: ['GEN', 'EXO', 'LEV'],
|
|
23
|
+
youversion_deep_link: 'https://bible.com/versions/111',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const createWrapper = (contextValue: { appKey: string }) => {
|
|
27
|
+
return ({ children }: { children: ReactNode }) => (
|
|
28
|
+
<YouVersionContext.Provider value={contextValue}>{children}</YouVersionContext.Provider>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
vi.resetAllMocks();
|
|
34
|
+
|
|
35
|
+
mockGetVersion.mockResolvedValue(mockVersion);
|
|
36
|
+
|
|
37
|
+
const mockClient: Partial<BibleClient> = { getVersion: mockGetVersion };
|
|
38
|
+
vi.mocked(useBibleClient).mockReturnValue(mockClient as BibleClient);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('fetching version', () => {
|
|
42
|
+
it('should fetch version by ID', async () => {
|
|
43
|
+
const wrapper = createWrapper({
|
|
44
|
+
appKey: mockAppKey,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const { result } = renderHook(() => useVersion(111), { wrapper });
|
|
48
|
+
|
|
49
|
+
expect(result.current.loading).toBe(true);
|
|
50
|
+
expect(result.current.version).toBe(null);
|
|
51
|
+
|
|
52
|
+
await waitFor(() => {
|
|
53
|
+
expect(result.current.loading).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(mockGetVersion).toHaveBeenCalledWith(111);
|
|
57
|
+
expect(result.current.version).toEqual(mockVersion);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should fetch different version by ID', async () => {
|
|
61
|
+
const mockKJV: BibleVersion = {
|
|
62
|
+
id: 1,
|
|
63
|
+
title: 'King James Version',
|
|
64
|
+
abbreviation: 'KJV',
|
|
65
|
+
localized_title: 'King James Version',
|
|
66
|
+
localized_abbreviation: 'KJV',
|
|
67
|
+
language_tag: 'en',
|
|
68
|
+
books: ['GEN', 'EXO', 'LEV'],
|
|
69
|
+
youversion_deep_link: 'https://bible.com/versions/1',
|
|
70
|
+
};
|
|
71
|
+
mockGetVersion.mockResolvedValueOnce(mockKJV);
|
|
72
|
+
|
|
73
|
+
const wrapper = createWrapper({
|
|
74
|
+
appKey: mockAppKey,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const { result } = renderHook(() => useVersion(1), { wrapper });
|
|
78
|
+
|
|
79
|
+
await waitFor(() => {
|
|
80
|
+
expect(result.current.loading).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(mockGetVersion).toHaveBeenCalledWith(1);
|
|
84
|
+
expect(result.current.version).toEqual(mockKJV);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('refetch behavior', () => {
|
|
89
|
+
it('should refetch when versionId changes', async () => {
|
|
90
|
+
const wrapper = createWrapper({
|
|
91
|
+
appKey: mockAppKey,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const { result, rerender } = renderHook(({ versionId }) => useVersion(versionId), {
|
|
95
|
+
wrapper,
|
|
96
|
+
initialProps: { versionId: 111 },
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
await waitFor(() => {
|
|
100
|
+
expect(result.current.loading).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
expect(mockGetVersion).toHaveBeenCalledTimes(1);
|
|
104
|
+
expect(mockGetVersion).toHaveBeenNthCalledWith(1, 111);
|
|
105
|
+
|
|
106
|
+
rerender({ versionId: 1 });
|
|
107
|
+
|
|
108
|
+
await waitFor(() => {
|
|
109
|
+
expect(result.current.loading).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(mockGetVersion).toHaveBeenCalledTimes(2);
|
|
113
|
+
expect(mockGetVersion).toHaveBeenNthCalledWith(2, 1);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('enabled option', () => {
|
|
118
|
+
it('should not fetch when enabled is false', async () => {
|
|
119
|
+
const wrapper = createWrapper({
|
|
120
|
+
appKey: mockAppKey,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const { result } = renderHook(() => useVersion(111, { enabled: false }), {
|
|
124
|
+
wrapper,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await waitFor(() => {
|
|
128
|
+
expect(result.current.loading).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(mockGetVersion).not.toHaveBeenCalled();
|
|
132
|
+
expect(result.current.version).toBe(null);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should fetch when enabled is true', async () => {
|
|
136
|
+
const wrapper = createWrapper({
|
|
137
|
+
appKey: mockAppKey,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const { result } = renderHook(() => useVersion(111, { enabled: true }), {
|
|
141
|
+
wrapper,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await waitFor(() => {
|
|
145
|
+
expect(result.current.loading).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
expect(mockGetVersion).toHaveBeenCalled();
|
|
149
|
+
expect(result.current.version).toEqual(mockVersion);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should fetch when enabled is not specified', async () => {
|
|
153
|
+
const wrapper = createWrapper({
|
|
154
|
+
appKey: mockAppKey,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const { result } = renderHook(() => useVersion(111), {
|
|
158
|
+
wrapper,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await waitFor(() => {
|
|
162
|
+
expect(result.current.loading).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(mockGetVersion).toHaveBeenCalled();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('error handling', () => {
|
|
170
|
+
it('should handle fetch errors', async () => {
|
|
171
|
+
const error = new Error('Failed to fetch version');
|
|
172
|
+
mockGetVersion.mockRejectedValueOnce(error);
|
|
173
|
+
|
|
174
|
+
const wrapper = createWrapper({
|
|
175
|
+
appKey: mockAppKey,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const { result } = renderHook(() => useVersion(111), { wrapper });
|
|
179
|
+
|
|
180
|
+
await waitFor(() => {
|
|
181
|
+
expect(result.current.loading).toBe(false);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(result.current.error).toEqual(error);
|
|
185
|
+
expect(result.current.version).toBe(null);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should clear error on successful refetch', async () => {
|
|
189
|
+
const error = new Error('Failed to fetch version');
|
|
190
|
+
mockGetVersion.mockRejectedValueOnce(error).mockResolvedValueOnce(mockVersion);
|
|
191
|
+
|
|
192
|
+
const wrapper = createWrapper({
|
|
193
|
+
appKey: mockAppKey,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const { result } = renderHook(() => useVersion(111), { wrapper });
|
|
197
|
+
|
|
198
|
+
await waitFor(() => {
|
|
199
|
+
expect(result.current.loading).toBe(false);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
expect(result.current.error).toEqual(error);
|
|
203
|
+
|
|
204
|
+
act(() => {
|
|
205
|
+
result.current.refetch();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
await waitFor(() => {
|
|
209
|
+
expect(result.current.error).toBe(null);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
expect(result.current.version).toEqual(mockVersion);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('manual refetch', () => {
|
|
217
|
+
it('should support manual refetch', async () => {
|
|
218
|
+
const wrapper = createWrapper({
|
|
219
|
+
appKey: mockAppKey,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const { result } = renderHook(() => useVersion(111), { wrapper });
|
|
223
|
+
|
|
224
|
+
await waitFor(() => {
|
|
225
|
+
expect(result.current.loading).toBe(false);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
expect(mockGetVersion).toHaveBeenCalledTimes(1);
|
|
229
|
+
expect(mockGetVersion).toHaveBeenNthCalledWith(1, 111);
|
|
230
|
+
|
|
231
|
+
act(() => {
|
|
232
|
+
result.current.refetch();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
await waitFor(() => {
|
|
236
|
+
expect(mockGetVersion).toHaveBeenCalledTimes(2);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
expect(mockGetVersion).toHaveBeenNthCalledWith(2, 111);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|