@youversion/platform-react-hooks 1.12.0 → 1.12.2

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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @youversion/platform-react-hooks@1.12.0 build /home/runner/work/platform-sdk-react/platform-sdk-react/packages/hooks
2
+ > @youversion/platform-react-hooks@1.12.2 build /home/runner/work/platform-sdk-react/platform-sdk-react/packages/hooks
3
3
  > tsc -p tsconfig.build.json
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @youversion/platform-react-hooks
2
2
 
3
+ ## 1.12.2
4
+
5
+ ### Patch Changes
6
+
7
+ - ad912db: Fix broken bible reader when auth is disabled.
8
+ - Updated dependencies [ad912db]
9
+ - @youversion/platform-core@1.12.2
10
+
11
+ ## 1.12.1
12
+
13
+ ### Patch Changes
14
+
15
+ - 165feca: Fix user settings from localStorage not loading in the bible reader
16
+ - Updated dependencies [165feca]
17
+ - @youversion/platform-core@1.12.1
18
+
3
19
  ## 1.12.0
4
20
 
5
21
  ### Minor Changes
@@ -3,6 +3,7 @@ type YouVersionContextData = {
3
3
  apiHost?: string;
4
4
  installationId?: string;
5
5
  theme?: 'light' | 'dark';
6
+ authEnabled?: boolean;
6
7
  };
7
8
  export declare const YouVersionContext: import("react").Context<YouVersionContextData | null>;
8
9
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"YouVersionContext.d.ts","sourceRoot":"","sources":["../../src/context/YouVersionContext.tsx"],"names":[],"mappings":"AAIA,KAAK,qBAAqB,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,iBAAiB,uDAAoD,CAAC"}
1
+ {"version":3,"file":"YouVersionContext.d.ts","sourceRoot":"","sources":["../../src/context/YouVersionContext.tsx"],"names":[],"mappings":"AAIA,KAAK,qBAAqB,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,eAAO,MAAM,iBAAiB,uDAAoD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"YouVersionContext.js","sourceRoot":"","sources":["../../src/context/YouVersionContext.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAStC,MAAM,CAAC,MAAM,iBAAiB,GAAG,aAAa,CAA+B,IAAI,CAAC,CAAC"}
1
+ {"version":3,"file":"YouVersionContext.js","sourceRoot":"","sources":["../../src/context/YouVersionContext.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAUtC,MAAM,CAAC,MAAM,iBAAiB,GAAG,aAAa,CAA+B,IAAI,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"YouVersionProvider.d.ts","sourceRoot":"","sources":["../../src/context/YouVersionProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAK1D,UAAU,2BAA2B;IACnC,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B;AAED,UAAU,+BAAgC,SAAQ,2BAA2B;IAC3E,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,IAAI,CAAC;CACnB;AAED,UAAU,kCAAmC,SAAQ,2BAA2B;IAC9E,WAAW,CAAC,EAAE,KAAK,CAAC;IACpB,eAAe,CAAC,EAAE,KAAK,CAAC;CACzB;AAID,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,iBAAiB,CAAC,+BAA+B,GAAG,kCAAkC,CAAC,GAC7F,KAAK,CAAC,YAAY,CA+CpB"}
1
+ {"version":3,"file":"YouVersionProvider.d.ts","sourceRoot":"","sources":["../../src/context/YouVersionProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAK1D,UAAU,2BAA2B;IACnC,QAAQ,EAAE,SAAS,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B;AAED,UAAU,+BAAgC,SAAQ,2BAA2B;IAC3E,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,IAAI,CAAC;CACnB;AAED,UAAU,kCAAmC,SAAQ,2BAA2B;IAC9E,WAAW,CAAC,EAAE,KAAK,CAAC;IACpB,eAAe,CAAC,EAAE,KAAK,CAAC;CACzB;AAID,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,iBAAiB,CAAC,+BAA+B,GAAG,kCAAkC,CAAC,GAC7F,KAAK,CAAC,YAAY,CAiDpB"}
@@ -22,6 +22,7 @@ export function YouVersionProvider(props) {
22
22
  apiHost,
23
23
  installationId: YouVersionPlatformConfiguration.installationId,
24
24
  theme,
25
+ authEnabled: !!includeAuth,
25
26
  }, children: _jsx(Suspense, { children: _jsx(AuthProvider, { config: { appKey, apiHost, redirectUri: authRedirectUrl }, children: children }) }) }));
26
27
  }
