@youversion/platform-react-hooks 1.18.1 → 1.20.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.
Files changed (79) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +3 -7
  3. package/CHANGELOG.md +32 -0
  4. package/dist/__tests__/mocks/bibles.d.ts +6 -1
  5. package/dist/__tests__/mocks/bibles.d.ts.map +1 -1
  6. package/dist/__tests__/mocks/bibles.js +10 -0
  7. package/dist/__tests__/mocks/bibles.js.map +1 -1
  8. package/dist/__tests__/mocks/core-mock-factory.d.ts +83 -0
  9. package/dist/__tests__/mocks/core-mock-factory.d.ts.map +1 -0
  10. package/dist/__tests__/mocks/core-mock-factory.js +138 -0
  11. package/dist/__tests__/mocks/core-mock-factory.js.map +1 -0
  12. package/dist/context/ReaderContext.d.ts +6 -0
  13. package/dist/context/ReaderContext.d.ts.map +1 -1
  14. package/dist/context/ReaderContext.js +6 -0
  15. package/dist/context/ReaderContext.js.map +1 -1
  16. package/dist/context/ReaderProvider.d.ts +3 -0
  17. package/dist/context/ReaderProvider.d.ts.map +1 -1
  18. package/dist/context/ReaderProvider.js +3 -0
  19. package/dist/context/ReaderProvider.js.map +1 -1
  20. package/dist/context/VerseSelectionContext.d.ts +6 -0
  21. package/dist/context/VerseSelectionContext.d.ts.map +1 -1
  22. package/dist/context/VerseSelectionContext.js +3 -0
  23. package/dist/context/VerseSelectionContext.js.map +1 -1
  24. package/dist/context/VerseSelectionProvider.d.ts +3 -0
  25. package/dist/context/VerseSelectionProvider.d.ts.map +1 -1
  26. package/dist/context/VerseSelectionProvider.js +3 -0
  27. package/dist/context/VerseSelectionProvider.js.map +1 -1
  28. package/dist/test/utils.d.ts +7 -0
  29. package/dist/test/utils.d.ts.map +1 -0
  30. package/dist/test/utils.js +7 -0
  31. package/dist/test/utils.js.map +1 -0
  32. package/dist/useChapterNavigation.d.ts +3 -0
  33. package/dist/useChapterNavigation.d.ts.map +1 -1
  34. package/dist/useChapterNavigation.js +3 -0
  35. package/dist/useChapterNavigation.js.map +1 -1
  36. package/dist/useInitData.d.ts +4 -0
  37. package/dist/useInitData.d.ts.map +1 -1
  38. package/dist/useInitData.js +4 -0
  39. package/dist/useInitData.js.map +1 -1
  40. package/dist/useVerseSelection.d.ts +3 -0
  41. package/dist/useVerseSelection.d.ts.map +1 -1
  42. package/dist/useVerseSelection.js +3 -0
  43. package/dist/useVerseSelection.js.map +1 -1
  44. package/package.json +2 -2
  45. package/src/__tests__/mocks/bibles.ts +18 -1
  46. package/src/__tests__/mocks/core-mock-factory.ts +226 -0
  47. package/src/context/ReaderContext.tsx +6 -0
  48. package/src/context/ReaderProvider.tsx +3 -0
  49. package/src/context/VerseSelectionContext.tsx +6 -0
  50. package/src/context/VerseSelectionProvider.tsx +3 -0
  51. package/src/context/YouVersionAuthProvider.test.tsx +14 -131
  52. package/src/test/utils.tsx +12 -0
  53. package/src/useBibleClient.test.tsx +8 -37
  54. package/src/useBook.test.tsx +158 -0
  55. package/src/useBooks.test.tsx +148 -0
  56. package/src/useChapter.test.tsx +70 -128
  57. package/src/useChapterNavigation.ts +3 -0
  58. package/src/useChapters.test.tsx +80 -150
  59. package/src/useHighlights.test.tsx +33 -104
  60. package/src/useInitData.ts +4 -0
  61. package/src/useLanguage.test.tsx +8 -10
  62. package/src/useLanguageClient.test.tsx +9 -25
  63. package/src/useLanguages.test.tsx +27 -64
  64. package/src/usePassage.test.tsx +304 -0
  65. package/src/useTheme.test.tsx +32 -0
  66. package/src/useVOTD.test.tsx +28 -67
  67. package/src/useVerse.test.tsx +73 -149
  68. package/src/useVerseSelection.ts +3 -0
  69. package/src/useVerses.test.tsx +37 -104
  70. package/src/useVersion.test.tsx +29 -66
  71. package/src/useVersions.test.tsx +72 -154
  72. package/src/useYVAuth.test.tsx +26 -134
  73. package/src/utility/getDayOfYear.test.ts +48 -0
  74. package/vitest.config.ts +12 -0
  75. package/src/context/ReaderProvider.test.tsx +0 -264
  76. package/src/context/VerseSelectionProvider.test.tsx +0 -362
  77. package/src/useChapterNavigation.test.tsx +0 -160
  78. package/src/useVerseSelection.test.tsx +0 -33
  79. package/vitest.setup.ts +0 -1
