@youversion/platform-react-hooks 0.4.1
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 +4 -0
- package/CHANGELOG.md +9 -0
- package/LICENSE +201 -0
- package/dist/context/BibleSDKContext.d.ts +6 -0
- package/dist/context/BibleSDKContext.d.ts.map +1 -0
- package/dist/context/BibleSDKContext.js +4 -0
- package/dist/context/BibleSDKContext.js.map +1 -0
- package/dist/context/BibleSDKProvider.d.ts +8 -0
- package/dist/context/BibleSDKProvider.d.ts.map +1 -0
- package/dist/context/BibleSDKProvider.js +7 -0
- package/dist/context/BibleSDKProvider.js.map +1 -0
- package/dist/context/ReaderContext.d.ts +15 -0
- package/dist/context/ReaderContext.d.ts.map +1 -0
- package/dist/context/ReaderContext.js +11 -0
- package/dist/context/ReaderContext.js.map +1 -0
- package/dist/context/ReaderProvider.d.ts +11 -0
- package/dist/context/ReaderProvider.d.ts.map +1 -0
- package/dist/context/ReaderProvider.js +21 -0
- package/dist/context/ReaderProvider.js.map +1 -0
- package/dist/context/VerseSelectionContext.d.ts +9 -0
- package/dist/context/VerseSelectionContext.d.ts.map +1 -0
- package/dist/context/VerseSelectionContext.js +3 -0
- package/dist/context/VerseSelectionContext.js.map +1 -0
- package/dist/context/VerseSelectionProvider.d.ts +3 -0
- package/dist/context/VerseSelectionProvider.d.ts.map +1 -0
- package/dist/context/VerseSelectionProvider.js +33 -0
- package/dist/context/VerseSelectionProvider.js.map +1 -0
- package/dist/context/index.d.ts +7 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +7 -0
- package/dist/context/index.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/test/setup.d.ts +2 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/test/setup.js +9 -0
- package/dist/test/setup.js.map +1 -0
- package/dist/useApiData.d.ts +12 -0
- package/dist/useApiData.d.ts.map +1 -0
- package/dist/useApiData.js +46 -0
- package/dist/useApiData.js.map +1 -0
- package/dist/useBibleClient.d.ts +3 -0
- package/dist/useBibleClient.d.ts.map +1 -0
- package/dist/useBibleClient.js +17 -0
- package/dist/useBibleClient.js.map +1 -0
- package/dist/useBook.d.ts +9 -0
- package/dist/useBook.d.ts.map +1 -0
- package/dist/useBook.js +11 -0
- package/dist/useBook.js.map +1 -0
- package/dist/useBooks.d.ts +9 -0
- package/dist/useBooks.d.ts.map +1 -0
- package/dist/useBooks.js +11 -0
- package/dist/useBooks.js.map +1 -0
- package/dist/useChapter.d.ts +9 -0
- package/dist/useChapter.d.ts.map +1 -0
- package/dist/useChapter.js +11 -0
- package/dist/useChapter.js.map +1 -0
- package/dist/useChapterNavigation.d.ts +23 -0
- package/dist/useChapterNavigation.d.ts.map +1 -0
- package/dist/useChapterNavigation.js +52 -0
- package/dist/useChapterNavigation.js.map +1 -0
- package/dist/useChapters.d.ts +9 -0
- package/dist/useChapters.d.ts.map +1 -0
- package/dist/useChapters.js +13 -0
- package/dist/useChapters.js.map +1 -0
- package/dist/useFilteredVersions.d.ts +6 -0
- package/dist/useFilteredVersions.d.ts.map +1 -0
- package/dist/useFilteredVersions.js +24 -0
- package/dist/useFilteredVersions.js.map +1 -0
- package/dist/useHighlights.d.ts +11 -0
- package/dist/useHighlights.d.ts.map +1 -0
- package/dist/useHighlights.js +40 -0
- package/dist/useHighlights.js.map +1 -0
- package/dist/useInitData.d.ts +23 -0
- package/dist/useInitData.d.ts.map +1 -0
- package/dist/useInitData.js +24 -0
- package/dist/useInitData.js.map +1 -0
- package/dist/useLanguages.d.ts +9 -0
- package/dist/useLanguages.d.ts.map +1 -0
- package/dist/useLanguages.js +29 -0
- package/dist/useLanguages.js.map +1 -0
- package/dist/usePassage.d.ts +18 -0
- package/dist/usePassage.d.ts.map +1 -0
- package/dist/usePassage.js +11 -0
- package/dist/usePassage.js.map +1 -0
- package/dist/useVOTD.d.ts +9 -0
- package/dist/useVOTD.d.ts.map +1 -0
- package/dist/useVOTD.js +9 -0
- package/dist/useVOTD.js.map +1 -0
- package/dist/useVerse.d.ts +9 -0
- package/dist/useVerse.d.ts.map +1 -0
- package/dist/useVerse.js +11 -0
- package/dist/useVerse.js.map +1 -0
- package/dist/useVerseSelection.d.ts +3 -0
- package/dist/useVerseSelection.d.ts.map +1 -0
- package/dist/useVerseSelection.js +10 -0
- package/dist/useVerseSelection.js.map +1 -0
- package/dist/useVerses.d.ts +9 -0
- package/dist/useVerses.d.ts.map +1 -0
- package/dist/useVerses.js +11 -0
- package/dist/useVerses.js.map +1 -0
- package/dist/useVersion.d.ts +9 -0
- package/dist/useVersion.d.ts.map +1 -0
- package/dist/useVersion.js +11 -0
- package/dist/useVersion.js.map +1 -0
- package/dist/useVersions.d.ts +9 -0
- package/dist/useVersions.d.ts.map +1 -0
- package/dist/useVersions.js +11 -0
- package/dist/useVersions.js.map +1 -0
- package/dist/utility/extractTextFromHTML.d.ts +9 -0
- package/dist/utility/extractTextFromHTML.d.ts.map +1 -0
- package/dist/utility/extractTextFromHTML.js +21 -0
- package/dist/utility/extractTextFromHTML.js.map +1 -0
- package/dist/utility/extractVersesFromHTML.d.ts +9 -0
- package/dist/utility/extractVersesFromHTML.d.ts.map +1 -0
- package/dist/utility/extractVersesFromHTML.js +26 -0
- package/dist/utility/extractVersesFromHTML.js.map +1 -0
- package/dist/utility/getDayOfYear.d.ts +2 -0
- package/dist/utility/getDayOfYear.d.ts.map +1 -0
- package/dist/utility/getDayOfYear.js +5 -0
- package/dist/utility/getDayOfYear.js.map +1 -0
- package/dist/utility/index.d.ts +6 -0
- package/dist/utility/index.d.ts.map +1 -0
- package/dist/utility/index.js +6 -0
- package/dist/utility/index.js.map +1 -0
- package/dist/utility/useDebounce.d.ts +14 -0
- package/dist/utility/useDebounce.d.ts.map +1 -0
- package/dist/utility/useDebounce.js +24 -0
- package/dist/utility/useDebounce.js.map +1 -0
- package/dist/utility/version.d.ts +3 -0
- package/dist/utility/version.d.ts.map +1 -0
- package/dist/utility/version.js +4 -0
- package/dist/utility/version.js.map +1 -0
- package/package.json +50 -0
- package/src/context/BibleSDKContext.tsx +9 -0
- package/src/context/BibleSDKProvider.tsx +16 -0
- package/src/context/ReaderContext.tsx +27 -0
- package/src/context/ReaderProvider.tsx +36 -0
- package/src/context/VerseSelectionContext.tsx +11 -0
- package/src/context/VerseSelectionProvider.tsx +39 -0
- package/src/context/index.ts +6 -0
- package/src/index.ts +20 -0
- package/src/test/setup.ts +9 -0
- package/src/useApiData.ts +71 -0
- package/src/useBibleClient.test.tsx +151 -0
- package/src/useBibleClient.ts +22 -0
- package/src/useBook.ts +28 -0
- package/src/useBooks.ts +31 -0
- package/src/useChapter.ts +33 -0
- package/src/useChapterNavigation.ts +77 -0
- package/src/useChapters.ts +36 -0
- package/src/useFilteredVersions.test.tsx +248 -0
- package/src/useFilteredVersions.ts +38 -0
- package/src/useHighlights.test.tsx +448 -0
- package/src/useHighlights.ts +80 -0
- package/src/useInitData.ts +54 -0
- package/src/useLanguages.test.tsx +296 -0
- package/src/useLanguages.ts +57 -0
- package/src/usePassage.ts +48 -0
- package/src/useVOTD.test.tsx +269 -0
- package/src/useVOTD.ts +19 -0
- package/src/useVerse.ts +35 -0
- package/src/useVerseSelection.ts +13 -0
- package/src/useVerses.ts +34 -0
- package/src/useVersion.ts +28 -0
- package/src/useVersions.ts +33 -0
- package/src/utility/extractTextFromHTML.test.ts +112 -0
- package/src/utility/extractTextFromHTML.ts +22 -0
- package/src/utility/extractVersesFromHTML.test.ts +186 -0
- package/src/utility/extractVersesFromHTML.ts +31 -0
- package/src/utility/getDayOfYear.ts +6 -0
- package/src/utility/index.ts +5 -0
- package/src/utility/useDebounce.test.tsx +95 -0
- package/src/utility/useDebounce.ts +27 -0
- package/src/utility/version.ts +5 -0
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +13 -0
- package/vitest.config.ts +11 -0
- package/vitest.setup.ts +1 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
import { useHighlights } from './useHighlights';
|
|
5
|
+
import { BibleSDKContext } from './context';
|
|
6
|
+
import {
|
|
7
|
+
HighlightsClient,
|
|
8
|
+
ApiClient,
|
|
9
|
+
type Collection,
|
|
10
|
+
type Highlight,
|
|
11
|
+
type CreateHighlight,
|
|
12
|
+
} from '@youversion/platform-core';
|
|
13
|
+
|
|
14
|
+
// Mock the core package
|
|
15
|
+
vi.mock('@youversion/platform-core', async () => {
|
|
16
|
+
const actual = await vi.importActual('@youversion/platform-core');
|
|
17
|
+
return {
|
|
18
|
+
...actual,
|
|
19
|
+
HighlightsClient: vi.fn(function () {
|
|
20
|
+
return {};
|
|
21
|
+
}),
|
|
22
|
+
ApiClient: vi.fn(function () {
|
|
23
|
+
return { isApiClient: true };
|
|
24
|
+
}),
|
|
25
|
+
YouVersionPlatformConfiguration: {
|
|
26
|
+
get installationId() {
|
|
27
|
+
return 'auto-generated-uuid';
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('useHighlights', () => {
|
|
34
|
+
const mockAppId = 'test-app-id';
|
|
35
|
+
|
|
36
|
+
const mockHighlights: Collection<Highlight> = {
|
|
37
|
+
data: [
|
|
38
|
+
{
|
|
39
|
+
version_id: 111,
|
|
40
|
+
passage_id: 'MAT.1.1',
|
|
41
|
+
color: 'fffe00',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
version_id: 111,
|
|
45
|
+
passage_id: 'MAT.1.2',
|
|
46
|
+
color: '5dff79',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
next_page_token: null,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const mockHighlight: Highlight = {
|
|
53
|
+
version_id: 111,
|
|
54
|
+
passage_id: 'MAT.1.1',
|
|
55
|
+
color: 'fffe00',
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
let mockGetHighlights: Mock;
|
|
59
|
+
let mockCreateHighlight: Mock;
|
|
60
|
+
let mockDeleteHighlight: Mock;
|
|
61
|
+
|
|
62
|
+
const createWrapper = (contextValue: { appId: string }) => {
|
|
63
|
+
return ({ children }: { children: ReactNode }) => (
|
|
64
|
+
<BibleSDKContext.Provider value={contextValue}>{children}</BibleSDKContext.Provider>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
vi.clearAllMocks();
|
|
70
|
+
|
|
71
|
+
mockGetHighlights = vi.fn().mockResolvedValue(mockHighlights);
|
|
72
|
+
mockCreateHighlight = vi.fn().mockResolvedValue(mockHighlight);
|
|
73
|
+
mockDeleteHighlight = vi.fn().mockResolvedValue(undefined);
|
|
74
|
+
|
|
75
|
+
(HighlightsClient as unknown as ReturnType<typeof vi.fn>).mockImplementation(function () {
|
|
76
|
+
return {
|
|
77
|
+
getHighlights: mockGetHighlights,
|
|
78
|
+
createHighlight: mockCreateHighlight,
|
|
79
|
+
deleteHighlight: mockDeleteHighlight,
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
(ApiClient as unknown as ReturnType<typeof vi.fn>).mockImplementation(function () {
|
|
84
|
+
return {
|
|
85
|
+
isApiClient: true,
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('context validation', () => {
|
|
91
|
+
it('should throw error when context is not provided', () => {
|
|
92
|
+
expect(() => renderHook(() => useHighlights())).toThrow(
|
|
93
|
+
'BibleSDK context not found. Make sure your component is wrapped with BibleSDKProvider and an API key is provided.',
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should throw error when appId is missing', () => {
|
|
98
|
+
const wrapper = createWrapper({
|
|
99
|
+
appId: '',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expect(() => renderHook(() => useHighlights(), { wrapper })).toThrow(
|
|
103
|
+
'BibleSDK context not found. Make sure your component is wrapped with BibleSDKProvider and an API key is provided.',
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('client creation', () => {
|
|
109
|
+
it('should create HighlightsClient with correct ApiClient config', () => {
|
|
110
|
+
const wrapper = createWrapper({
|
|
111
|
+
appId: mockAppId,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
renderHook(() => useHighlights(), { wrapper });
|
|
115
|
+
|
|
116
|
+
expect(ApiClient).toHaveBeenCalledWith({
|
|
117
|
+
appId: mockAppId,
|
|
118
|
+
installationId: 'auto-generated-uuid',
|
|
119
|
+
});
|
|
120
|
+
expect(HighlightsClient).toHaveBeenCalledWith(expect.objectContaining({ isApiClient: true }));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should memoize HighlightsClient instance', () => {
|
|
124
|
+
const wrapper = createWrapper({
|
|
125
|
+
appId: mockAppId,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const { rerender } = renderHook(() => useHighlights(), { wrapper });
|
|
129
|
+
|
|
130
|
+
rerender();
|
|
131
|
+
|
|
132
|
+
// If client is memoized, refetch should be stable (though useCallback might still create new refs)
|
|
133
|
+
expect(HighlightsClient).toHaveBeenCalledTimes(1);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should create new HighlightsClient when context values change', () => {
|
|
137
|
+
let currentAppId = mockAppId;
|
|
138
|
+
|
|
139
|
+
const wrapper = ({ children }: { children: ReactNode }) => (
|
|
140
|
+
<BibleSDKContext.Provider
|
|
141
|
+
value={{
|
|
142
|
+
appId: currentAppId,
|
|
143
|
+
}}
|
|
144
|
+
>
|
|
145
|
+
{children}
|
|
146
|
+
</BibleSDKContext.Provider>
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const { rerender } = renderHook(() => useHighlights(), { wrapper });
|
|
150
|
+
|
|
151
|
+
expect(HighlightsClient).toHaveBeenCalledTimes(1);
|
|
152
|
+
|
|
153
|
+
currentAppId = 'new-app-id';
|
|
154
|
+
|
|
155
|
+
rerender();
|
|
156
|
+
|
|
157
|
+
expect(HighlightsClient).toHaveBeenCalledTimes(2);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('fetching highlights', () => {
|
|
162
|
+
it('should fetch highlights with no options', async () => {
|
|
163
|
+
const wrapper = createWrapper({
|
|
164
|
+
appId: mockAppId,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const { result } = renderHook(() => useHighlights(), { wrapper });
|
|
168
|
+
|
|
169
|
+
expect(result.current.loading).toBe(true);
|
|
170
|
+
expect(result.current.highlights).toBe(null);
|
|
171
|
+
|
|
172
|
+
await waitFor(() => {
|
|
173
|
+
expect(result.current.loading).toBe(false);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect(mockGetHighlights).toHaveBeenCalledWith(undefined);
|
|
177
|
+
expect(result.current.highlights).toEqual(mockHighlights);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should fetch highlights with version_id option', async () => {
|
|
181
|
+
const wrapper = createWrapper({
|
|
182
|
+
appId: mockAppId,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const { result } = renderHook(() => useHighlights({ version_id: 111 }), { wrapper });
|
|
186
|
+
|
|
187
|
+
await waitFor(() => {
|
|
188
|
+
expect(result.current.loading).toBe(false);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
expect(mockGetHighlights).toHaveBeenCalledWith({ version_id: 111 });
|
|
192
|
+
expect(result.current.highlights).toEqual(mockHighlights);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should fetch highlights with passage_id option', async () => {
|
|
196
|
+
const wrapper = createWrapper({
|
|
197
|
+
appId: mockAppId,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const { result } = renderHook(() => useHighlights({ passage_id: 'MAT.1.1' }), { wrapper });
|
|
201
|
+
|
|
202
|
+
await waitFor(() => {
|
|
203
|
+
expect(result.current.loading).toBe(false);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
expect(mockGetHighlights).toHaveBeenCalledWith({ passage_id: 'MAT.1.1' });
|
|
207
|
+
expect(result.current.highlights).toEqual(mockHighlights);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should fetch highlights with both options', async () => {
|
|
211
|
+
const wrapper = createWrapper({
|
|
212
|
+
appId: mockAppId,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const { result } = renderHook(
|
|
216
|
+
() => useHighlights({ version_id: 111, passage_id: 'MAT.1.1' }),
|
|
217
|
+
{
|
|
218
|
+
wrapper,
|
|
219
|
+
},
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
await waitFor(() => {
|
|
223
|
+
expect(result.current.loading).toBe(false);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(mockGetHighlights).toHaveBeenCalledWith({
|
|
227
|
+
version_id: 111,
|
|
228
|
+
passage_id: 'MAT.1.1',
|
|
229
|
+
});
|
|
230
|
+
expect(result.current.highlights).toEqual(mockHighlights);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should refetch when options change', async () => {
|
|
234
|
+
const wrapper = createWrapper({
|
|
235
|
+
appId: mockAppId,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const { result, rerender } = renderHook(({ options }) => useHighlights(options), {
|
|
239
|
+
wrapper,
|
|
240
|
+
initialProps: { options: { version_id: 111 } },
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
await waitFor(() => {
|
|
244
|
+
expect(result.current.loading).toBe(false);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
expect(mockGetHighlights).toHaveBeenCalledTimes(1);
|
|
248
|
+
|
|
249
|
+
rerender({ options: { version_id: 1 } });
|
|
250
|
+
|
|
251
|
+
await waitFor(() => {
|
|
252
|
+
expect(result.current.loading).toBe(false);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
expect(mockGetHighlights).toHaveBeenCalledTimes(2);
|
|
256
|
+
expect(mockGetHighlights).toHaveBeenLastCalledWith({ version_id: 1 });
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should not fetch when enabled is false', async () => {
|
|
260
|
+
const wrapper = createWrapper({
|
|
261
|
+
appId: mockAppId,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const { result } = renderHook(() => useHighlights(undefined, { enabled: false }), {
|
|
265
|
+
wrapper,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
await waitFor(() => {
|
|
269
|
+
expect(result.current.loading).toBe(false);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
expect(mockGetHighlights).not.toHaveBeenCalled();
|
|
273
|
+
expect(result.current.highlights).toBe(null);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should handle fetch errors', async () => {
|
|
277
|
+
const error = new Error('Failed to fetch highlights');
|
|
278
|
+
mockGetHighlights.mockRejectedValueOnce(error);
|
|
279
|
+
|
|
280
|
+
const wrapper = createWrapper({
|
|
281
|
+
appId: mockAppId,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const { result } = renderHook(() => useHighlights(), { wrapper });
|
|
285
|
+
|
|
286
|
+
await waitFor(() => {
|
|
287
|
+
expect(result.current.loading).toBe(false);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
expect(result.current.error).toEqual(error);
|
|
291
|
+
expect(result.current.highlights).toBe(null);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should support manual refetch', async () => {
|
|
295
|
+
const wrapper = createWrapper({
|
|
296
|
+
appId: mockAppId,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const { result } = renderHook(() => useHighlights(), { wrapper });
|
|
300
|
+
|
|
301
|
+
await waitFor(() => {
|
|
302
|
+
expect(result.current.loading).toBe(false);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
expect(mockGetHighlights).toHaveBeenCalledTimes(1);
|
|
306
|
+
|
|
307
|
+
result.current.refetch();
|
|
308
|
+
|
|
309
|
+
await waitFor(() => {
|
|
310
|
+
expect(mockGetHighlights).toHaveBeenCalledTimes(2);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe('createHighlight mutation', () => {
|
|
316
|
+
it('should create highlight and refetch', async () => {
|
|
317
|
+
const wrapper = createWrapper({
|
|
318
|
+
appId: mockAppId,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const { result } = renderHook(() => useHighlights(), { wrapper });
|
|
322
|
+
|
|
323
|
+
await waitFor(() => {
|
|
324
|
+
expect(result.current.loading).toBe(false);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const createData: CreateHighlight = {
|
|
328
|
+
version_id: 111,
|
|
329
|
+
passage_id: 'MAT.1.1',
|
|
330
|
+
color: 'fffe00',
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const createPromise = result.current.createHighlight(createData);
|
|
334
|
+
|
|
335
|
+
await waitFor(() => {
|
|
336
|
+
expect(mockCreateHighlight).toHaveBeenCalledWith(createData);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const created = await createPromise;
|
|
340
|
+
expect(created).toEqual(mockHighlight);
|
|
341
|
+
|
|
342
|
+
// Should refetch after creation
|
|
343
|
+
await waitFor(() => {
|
|
344
|
+
expect(mockGetHighlights).toHaveBeenCalledTimes(2);
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('should handle create error', async () => {
|
|
349
|
+
const error = new Error('Failed to create highlight');
|
|
350
|
+
mockCreateHighlight.mockRejectedValueOnce(error);
|
|
351
|
+
|
|
352
|
+
const wrapper = createWrapper({
|
|
353
|
+
appId: mockAppId,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const { result } = renderHook(() => useHighlights(), { wrapper });
|
|
357
|
+
|
|
358
|
+
await waitFor(() => {
|
|
359
|
+
expect(result.current.loading).toBe(false);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const createData: CreateHighlight = {
|
|
363
|
+
version_id: 111,
|
|
364
|
+
passage_id: 'MAT.1.1',
|
|
365
|
+
color: 'fffe00',
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
await expect(result.current.createHighlight(createData)).rejects.toThrow(
|
|
369
|
+
'Failed to create highlight',
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
// Should not refetch on error
|
|
373
|
+
expect(mockGetHighlights).toHaveBeenCalledTimes(1);
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
describe('deleteHighlight mutation', () => {
|
|
378
|
+
it('should delete highlight and refetch', async () => {
|
|
379
|
+
const wrapper = createWrapper({
|
|
380
|
+
appId: mockAppId,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
const { result } = renderHook(() => useHighlights(), { wrapper });
|
|
384
|
+
|
|
385
|
+
await waitFor(() => {
|
|
386
|
+
expect(result.current.loading).toBe(false);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const deletePromise = result.current.deleteHighlight('MAT.1.1');
|
|
390
|
+
|
|
391
|
+
await waitFor(() => {
|
|
392
|
+
expect(mockDeleteHighlight).toHaveBeenCalledWith('MAT.1.1', undefined);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
await deletePromise;
|
|
396
|
+
|
|
397
|
+
// Should refetch after deletion
|
|
398
|
+
await waitFor(() => {
|
|
399
|
+
expect(mockGetHighlights).toHaveBeenCalledTimes(2);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('should delete highlight with options', async () => {
|
|
404
|
+
const wrapper = createWrapper({
|
|
405
|
+
appId: mockAppId,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const { result } = renderHook(() => useHighlights(), { wrapper });
|
|
409
|
+
|
|
410
|
+
await waitFor(() => {
|
|
411
|
+
expect(result.current.loading).toBe(false);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
const deletePromise = result.current.deleteHighlight('MAT.1.1', { version_id: 111 });
|
|
415
|
+
|
|
416
|
+
await waitFor(() => {
|
|
417
|
+
expect(mockDeleteHighlight).toHaveBeenCalledWith('MAT.1.1', { version_id: 111 });
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
await deletePromise;
|
|
421
|
+
|
|
422
|
+
// Should refetch after deletion
|
|
423
|
+
expect(mockGetHighlights).toHaveBeenCalledTimes(2);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('should handle delete error', async () => {
|
|
427
|
+
const error = new Error('Failed to delete highlight');
|
|
428
|
+
mockDeleteHighlight.mockRejectedValueOnce(error);
|
|
429
|
+
|
|
430
|
+
const wrapper = createWrapper({
|
|
431
|
+
appId: mockAppId,
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
const { result } = renderHook(() => useHighlights(), { wrapper });
|
|
435
|
+
|
|
436
|
+
await waitFor(() => {
|
|
437
|
+
expect(result.current.loading).toBe(false);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
await expect(result.current.deleteHighlight('MAT.1.1')).rejects.toThrow(
|
|
441
|
+
'Failed to delete highlight',
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
// Should not refetch on error
|
|
445
|
+
expect(mockGetHighlights).toHaveBeenCalledTimes(1);
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useMemo, useCallback } from 'react';
|
|
4
|
+
import { useContext } from 'react';
|
|
5
|
+
import { BibleSDKContext } from './context';
|
|
6
|
+
import {
|
|
7
|
+
HighlightsClient,
|
|
8
|
+
ApiClient,
|
|
9
|
+
YouVersionPlatformConfiguration,
|
|
10
|
+
} from '@youversion/platform-core';
|
|
11
|
+
import { useApiData, type UseApiDataOptions } from './useApiData';
|
|
12
|
+
import {
|
|
13
|
+
type GetHighlightsOptions,
|
|
14
|
+
type DeleteHighlightOptions,
|
|
15
|
+
type CreateHighlight,
|
|
16
|
+
type Collection,
|
|
17
|
+
type Highlight,
|
|
18
|
+
} from '@youversion/platform-core';
|
|
19
|
+
|
|
20
|
+
export function useHighlights(
|
|
21
|
+
options?: GetHighlightsOptions,
|
|
22
|
+
apiOptions?: UseApiDataOptions,
|
|
23
|
+
): {
|
|
24
|
+
highlights: Collection<Highlight> | null;
|
|
25
|
+
loading: boolean;
|
|
26
|
+
error: Error | null;
|
|
27
|
+
refetch: () => void;
|
|
28
|
+
createHighlight: (data: CreateHighlight) => Promise<Highlight>;
|
|
29
|
+
deleteHighlight: (passageId: string, deleteOptions?: DeleteHighlightOptions) => Promise<void>;
|
|
30
|
+
} {
|
|
31
|
+
const context = useContext(BibleSDKContext);
|
|
32
|
+
|
|
33
|
+
const highlightsClient = useMemo(() => {
|
|
34
|
+
if (!context?.appId) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
'BibleSDK context not found. Make sure your component is wrapped with BibleSDKProvider and an API key is provided.',
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return new HighlightsClient(
|
|
40
|
+
new ApiClient({
|
|
41
|
+
appId: context.appId,
|
|
42
|
+
installationId: YouVersionPlatformConfiguration.installationId,
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
}, [context?.appId]);
|
|
46
|
+
|
|
47
|
+
const { data, loading, error, refetch } = useApiData<Collection<Highlight>>(
|
|
48
|
+
() => highlightsClient.getHighlights(options),
|
|
49
|
+
[highlightsClient, options?.version_id, options?.passage_id],
|
|
50
|
+
{
|
|
51
|
+
enabled: apiOptions?.enabled !== false,
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const createHighlight = useCallback(
|
|
56
|
+
async (data: CreateHighlight): Promise<Highlight> => {
|
|
57
|
+
const result = await highlightsClient.createHighlight(data);
|
|
58
|
+
refetch();
|
|
59
|
+
return result;
|
|
60
|
+
},
|
|
61
|
+
[highlightsClient, refetch],
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const deleteHighlight = useCallback(
|
|
65
|
+
async (passageId: string, deleteOptions?: DeleteHighlightOptions): Promise<void> => {
|
|
66
|
+
await highlightsClient.deleteHighlight(passageId, deleteOptions);
|
|
67
|
+
refetch();
|
|
68
|
+
},
|
|
69
|
+
[highlightsClient, refetch],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
highlights: data,
|
|
74
|
+
loading,
|
|
75
|
+
error,
|
|
76
|
+
refetch,
|
|
77
|
+
createHighlight,
|
|
78
|
+
deleteHighlight,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useVersion } from './useVersion';
|
|
2
|
+
import { useBook } from './useBook';
|
|
3
|
+
import { useChapter } from './useChapter';
|
|
4
|
+
import type { BibleChapter, BibleVersion, BibleBook } from '@youversion/platform-core';
|
|
5
|
+
|
|
6
|
+
export const DEFAULT = {
|
|
7
|
+
VERSION: 111,
|
|
8
|
+
BOOK: 'GEN',
|
|
9
|
+
CHAPTER: 1,
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
version: number;
|
|
14
|
+
book: string;
|
|
15
|
+
chapter: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface InitData {
|
|
19
|
+
version: BibleVersion;
|
|
20
|
+
book: BibleBook;
|
|
21
|
+
chapter: BibleChapter;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useInitData(
|
|
25
|
+
{ version, book, chapter }: Props = {
|
|
26
|
+
version: DEFAULT.VERSION,
|
|
27
|
+
book: DEFAULT.BOOK,
|
|
28
|
+
chapter: DEFAULT.CHAPTER,
|
|
29
|
+
},
|
|
30
|
+
): {
|
|
31
|
+
loading: boolean;
|
|
32
|
+
error: string | null;
|
|
33
|
+
data: InitData | null;
|
|
34
|
+
} {
|
|
35
|
+
const {
|
|
36
|
+
version: versionData,
|
|
37
|
+
loading: versionLoading,
|
|
38
|
+
error: versionError,
|
|
39
|
+
} = useVersion(version);
|
|
40
|
+
const { book: bookData, loading: bookLoading, error: bookError } = useBook(version, book);
|
|
41
|
+
const {
|
|
42
|
+
chapter: chapterData,
|
|
43
|
+
loading: chapterLoading,
|
|
44
|
+
error: chapterError,
|
|
45
|
+
} = useChapter(version, book, chapter);
|
|
46
|
+
|
|
47
|
+
const allDataAvailable = versionData && bookData && chapterData;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
loading: versionLoading || bookLoading || chapterLoading,
|
|
51
|
+
error: [versionError, bookError, chapterError].filter(Boolean).join(' '),
|
|
52
|
+
data: allDataAvailable ? { version: versionData, book: bookData, chapter: chapterData } : null,
|
|
53
|
+
};
|
|
54
|
+
}
|