27
28
  // Installation ID gets set automatically by YouVersionPlatformConfiguration
@@ -30,6 +31,7 @@ export function YouVersionProvider(props) {
30
31
  apiHost,
31
32
  installationId: YouVersionPlatformConfiguration.installationId,
32
33
  theme,
34
+ authEnabled: !!includeAuth,
33
35
  }, children: children }));
34
36
  }
35
37
  //# sourceMappingURL=YouVersionProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"YouVersionProvider.js","sourceRoot":"","sources":["../../src/context/YouVersionProvider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAGb,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,+BAA+B,EAAE,MAAM,2BAA2B,CAAC;AAmB5E,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,0BAA0B,CAAC,CAAC,CAAC;AAEpE,MAAM,UAAU,kBAAkB,CAChC,KAA8F;IAE9F,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,oBAAoB,EAAE,WAAW,EAAE,KAAK,GAAG,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAEjG,gEAAgE;IAChE,4DAA4D;IAC5D,6DAA6D;IAC7D,wCAAwC;IACxC,SAAS,CAAC,GAAG,EAAE;QACb,+BAA+B,CAAC,MAAM,GAAG,MAAM,CAAC;QAChD,+BAA+B,CAAC,OAAO,GAAG,OAAO,CAAC;IACpD,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAEtB,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,EAAE,eAAe,EAAE,GAAG,KAAK,CAAC;QAElC,4EAA4E;QAC5E,OAAO,CACL,KAAC,iBAAiB,CAAC,QAAQ,IACzB,KAAK,EAAE;gBACL,MAAM;gBACN,OAAO;gBACP,cAAc,EAAE,+BAA+B,CAAC,cAAc;gBAC9D,KAAK;aACN,YAED,KAAC,QAAQ,cACP,KAAC,YAAY,IAAC,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,YACpE,QAAQ,GACI,GACN,GACgB,CAC9B,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,OAAO,CACL,KAAC,iBAAiB,CAAC,QAAQ,IACzB,KAAK,EAAE;YACL,MAAM;YACN,OAAO;YACP,cAAc,EAAE,+BAA+B,CAAC,cAAc;YAC9D,KAAK;SACN,YAEA,QAAQ,GACkB,CAC9B,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"YouVersionProvider.js","sourceRoot":"","sources":["../../src/context/YouVersionProvider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAGb,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,+BAA+B,EAAE,MAAM,2BAA2B,CAAC;AAmB5E,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,0BAA0B,CAAC,CAAC,CAAC;AAEpE,MAAM,UAAU,kBAAkB,CAChC,KAA8F;IAE9F,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,oBAAoB,EAAE,WAAW,EAAE,KAAK,GAAG,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAEjG,gEAAgE;IAChE,4DAA4D;IAC5D,6DAA6D;IAC7D,wCAAwC;IACxC,SAAS,CAAC,GAAG,EAAE;QACb,+BAA+B,CAAC,MAAM,GAAG,MAAM,CAAC;QAChD,+BAA+B,CAAC,OAAO,GAAG,OAAO,CAAC;IACpD,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAEtB,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,EAAE,eAAe,EAAE,GAAG,KAAK,CAAC;QAElC,4EAA4E;QAC5E,OAAO,CACL,KAAC,iBAAiB,CAAC,QAAQ,IACzB,KAAK,EAAE;gBACL,MAAM;gBACN,OAAO;gBACP,cAAc,EAAE,+BAA+B,CAAC,cAAc;gBAC9D,KAAK;gBACL,WAAW,EAAE,CAAC,CAAC,WAAW;aAC3B,YAED,KAAC,QAAQ,cACP,KAAC,YAAY,IAAC,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,YACpE,QAAQ,GACI,GACN,GACgB,CAC9B,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,OAAO,CACL,KAAC,iBAAiB,CAAC,QAAQ,IACzB,KAAK,EAAE;YACL,MAAM;YACN,OAAO;YACP,cAAc,EAAE,+BAA+B,CAAC,cAAc;YAC9D,KAAK;YACL,WAAW,EAAE,CAAC,CAAC,WAAW;SAC3B,YAEA,QAAQ,GACkB,CAC9B,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youversion/platform-react-hooks",