@@ -1,15 +1,13 @@
1
1
  import { renderHook, waitFor, act } from '@testing-library/react';
2
- import { describe, it, expect, vi, beforeEach } from 'vitest';
3
- import type { ReactNode } from 'react';
2
+ import { describe, expect, vi, beforeEach, it } from 'vitest';
4
3
  import { useVerses } from './useVerses';
5
- import { YouVersionContext } from './context';
6
4
  import { type BibleClient, type BibleVerse, type Collection } from '@youversion/platform-core';
7
5
  import { useBibleClient } from './useBibleClient';
6
+ import { createYVWrapper } from './test/utils';
8
7
 
9
8
  vi.mock('./useBibleClient');
10
9
 
11
10
  describe('useVerses', () => {
12
- const mockAppKey = 'test-app-key';
13
11
  const mockGetVerses = vi.fn();
14
12
 
15
13
  const mockVerses: Collection<BibleVerse> = {
@@ -21,15 +19,7 @@ describe('useVerses', () => {
21
19
  next_page_token: null,
22
20
  };
23
21
 
24
- const createWrapper = (contextValue: { appKey: string }) => {
25
- return ({ children }: { children: ReactNode }) => (
26
- <YouVersionContext.Provider value={contextValue}>{children}</YouVersionContext.Provider>
27
- );
28
- };
29
-
30
22
  beforeEach(() => {
31
- vi.resetAllMocks();
32
-
33
23
  mockGetVerses.mockResolvedValue(mockVerses);
34
24
 
35
25
  const mockClient: Partial<BibleClient> = { getVerses: mockGetVerses };
@@ -38,10 +28,7 @@ describe('useVerses', () => {
38
28
 
39
29
  describe('fetching verses', () => {
40
30
  it('should fetch verses with all 3 parameters', async () => {
41
- const wrapper = createWrapper({
42
- appKey: mockAppKey,
43
- });
44
-
31
+ const wrapper = createYVWrapper();
45
32
  const { result } = renderHook(() => useVerses(111, 'MAT', 1), { wrapper });
46
33
 
47
34
  expect(result.current.loading).toBe(true);
@@ -51,102 +38,54 @@ describe('useVerses', () => {
51
38
  expect(result.current.loading).toBe(false);
52
39
  });
53
40
 
54
- expect(mockGetVerses).toHaveBeenCalledWith(111, 'MAT', 1);
55
- expect(result.current.verses).toEqual(mockVerses);
56
- });
57
-
58
- it('should refetch when versionId changes', async () => {
59
- const wrapper = createWrapper({
60
- appKey: mockAppKey,
61
- });
62
-
63
- const { result, rerender } = renderHook(({ versionId }) => useVerses(versionId, 'MAT', 1), {
64
- wrapper,
65
- initialProps: { versionId: 1 },
66
- });
67
-
68
- await waitFor(() => {
69
- expect(result.current.loading).toBe(false);
70
- });
71
-
72
- expect(mockGetVerses).toHaveBeenCalledTimes(1);
73
- expect(mockGetVerses).toHaveBeenLastCalledWith(1, 'MAT', 1);
74
-
75
- act(() => {
76
- rerender({ versionId: 111 });
77
- });
78
-
79
- await waitFor(() => {
80
- expect(result.current.loading).toBe(false);
81
- });
82
-
83
- expect(mockGetVerses).toHaveBeenCalledTimes(2);
84
- expect(mockGetVerses).toHaveBeenLastCalledWith(111, 'MAT', 1);
85
- });
86
-
87
- it('should refetch when book changes', async () => {
88
- const wrapper = createWrapper({
89
- appKey: mockAppKey,
90
- });
91
-
92
- const { result, rerender } = renderHook(({ book }) => useVerses(1, book, 1), {
93
- wrapper,
94
- initialProps: { book: 'MAT' },
95
- });
96
-
97
- await waitFor(() => {
98
- expect(result.current.loading).toBe(false);
99
- });
100
-
101
- expect(mockGetVerses).toHaveBeenCalledTimes(1);
102
- expect(mockGetVerses).toHaveBeenLastCalledWith(1, 'MAT', 1);
103
-
104
- act(() => {
105
- rerender({ book: 'GEN' });
106
- });
107
-
108
- await waitFor(() => {
109
- expect(result.current.loading).toBe(false);
110
- });
111
-
112
- expect(mockGetVerses).toHaveBeenCalledTimes(2);
113
- expect(mockGetVerses).toHaveBeenLastCalledWith(1, 'GEN', 1);
41
+ expect.soft(mockGetVerses).toHaveBeenCalledWith(111, 'MAT', 1);
42
+ expect.soft(result.current.verses).toEqual(mockVerses);
114
43
  });
115
44
 
116
- it('should refetch when chapter changes', async () => {
117
- const wrapper = createWrapper({
118
- appKey: mockAppKey,
119
- });
120
-
121
- const { result, rerender } = renderHook(({ chapter }) => useVerses(1, 'MAT', chapter), {
45
+ it.each([
46
+ {
47
+ param: 'versionId',
48
+ initialArgs: [1, 'MAT', 1] as [number, string, number],
49
+ updatedArgs: [111, 'MAT', 1] as [number, string, number],
50
+ },
51
+ {
52
+ param: 'book',
53
+ initialArgs: [1, 'MAT', 1] as [number, string, number],
54
+ updatedArgs: [1, 'GEN', 1] as [number, string, number],
55
+ },
56
+ {
57
+ param: 'chapter',
58
+ initialArgs: [1, 'MAT', 1] as [number, string, number],
59
+ updatedArgs: [1, 'MAT', 5] as [number, string, number],
60
+ },
61
+ ])('should refetch when $param changes', async ({ initialArgs, updatedArgs }) => {
62
+ const wrapper = createYVWrapper();
63
+ const { result, rerender } = renderHook(({ args }) => useVerses(args[0], args[1], args[2]), {
122
64
  wrapper,
123
- initialProps: { chapter: 1 },
65
+ initialProps: { args: initialArgs },
124
66
  });
125
67
 
126
68
  await waitFor(() => {
127
69
  expect(result.current.loading).toBe(false);
128
70
  });
129
71
 
130
- expect(mockGetVerses).toHaveBeenCalledTimes(1);
131
- expect(mockGetVerses).toHaveBeenLastCalledWith(1, 'MAT', 1);
72
+ expect.soft(mockGetVerses).toHaveBeenCalledTimes(1);
73
+ expect.soft(mockGetVerses).toHaveBeenLastCalledWith(...initialArgs);
132
74
 
133
75
  act(() => {
134
- rerender({ chapter: 5 });
76
+ rerender({ args: updatedArgs });
135
77
  });
136
78
 
137
79
  await waitFor(() => {
138
80
  expect(result.current.loading).toBe(false);
139
81
  });
140
82
 
141
- expect(mockGetVerses).toHaveBeenCalledTimes(2);
142
- expect(mockGetVerses).toHaveBeenLastCalledWith(1, 'MAT', 5);
83
+ expect.soft(mockGetVerses).toHaveBeenCalledTimes(2);
84
+ expect.soft(mockGetVerses).toHaveBeenLastCalledWith(...updatedArgs);
143
85
  });
144
86
 
145
87
  it('should not fetch when enabled is false', async () => {
146
- const wrapper = createWrapper({
147
- appKey: mockAppKey,
148
- });
149
-
88
+ const wrapper = createYVWrapper();
150
89
  const { result } = renderHook(() => useVerses(1, 'MAT', 1, { enabled: false }), {
151
90
  wrapper,
152
91
  });
@@ -155,33 +94,27 @@ describe('useVerses', () => {
155
94
  expect(result.current.loading).toBe(false);
156
95
  });
157
96
 
158
- expect(mockGetVerses).not.toHaveBeenCalled();
159
- expect(result.current.verses).toBe(null);
97
+ expect.soft(mockGetVerses).not.toHaveBeenCalled();
98
+ expect.soft(result.current.verses).toBe(null);
160
99
  });
161
100
 
162
101
  it('should handle fetch errors', async () => {
102
+ const wrapper = createYVWrapper();
163
103
  const error = new Error('Failed to fetch verses');
164
104
  mockGetVerses.mockRejectedValueOnce(error);
165
105
 
166
- const wrapper = createWrapper({
167
- appKey: mockAppKey,
168
- });
169
-
170
106
  const { result } = renderHook(() => useVerses(1, 'MAT', 1), { wrapper });
171
107
 
172
108
  await waitFor(() => {
173
109
  expect(result.current.loading).toBe(false);
174
110
  });
175
111
 
176
- expect(result.current.error).toEqual(error);
177
- expect(result.current.verses).toBe(null);
112
+ expect.soft(result.current.error).toEqual(error);
113
+ expect.soft(result.current.verses).toBe(null);
178
114
  });
179
115
 
180
116
  it('should support manual refetch', async () => {
181
- const wrapper = createWrapper({
182
- appKey: mockAppKey,
183
- });
184
-
117
+ const wrapper = createYVWrapper();
185
118
  const { result } = renderHook(() => useVerses(1, 'MAT', 1), { wrapper });
186
119
 
187
120
  await waitFor(() => {
@@ -1,15 +1,13 @@
1
1
  import { renderHook, waitFor, act } from '@testing-library/react';
2
- import { describe, it, expect, vi, beforeEach } from 'vitest';
3
- import type { ReactNode } from 'react';
2
+ import { describe, expect, vi, beforeEach, it } from 'vitest';
4
3
  import { useVersion } from './useVersion';
5
- import { YouVersionContext } from './context';
6
4
  import { type BibleClient, type BibleVersion } from '@youversion/platform-core';
7
5
  import { useBibleClient } from './useBibleClient';
6
+ import { createYVWrapper } from './test/utils';
8
7
 
9
8
  vi.mock('./useBibleClient');
10
9
 
11
10
  describe('useVersion', () => {
12
- const mockAppKey = 'test-app-key';
13
11
  const mockGetVersion = vi.fn();
14
12
 
15
13
  const mockVersion: BibleVersion = {
@@ -23,15 +21,7 @@ describe('useVersion', () => {
23
21
  youversion_deep_link: 'https://bible.com/versions/111',
24
22
  };
25
23
 
26
- const createWrapper = (contextValue: { appKey: string }) => {
27
- return ({ children }: { children: ReactNode }) => (
28
- <YouVersionContext.Provider value={contextValue}>{children}</YouVersionContext.Provider>
29
- );
30
- };
31
-
32
24
  beforeEach(() => {
33
- vi.resetAllMocks();
34
-
35
25
  mockGetVersion.mockResolvedValue(mockVersion);
36
26
 
37
27
  const mockClient: Partial<BibleClient> = { getVersion: mockGetVersion };
@@ -40,10 +30,7 @@ describe('useVersion', () => {
40
30
 
41
31
  describe('fetching version', () => {
42
32
  it('should fetch version by ID', async () => {
43
- const wrapper = createWrapper({
44
- appKey: mockAppKey,
45
- });
46
-
33
+ const wrapper = createYVWrapper();
47
34
  const { result } = renderHook(() => useVersion(111), { wrapper });
48
35
 
49
36
  expect(result.current.loading).toBe(true);
@@ -53,11 +40,12 @@ describe('useVersion', () => {
53
40
  expect(result.current.loading).toBe(false);
54
41
  });
55
42
 
56
- expect(mockGetVersion).toHaveBeenCalledWith(111);
57
- expect(result.current.version).toEqual(mockVersion);
43
+ expect.soft(mockGetVersion).toHaveBeenCalledWith(111);
44
+ expect.soft(result.current.version).toEqual(mockVersion);
58
45
  });
59
46
 
60
47
  it('should fetch different version by ID', async () => {
48
+ const wrapper = createYVWrapper();
61
49
  const mockKJV: BibleVersion = {
62
50
  id: 1,
63
51
  title: 'King James Version',
@@ -70,27 +58,20 @@ describe('useVersion', () => {
70
58
  };
71
59
  mockGetVersion.mockResolvedValueOnce(mockKJV);
72
60
 
73
- const wrapper = createWrapper({
74
- appKey: mockAppKey,
75
- });
76
-
77
61
  const { result } = renderHook(() => useVersion(1), { wrapper });
78
62
 
79
63
  await waitFor(() => {
80
64
  expect(result.current.loading).toBe(false);
81
65
  });
82
66
 
83
- expect(mockGetVersion).toHaveBeenCalledWith(1);
84
- expect(result.current.version).toEqual(mockKJV);
67
+ expect.soft(mockGetVersion).toHaveBeenCalledWith(1);
68
+ expect.soft(result.current.version).toEqual(mockKJV);
85
69
  });
86
70
  });
87
71
 
88
72
  describe('refetch behavior', () => {
89
73
  it('should refetch when versionId changes', async () => {
90
- const wrapper = createWrapper({
91
- appKey: mockAppKey,
92
- });
93
-
74
+ const wrapper = createYVWrapper();
94
75
  const { result, rerender } = renderHook(({ versionId }) => useVersion(versionId), {
95
76
  wrapper,
96
77
  initialProps: { versionId: 111 },
@@ -100,8 +81,8 @@ describe('useVersion', () => {
100
81
  expect(result.current.loading).toBe(false);
101
82
  });
102
83
 
103
- expect(mockGetVersion).toHaveBeenCalledTimes(1);
104
- expect(mockGetVersion).toHaveBeenNthCalledWith(1, 111);
84
+ expect.soft(mockGetVersion).toHaveBeenCalledTimes(1);
85
+ expect.soft(mockGetVersion).toHaveBeenNthCalledWith(1, 111);
105
86
 
106
87
  rerender({ versionId: 1 });
107
88
 
@@ -109,17 +90,14 @@ describe('useVersion', () => {
109
90
  expect(result.current.loading).toBe(false);
110
91
  });
111
92
 
112
- expect(mockGetVersion).toHaveBeenCalledTimes(2);
113
- expect(mockGetVersion).toHaveBeenNthCalledWith(2, 1);
93
+ expect.soft(mockGetVersion).toHaveBeenCalledTimes(2);
94
+ expect.soft(mockGetVersion).toHaveBeenNthCalledWith(2, 1);
114
95
  });
115
96
  });
116
97
 
117
98
  describe('enabled option', () => {
118
99
  it('should not fetch when enabled is false', async () => {
119
- const wrapper = createWrapper({
120
- appKey: mockAppKey,
121
- });
122
-
100
+ const wrapper = createYVWrapper();
123
101
  const { result } = renderHook(() => useVersion(111, { enabled: false }), {
124
102
  wrapper,
125
103
  });
@@ -128,15 +106,12 @@ describe('useVersion', () => {
128
106
  expect(result.current.loading).toBe(false);
129
107
  });
130
108
 
131
- expect(mockGetVersion).not.toHaveBeenCalled();
132
- expect(result.current.version).toBe(null);
109
+ expect.soft(mockGetVersion).not.toHaveBeenCalled();
110
+ expect.soft(result.current.version).toBe(null);
133
111
  });
134
112
 
135
113
  it('should fetch when enabled is true', async () => {
136
- const wrapper = createWrapper({
137
- appKey: mockAppKey,
138
- });
139
-
114
+ const wrapper = createYVWrapper();
140
115
  const { result } = renderHook(() => useVersion(111, { enabled: true }), {
141
116
  wrapper,
142
117
  });
@@ -145,15 +120,12 @@ describe('useVersion', () => {
145
120
  expect(result.current.loading).toBe(false);
146
121
  });
147
122
 
148
- expect(mockGetVersion).toHaveBeenCalled();
149
- expect(result.current.version).toEqual(mockVersion);
123
+ expect.soft(mockGetVersion).toHaveBeenCalled();
124
+ expect.soft(result.current.version).toEqual(mockVersion);
150
125
  });
151
126
 
152
127
  it('should fetch when enabled is not specified', async () => {
153
- const wrapper = createWrapper({
154
- appKey: mockAppKey,
155
- });
156
-
128
+ const wrapper = createYVWrapper();
157
129
  const { result } = renderHook(() => useVersion(111), {
158
130
  wrapper,
159
131
  });
@@ -168,31 +140,25 @@ describe('useVersion', () => {
168
140
 
169
141
  describe('error handling', () => {
170
142
  it('should handle fetch errors', async () => {
143
+ const wrapper = createYVWrapper();
171
144
  const error = new Error('Failed to fetch version');
172
145
  mockGetVersion.mockRejectedValueOnce(error);
173
146
 
174
- const wrapper = createWrapper({
175
- appKey: mockAppKey,
176
- });
177
-
178
147
  const { result } = renderHook(() => useVersion(111), { wrapper });
179
148
 
180
149
  await waitFor(() => {
181
150
  expect(result.current.loading).toBe(false);
182
151
  });
183
152
 
184
- expect(result.current.error).toEqual(error);
185
- expect(result.current.version).toBe(null);
153
+ expect.soft(result.current.error).toEqual(error);
154
+ expect.soft(result.current.version).toBe(null);
186
155
  });
187
156
 
188
157
  it('should clear error on successful refetch', async () => {
158
+ const wrapper = createYVWrapper();
189
159
  const error = new Error('Failed to fetch version');
190
160
  mockGetVersion.mockRejectedValueOnce(error).mockResolvedValueOnce(mockVersion);
191
161
 
192
- const wrapper = createWrapper({
193
- appKey: mockAppKey,
194
- });
195
-
196
162
  const { result } = renderHook(() => useVersion(111), { wrapper });
197
163
 
198
164
  await waitFor(() => {
@@ -210,25 +176,22 @@ describe('useVersion', () => {
210
176
  expect(result.current.error).toBe(null);
211
177
  });
212
178
 
213
- expect(result.current.error).toBe(null);
214
- expect(result.current.version).toEqual(mockVersion);
179
+ expect.soft(result.current.error).toBe(null);
180
+ expect.soft(result.current.version).toEqual(mockVersion);
215
181
  });
216
182
  });
217
183
 
218
184
  describe('manual refetch', () => {
219
185
  it('should support manual refetch', async () => {
220
- const wrapper = createWrapper({
221
- appKey: mockAppKey,
222
- });
223
-
186
+ const wrapper = createYVWrapper();
224
187
  const { result } = renderHook(() => useVersion(111), { wrapper });
225
188
 
226
189
  await waitFor(() => {
227
190
  expect(result.current.loading).toBe(false);
228
191
  });
229
192
 
230
- expect(mockGetVersion).toHaveBeenCalledTimes(1);
231
- expect(mockGetVersion).toHaveBeenNthCalledWith(1, 111);
193
+ expect.soft(mockGetVersion).toHaveBeenCalledTimes(1);
194
+ expect.soft(mockGetVersion).toHaveBeenNthCalledWith(1, 111);
232
195
 
233
196
  act(() => {
234
197
  result.current.refetch();