3
- "version": "1.12.0",
3
+ "version": "1.12.2",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -22,7 +22,7 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@youversion/platform-core": "1.12.0"
25
+ "@youversion/platform-core": "1.12.2"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "react": ">=19.1.0 <20.0.0"
@@ -7,6 +7,7 @@ type YouVersionContextData = {
7
7
  apiHost?: string;
8
8
  installationId?: string;
9
9
  theme?: 'light' | 'dark';
10
+ authEnabled?: boolean;
10
11
  };
11
12
 
12
13
  export const YouVersionContext = createContext<YouVersionContextData | null>(null);
@@ -49,6 +49,7 @@ export function YouVersionProvider(
49
49
  apiHost,
50
50
  installationId: YouVersionPlatformConfiguration.installationId,
51
51
  theme,
52
+ authEnabled: !!includeAuth,
52
53
  }}
53
54
  >
54
55
  <Suspense>
@@ -68,6 +69,7 @@ export function YouVersionProvider(
68
69
  apiHost,
69
70
  installationId: YouVersionPlatformConfiguration.installationId,
70
71
  theme,
72
+ authEnabled: !!includeAuth,
71
73
  }}
72
74
  >
73
75
  {children}
@@ -0,0 +1,228 @@
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 { useVerse } from './useVerse';
5
+ import { YouVersionContext } from './context';
6
+ import { type BibleClient, type BibleVerse } from '@youversion/platform-core';
7
+ import { useBibleClient } from './useBibleClient';
8
+
9
+ vi.mock('./useBibleClient');
10
+
11
+ describe('useVerse', () => {
12
+ const mockAppKey = 'test-app-key';
13
+ const mockGetVerse = vi.fn();
14
+
15
+ const mockVerse: BibleVerse = {
16
+ id: '1',
17
+ passage_id: 'MAT.1.1',
18
+ title: '1',
19
+ };
20
+
21
+ const createWrapper = (contextValue: { appKey: string }) => {
22
+ return ({ children }: { children: ReactNode }) => (
23
+ <YouVersionContext.Provider value={contextValue}>{children}</YouVersionContext.Provider>
24
+ );
25
+ };
26
+
27
+ beforeEach(() => {
28
+ vi.resetAllMocks();
29
+
30
+ mockGetVerse.mockResolvedValue(mockVerse);
31
+
32
+ const mockClient: Partial<BibleClient> = { getVerse: mockGetVerse };
33
+ vi.mocked(useBibleClient).mockReturnValue(mockClient as BibleClient);
34
+ });
35
+
36
+ describe('fetching verse', () => {
37
+ it('should fetch verse with all 4 parameters', async () => {
38
+ const wrapper = createWrapper({
39
+ appKey: mockAppKey,
40
+ });
41
+
42
+ const { result } = renderHook(() => useVerse(111, 'MAT', 1, 1), { wrapper });
43
+
44
+ expect(result.current.loading).toBe(true);
45
+ expect(result.current.verse).toBe(null);
46
+
47
+ await waitFor(() => {
48
+ expect(result.current.loading).toBe(false);
49
+ });
50
+
51
+ expect(mockGetVerse).toHaveBeenCalledWith(111, 'MAT', 1, 1);
52
+ expect(result.current.verse).toEqual(mockVerse);
53
+ });
54
+
55
+ it('should refetch when versionId changes', async () => {
56
+ const wrapper = createWrapper({
57
+ appKey: mockAppKey,
58
+ });
59
+
60
+ const { result, rerender } = renderHook(({ versionId }) => useVerse(versionId, 'MAT', 1, 1), {
61
+ wrapper,
62
+ initialProps: { versionId: 1 },
63
+ });
64
+
65
+ await waitFor(() => {
66
+ expect(result.current.loading).toBe(false);
67
+ });
68
+
69
+ expect(mockGetVerse).toHaveBeenCalledTimes(1);
70
+ expect(mockGetVerse).toHaveBeenLastCalledWith(1, 'MAT', 1, 1);
71
+
72
+ act(() => {
73
+ rerender({ versionId: 111 });
74
+ });
75
+
76
+ await waitFor(() => {
77
+ expect(result.current.loading).toBe(false);
78
+ });
79
+
80
+ expect(mockGetVerse).toHaveBeenCalledTimes(2);
81
+ expect(mockGetVerse).toHaveBeenLastCalledWith(111, 'MAT', 1, 1);
82
+ });
83
+
84
+ it('should refetch when book changes', async () => {
85
+ const wrapper = createWrapper({
86
+ appKey: mockAppKey,
87
+ });
88
+
89
+ const { result, rerender } = renderHook(({ book }) => useVerse(1, book, 1, 1), {
90
+ wrapper,
91
+ initialProps: { book: 'MAT' },
92
+ });
93
+
94
+ await waitFor(() => {
95
+ expect(result.current.loading).toBe(false);
96
+ });
97
+
98
+ expect(mockGetVerse).toHaveBeenCalledTimes(1);
99
+ expect(mockGetVerse).toHaveBeenLastCalledWith(1, 'MAT', 1, 1);
100
+
101
+ act(() => {
102
+ rerender({ book: 'GEN' });
103
+ });
104
+
105
+ await waitFor(() => {
106
+ expect(result.current.loading).toBe(false);
107
+ });
108
+
109
+ expect(mockGetVerse).toHaveBeenCalledTimes(2);
110
+ expect(mockGetVerse).toHaveBeenLastCalledWith(1, 'GEN', 1, 1);
111
+ });
112
+
113
+ it('should refetch when chapter changes', async () => {
114
+ const wrapper = createWrapper({
115
+ appKey: mockAppKey,
116
+ });
117
+
118
+ const { result, rerender } = renderHook(({ chapter }) => useVerse(1, 'MAT', chapter, 1), {
119
+ wrapper,
120
+ initialProps: { chapter: 1 },
121
+ });
122
+
123
+ await waitFor(() => {
124
+ expect(result.current.loading).toBe(false);
125
+ });
126
+
127
+ expect(mockGetVerse).toHaveBeenCalledTimes(1);
128
+ expect(mockGetVerse).toHaveBeenLastCalledWith(1, 'MAT', 1, 1);
129
+
130
+ act(() => {
131
+ rerender({ chapter: 5 });
132
+ });
133
+
134
+ await waitFor(() => {
135
+ expect(result.current.loading).toBe(false);
136
+ });
137
+
138
+ expect(mockGetVerse).toHaveBeenCalledTimes(2);
139
+ expect(mockGetVerse).toHaveBeenLastCalledWith(1, 'MAT', 5, 1);
140
+ });
141
+
142
+ it('should refetch when verse changes', async () => {
143
+ const wrapper = createWrapper({
144
+ appKey: mockAppKey,
145
+ });
146
+
147
+ const { result, rerender } = renderHook(({ verse }) => useVerse(1, 'MAT', 1, verse), {
148
+ wrapper,
149
+ initialProps: { verse: 1 },
150
+ });
151
+
152
+ await waitFor(() => {
153
+ expect(result.current.loading).toBe(false);
154
+ });
155
+
156
+ expect(mockGetVerse).toHaveBeenCalledTimes(1);
157
+ expect(mockGetVerse).toHaveBeenLastCalledWith(1, 'MAT', 1, 1);
158
+
159
+ act(() => {
160
+ rerender({ verse: 10 });
161
+ });
162
+
163
+ await waitFor(() => {
164
+ expect(result.current.loading).toBe(false);
165
+ });
166
+
167
+ expect(mockGetVerse).toHaveBeenCalledTimes(2);
168
+ expect(mockGetVerse).toHaveBeenLastCalledWith(1, 'MAT', 1, 10);
169
+ });
170
+
171
+ it('should not fetch when enabled is false', async () => {
172
+ const wrapper = createWrapper({
173
+ appKey: mockAppKey,
174
+ });
175
+
176
+ const { result } = renderHook(() => useVerse(1, 'MAT', 1, 1, { enabled: false }), {
177
+ wrapper,
178
+ });
179
+
180
+ await waitFor(() => {
181
+ expect(result.current.loading).toBe(false);
182
+ });
183
+
184
+ expect(mockGetVerse).not.toHaveBeenCalled();
185
+ expect(result.current.verse).toBe(null);
186
+ });
187
+
188
+ it('should handle fetch errors', async () => {
189
+ const error = new Error('Failed to fetch verse');
190
+ mockGetVerse.mockRejectedValueOnce(error);
191
+
192
+ const wrapper = createWrapper({
193
+ appKey: mockAppKey,
194
+ });
195
+
196
+ const { result } = renderHook(() => useVerse(1, 'MAT', 1, 1), { wrapper });
197
+
198
+ await waitFor(() => {
199
+ expect(result.current.loading).toBe(false);
200
+ });
201
+
202
+ expect(result.current.error).toEqual(error);
203
+ expect(result.current.verse).toBe(null);
204
+ });
205
+
206
+ it('should support manual refetch', async () => {
207
+ const wrapper = createWrapper({
208
+ appKey: mockAppKey,
209
+ });
210
+
211
+ const { result } = renderHook(() => useVerse(1, 'MAT', 1, 1), { wrapper });
212
+
213
+ await waitFor(() => {
214
+ expect(result.current.loading).toBe(false);
215
+ });
216
+
217
+ expect(mockGetVerse).toHaveBeenCalledTimes(1);
218
+
219
+ act(() => {
220
+ result.current.refetch();
221
+ });
222
+
223
+ await waitFor(() => {
224
+ expect(mockGetVerse).toHaveBeenCalledTimes(2);
225
+ });
226
+ });
227
+ });
228
+ });
@@ -0,0 +1,33 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { renderHook } from '@testing-library/react';
3
+ import type { ReactNode } from 'react';
4
+ import { useVerseSelection } from './useVerseSelection';
5
+ import { VerseSelectionProvider } from './context/VerseSelectionProvider';
6
+
7
+ // Wrapper for renderHook
8
+ const wrapper = ({ children }: { children: ReactNode }) => (
9
+ <VerseSelectionProvider>{children}</VerseSelectionProvider>
10
+ );
11
+
12
+ describe('useVerseSelection', () => {
13
+ it('should throw error when used outside provider', () => {
14
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(vi.fn());
15
+
16
+ expect(() => {
17
+ renderHook(() => useVerseSelection());
18
+ }).toThrow('useVerseSelection must be used within a VerseSelectionProvider');
19
+
20
+ consoleSpy.mockRestore();
21
+ });
22
+
23
+ it('should return expected shape with correct initial values', () => {
24
+ const { result } = renderHook(() => useVerseSelection(), { wrapper });
25
+
26
+ expect(result.current.selectedVerseUsfms).toBeInstanceOf(Set);
27
+ expect(result.current.selectedVerseUsfms.size).toBe(0);
28
+ expect(result.current.selectedCount).toBe(0);
29
+ expect(typeof result.current.toggleVerse).toBe('function');
30
+ expect(typeof result.current.isSelected).toBe('function');
31
+ expect(typeof result.current.clearSelection).toBe('function');
32
+ });
33
+ });
@@ -0,0 +1,202 @@
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 { useVerses } from './useVerses';
5
+ import { YouVersionContext } from './context';
6
+ import { type BibleClient, type BibleVerse, type Collection } from '@youversion/platform-core';
7
+ import { useBibleClient } from './useBibleClient';
8
+
9
+ vi.mock('./useBibleClient');
10
+
11
+ describe('useVerses', () => {
12
+ const mockAppKey = 'test-app-key';
13
+ const mockGetVerses = vi.fn();
14
+
15
+ const mockVerses: Collection<BibleVerse> = {
16
+ data: [
17
+ { id: '1', passage_id: 'MAT.1.1', title: '1' },
18
+ { id: '2', passage_id: 'MAT.1.2', title: '2' },
19
+ { id: '3', passage_id: 'MAT.1.3', title: '3' },
20
+ ],
21
+ next_page_token: null,
22
+ };
23
+
24
+ const createWrapper = (contextValue: { appKey: string }) => {
25
+ return ({ children }: { children: ReactNode }) => (
26
+ <YouVersionContext.Provider value={contextValue}>{children}</YouVersionContext.Provider>
27
+ );
28
+ };
29
+
30
+ beforeEach(() => {
31
+ vi.resetAllMocks();
32
+
33
+ mockGetVerses.mockResolvedValue(mockVerses);
34
+
35
+ const mockClient: Partial<BibleClient> = { getVerses: mockGetVerses };
36
+ vi.mocked(useBibleClient).mockReturnValue(mockClient as BibleClient);
37
+ });
38
+
39
+ describe('fetching verses', () => {
40
+ it('should fetch verses with all 3 parameters', async () => {
41
+ const wrapper = createWrapper({
42
+ appKey: mockAppKey,
43
+ });
44
+
45
+ const { result } = renderHook(() => useVerses(111, 'MAT', 1), { wrapper });
46
+
47
+ expect(result.current.loading).toBe(true);
48
+ expect(result.current.verses).toBe(null);
49
+
50
+ await waitFor(() => {
51
+ expect(result.current.loading).toBe(false);
52
+ });
53
+
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);
114
+ });
115
+
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), {
122
+ wrapper,
123
+ initialProps: { chapter: 1 },
124
+ });
125
+
126
+ await waitFor(() => {
127
+ expect(result.current.loading).toBe(false);
128
+ });
129
+
130
+ expect(mockGetVerses).toHaveBeenCalledTimes(1);
131
+ expect(mockGetVerses).toHaveBeenLastCalledWith(1, 'MAT', 1);
132
+
133
+ act(() => {
134
+ rerender({ chapter: 5 });
135
+ });
136
+
137
+ await waitFor(() => {
138
+ expect(result.current.loading).toBe(false);
139
+ });
140
+
141
+ expect(mockGetVerses).toHaveBeenCalledTimes(2);
142
+ expect(mockGetVerses).toHaveBeenLastCalledWith(1, 'MAT', 5);
143
+ });
144
+
145
+ it('should not fetch when enabled is false', async () => {
146
+ const wrapper = createWrapper({
147
+ appKey: mockAppKey,
148
+ });
149
+
150
+ const { result } = renderHook(() => useVerses(1, 'MAT', 1, { enabled: false }), {
151
+ wrapper,
152
+ });
153
+
154
+ await waitFor(() => {
155
+ expect(result.current.loading).toBe(false);
156
+ });
157
+
158
+ expect(mockGetVerses).not.toHaveBeenCalled();
159
+ expect(result.current.verses).toBe(null);
160
+ });
161
+
162
+ it('should handle fetch errors', async () => {
163
+ const error = new Error('Failed to fetch verses');
164
+ mockGetVerses.mockRejectedValueOnce(error);
165
+
166
+ const wrapper = createWrapper({
167
+ appKey: mockAppKey,
168
+ });
169
+
170
+ const { result } = renderHook(() => useVerses(1, 'MAT', 1), { wrapper });
171
+
172
+ await waitFor(() => {
173
+ expect(result.current.loading).toBe(false);
174
+ });
175
+
176
+ expect(result.current.error).toEqual(error);
177
+ expect(result.current.verses).toBe(null);
178
+ });
179
+
180
+ it('should support manual refetch', async () => {
181
+ const wrapper = createWrapper({
182
+ appKey: mockAppKey,
183
+ });
184
+
185
+ const { result } = renderHook(() => useVerses(1, 'MAT', 1), { wrapper });
186
+
187
+ await waitFor(() => {
188
+ expect(result.current.loading).toBe(false);
189
+ });
190
+
191
+ expect(mockGetVerses).toHaveBeenCalledTimes(1);
192
+
193
+ act(() => {
194
+ result.current.refetch();
195
+ });
196
+
197
+ await waitFor(() => {
198
+ expect(mockGetVerses).toHaveBeenCalledTimes(2);
199
+ });
200
+ });
201
+ });
202
+ });