@youversion/platform-react-hooks 1.18.1 → 1.19.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/AGENTS.md +3 -7
- package/CHANGELOG.md +17 -0
- package/dist/__tests__/mocks/bibles.d.ts +6 -1
- package/dist/__tests__/mocks/bibles.d.ts.map +1 -1
- package/dist/__tests__/mocks/bibles.js +10 -0
- package/dist/__tests__/mocks/bibles.js.map +1 -1
- package/dist/__tests__/mocks/core-mock-factory.d.ts +83 -0
- package/dist/__tests__/mocks/core-mock-factory.d.ts.map +1 -0
- package/dist/__tests__/mocks/core-mock-factory.js +138 -0
- package/dist/__tests__/mocks/core-mock-factory.js.map +1 -0
- package/dist/context/ReaderContext.d.ts +6 -0
- package/dist/context/ReaderContext.d.ts.map +1 -1
- package/dist/context/ReaderContext.js +6 -0
- package/dist/context/ReaderContext.js.map +1 -1
- package/dist/context/ReaderProvider.d.ts +3 -0
- package/dist/context/ReaderProvider.d.ts.map +1 -1
- package/dist/context/ReaderProvider.js +3 -0
- package/dist/context/ReaderProvider.js.map +1 -1
- package/dist/context/VerseSelectionContext.d.ts +6 -0
- package/dist/context/VerseSelectionContext.d.ts.map +1 -1
- package/dist/context/VerseSelectionContext.js +3 -0
- package/dist/context/VerseSelectionContext.js.map +1 -1
- package/dist/context/VerseSelectionProvider.d.ts +3 -0
- package/dist/context/VerseSelectionProvider.d.ts.map +1 -1
- package/dist/context/VerseSelectionProvider.js +3 -0
- package/dist/context/VerseSelectionProvider.js.map +1 -1
- package/dist/test/utils.d.ts +7 -0
- package/dist/test/utils.d.ts.map +1 -0
- package/dist/test/utils.js +7 -0
- package/dist/test/utils.js.map +1 -0
- package/dist/useChapterNavigation.d.ts +3 -0
- package/dist/useChapterNavigation.d.ts.map +1 -1
- package/dist/useChapterNavigation.js +3 -0
- package/dist/useChapterNavigation.js.map +1 -1
- package/dist/useInitData.d.ts +4 -0
- package/dist/useInitData.d.ts.map +1 -1
- package/dist/useInitData.js +4 -0
- package/dist/useInitData.js.map +1 -1
- package/dist/useVerseSelection.d.ts +3 -0
- package/dist/useVerseSelection.d.ts.map +1 -1
- package/dist/useVerseSelection.js +3 -0
- package/dist/useVerseSelection.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/mocks/bibles.ts +18 -1
- package/src/__tests__/mocks/core-mock-factory.ts +226 -0
- package/src/context/ReaderContext.tsx +6 -0
- package/src/context/ReaderProvider.tsx +3 -0
- package/src/context/VerseSelectionContext.tsx +6 -0
- package/src/context/VerseSelectionProvider.tsx +3 -0
- package/src/context/YouVersionAuthProvider.test.tsx +14 -131
- package/src/test/utils.tsx +12 -0
- package/src/useBibleClient.test.tsx +8 -37
- package/src/useBook.test.tsx +158 -0
- package/src/useBooks.test.tsx +148 -0
- package/src/useChapter.test.tsx +70 -128
- package/src/useChapterNavigation.ts +3 -0
- package/src/useChapters.test.tsx +80 -150
- package/src/useHighlights.test.tsx +33 -104
- package/src/useInitData.ts +4 -0
- package/src/useLanguage.test.tsx +8 -10
- package/src/useLanguageClient.test.tsx +9 -25
- package/src/useLanguages.test.tsx +27 -64
- package/src/usePassage.test.tsx +304 -0
- package/src/useTheme.test.tsx +32 -0
- package/src/useVOTD.test.tsx +28 -67
- package/src/useVerse.test.tsx +73 -149
- package/src/useVerseSelection.ts +3 -0
- package/src/useVerses.test.tsx +37 -104
- package/src/useVersion.test.tsx +29 -66
- package/src/useVersions.test.tsx +72 -154
- package/src/useYVAuth.test.tsx +26 -134
- package/src/utility/getDayOfYear.test.ts +48 -0
- package/vitest.config.ts +12 -0
- package/src/context/ReaderProvider.test.tsx +0 -264
- package/src/context/VerseSelectionProvider.test.tsx +0 -362
- package/src/useChapterNavigation.test.tsx +0 -160
- package/src/useVerseSelection.test.tsx +0 -33
- package/vitest.setup.ts +0 -1
package/src/useLanguage.test.tsx
CHANGED
|
@@ -26,8 +26,6 @@ describe('useLanguage', () => {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
beforeEach(() => {
|
|
29
|
-
vi.resetAllMocks();
|
|
30
|
-
|
|
31
29
|
mockGetLanguage.mockResolvedValue(mockLanguage);
|
|
32
30
|
|
|
33
31
|
const mockClient: Partial<LanguagesClient> = { getLanguage: mockGetLanguage };
|
|
@@ -45,8 +43,8 @@ describe('useLanguage', () => {
|
|
|
45
43
|
expect(result.current.loading).toBe(false);
|
|
46
44
|
});
|
|
47
45
|
|
|
48
|
-
expect(mockGetLanguage).toHaveBeenCalledWith('en');
|
|
49
|
-
expect(result.current.language).toEqual(mockLanguage);
|
|
46
|
+
expect.soft(mockGetLanguage).toHaveBeenCalledWith('en');
|
|
47
|
+
expect.soft(result.current.language).toEqual(mockLanguage);
|
|
50
48
|
});
|
|
51
49
|
|
|
52
50
|
it('should refetch when languageId changes', async () => {
|
|
@@ -58,8 +56,8 @@ describe('useLanguage', () => {
|
|
|
58
56
|
expect(result.current.loading).toBe(false);
|
|
59
57
|
});
|
|
60
58
|
|
|
61
|
-
expect(mockGetLanguage).toHaveBeenCalledTimes(1);
|
|
62
|
-
expect(mockGetLanguage).toHaveBeenCalledWith('en');
|
|
59
|
+
expect.soft(mockGetLanguage).toHaveBeenCalledTimes(1);
|
|
60
|
+
expect.soft(mockGetLanguage).toHaveBeenCalledWith('en');
|
|
63
61
|
|
|
64
62
|
rerender({ languageId: 'es' });
|
|
65
63
|
|
|
@@ -77,8 +75,8 @@ describe('useLanguage', () => {
|
|
|
77
75
|
expect(result.current.loading).toBe(false);
|
|
78
76
|
});
|
|
79
77
|
|
|
80
|
-
expect(mockGetLanguage).not.toHaveBeenCalled();
|
|
81
|
-
expect(result.current.language).toBe(null);
|
|
78
|
+
expect.soft(mockGetLanguage).not.toHaveBeenCalled();
|
|
79
|
+
expect.soft(result.current.language).toBe(null);
|
|
82
80
|
});
|
|
83
81
|
|
|
84
82
|
it('should handle fetch errors', async () => {
|
|
@@ -91,8 +89,8 @@ describe('useLanguage', () => {
|
|
|
91
89
|
expect(result.current.loading).toBe(false);
|
|
92
90
|
});
|
|
93
91
|
|
|
94
|
-
expect(result.current.error).toEqual(error);
|
|
95
|
-
expect(result.current.language).toBe(null);
|
|
92
|
+
expect.soft(result.current.error).toEqual(error);
|
|
93
|
+
expect.soft(result.current.language).toBe(null);
|
|
96
94
|
});
|
|
97
95
|
|
|
98
96
|
it('should support manual refetch', async () => {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { renderHook } from '@testing-library/react';
|
|
2
|
-
import { describe,
|
|
2
|
+
import { describe, expect, vi, beforeEach, it } from 'vitest';
|
|
3
3
|
import type { ReactNode } from 'react';
|
|
4
4
|
import { useLanguagesClient } from './useLanguageClient';
|
|
5
5
|
import { YouVersionContext } from './context';
|
|
6
6
|
import { LanguagesClient, ApiClient } from '@youversion/platform-core';
|
|
7
|
+
import { createYVWrapper } from './test/utils';
|
|
7
8
|
|
|
8
|
-
// Mock the core package
|
|
9
9
|
vi.mock('@youversion/platform-core', async () => {
|
|
10
10
|
const actual = await vi.importActual('@youversion/platform-core');
|
|
11
11
|
return {
|
|
@@ -20,17 +20,7 @@ vi.mock('@youversion/platform-core', async () => {
|
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
describe('useLanguagesClient', () => {
|
|
23
|
-
const mockAppKey = 'test-app-key';
|
|
24
|
-
|
|
25
|
-
const createWrapper = (contextValue: { appKey: string }) => {
|
|
26
|
-
return ({ children }: { children: ReactNode }) => (
|
|
27
|
-
<YouVersionContext.Provider value={contextValue}>{children}</YouVersionContext.Provider>
|
|
28
|
-
);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
23
|
beforeEach(() => {
|
|
32
|
-
vi.resetAllMocks();
|
|
33
|
-
|
|
34
24
|
vi.mocked(LanguagesClient).mockImplementation(function () {
|
|
35
25
|
const mockClient: Partial<LanguagesClient> = { getLanguages: vi.fn() };
|
|
36
26
|
return mockClient;
|
|
@@ -50,9 +40,9 @@ describe('useLanguagesClient', () => {
|
|
|
50
40
|
});
|
|
51
41
|
|
|
52
42
|
it('should throw error when appKey is missing', () => {
|
|
53
|
-
const wrapper =
|
|
54
|
-
appKey: ''
|
|
55
|
-
|
|
43
|
+
const wrapper = ({ children }: { children: ReactNode }) => (
|
|
44
|
+
<YouVersionContext.Provider value={{ appKey: '' }}>{children}</YouVersionContext.Provider>
|
|
45
|
+
);
|
|
56
46
|
|
|
57
47
|
expect(() => renderHook(() => useLanguagesClient(), { wrapper })).toThrow(
|
|
58
48
|
'YouVersion context not found. Make sure your component is wrapped with YouVersionProvider and an API key is provided.',
|
|
@@ -62,23 +52,17 @@ describe('useLanguagesClient', () => {
|
|
|
62
52
|
|
|
63
53
|
describe('client creation', () => {
|
|
64
54
|
it('should create LanguagesClient with correct ApiClient config', () => {
|
|
65
|
-
const wrapper =
|
|
66
|
-
appKey: mockAppKey,
|
|
67
|
-
});
|
|
68
|
-
|
|
55
|
+
const wrapper = createYVWrapper();
|
|
69
56
|
renderHook(() => useLanguagesClient(), { wrapper });
|
|
70
57
|
|
|
71
58
|
expect(ApiClient).toHaveBeenCalledWith({
|
|
72
|
-
appKey:
|
|
59
|
+
appKey: 'test-app-key',
|
|
73
60
|
});
|
|
74
61
|
expect(LanguagesClient).toHaveBeenCalledWith(expect.objectContaining({ isApiClient: true }));
|
|
75
62
|
});
|
|
76
63
|
|
|
77
64
|
it('should memoize LanguagesClient instance', () => {
|
|
78
|
-
const wrapper =
|
|
79
|
-
appKey: mockAppKey,
|
|
80
|
-
});
|
|
81
|
-
|
|
65
|
+
const wrapper = createYVWrapper();
|
|
82
66
|
const { rerender } = renderHook(() => useLanguagesClient(), { wrapper });
|
|
83
67
|
|
|
84
68
|
rerender();
|
|
@@ -87,7 +71,7 @@ describe('useLanguagesClient', () => {
|
|
|
87
71
|
});
|
|
88
72
|
|
|
89
73
|
it('should create new LanguagesClient when context values change', () => {
|
|
90
|
-
let currentAppKey =
|
|
74
|
+
let currentAppKey = 'test-app-key';
|
|
91
75
|
|
|
92
76
|
const wrapper = ({ children }: { children: ReactNode }) => (
|
|
93
77
|
<YouVersionContext.Provider
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { renderHook, waitFor, act } from '@testing-library/react';
|
|
2
|
-
import { describe,
|
|
3
|
-
import type { ReactNode } from 'react';
|
|
2
|
+
import { describe, expect, vi, beforeEach, it } from 'vitest';
|
|
4
3
|
import { useLanguages } from './useLanguages';
|
|
5
|
-
import { YouVersionContext } from './context';
|
|
6
4
|
import {
|
|
7
5
|
type LanguagesClient,
|
|
8
6
|
type Collection,
|
|
@@ -10,11 +8,11 @@ import {
|
|
|
10
8
|
type GetLanguagesOptions,
|
|
11
9
|
} from '@youversion/platform-core';
|
|
12
10
|
import { useLanguagesClient } from './useLanguageClient';
|
|
11
|
+
import { createYVWrapper } from './test/utils';
|
|
13
12
|
|
|
14
13
|
vi.mock('./useLanguageClient');
|
|
15
14
|
|
|
16
15
|
describe('useLanguages', () => {
|
|
17
|
-
const mockAppKey = 'test-app-key';
|
|
18
16
|
const mockGetLanguages = vi.fn();
|
|
19
17
|
|
|
20
18
|
const mockLanguages: Collection<Language> = {
|
|
@@ -59,15 +57,7 @@ describe('useLanguages', () => {
|
|
|
59
57
|
next_page_token: null,
|
|
60
58
|
};
|
|
61
59
|
|
|
62
|
-
const createWrapper = (contextValue: { appKey: string }) => {
|
|
63
|
-
return ({ children }: { children: ReactNode }) => (
|
|
64
|
-
<YouVersionContext.Provider value={contextValue}>{children}</YouVersionContext.Provider>
|
|
65
|
-
);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
60
|
beforeEach(() => {
|
|
69
|
-
vi.resetAllMocks();
|
|
70
|
-
|
|
71
61
|
mockGetLanguages.mockResolvedValue(mockLanguages);
|
|
72
62
|
|
|
73
63
|
const mockClient: Partial<LanguagesClient> = { getLanguages: mockGetLanguages };
|
|
@@ -76,10 +66,7 @@ describe('useLanguages', () => {
|
|
|
76
66
|
|
|
77
67
|
describe('fetching languages', () => {
|
|
78
68
|
it('should fetch languages without country filter', async () => {
|
|
79
|
-
const wrapper =
|
|
80
|
-
appKey: mockAppKey,
|
|
81
|
-
});
|
|
82
|
-
|
|
69
|
+
const wrapper = createYVWrapper();
|
|
83
70
|
const { result } = renderHook(() => useLanguages(), { wrapper });
|
|
84
71
|
|
|
85
72
|
expect(result.current.loading).toBe(true);
|
|
@@ -89,15 +76,12 @@ describe('useLanguages', () => {
|
|
|
89
76
|
expect(result.current.loading).toBe(false);
|
|
90
77
|
});
|
|
91
78
|
|
|
92
|
-
expect(mockGetLanguages).toHaveBeenCalledWith({});
|
|
93
|
-
expect(result.current.languages).toEqual(mockLanguages);
|
|
79
|
+
expect.soft(mockGetLanguages).toHaveBeenCalledWith({});
|
|
80
|
+
expect.soft(result.current.languages).toEqual(mockLanguages);
|
|
94
81
|
});
|
|
95
82
|
|
|
96
83
|
it('should fetch languages with provided country', async () => {
|
|
97
|
-
const wrapper =
|
|
98
|
-
appKey: mockAppKey,
|
|
99
|
-
});
|
|
100
|
-
|
|
84
|
+
const wrapper = createYVWrapper();
|
|
101
85
|
const { result } = renderHook(() => useLanguages({ country: 'US' }), { wrapper });
|
|
102
86
|
|
|
103
87
|
expect(result.current.loading).toBe(true);
|
|
@@ -107,15 +91,12 @@ describe('useLanguages', () => {
|
|
|
107
91
|
expect(result.current.loading).toBe(false);
|
|
108
92
|
});
|
|
109
93
|
|
|
110
|
-
expect(mockGetLanguages).toHaveBeenCalledWith({ country: 'US' });
|
|
111
|
-
expect(result.current.languages).toEqual(mockLanguages);
|
|
94
|
+
expect.soft(mockGetLanguages).toHaveBeenCalledWith({ country: 'US' });
|
|
95
|
+
expect.soft(result.current.languages).toEqual(mockLanguages);
|
|
112
96
|
});
|
|
113
97
|
|
|
114
98
|
it('should fetch languages with all options', async () => {
|
|
115
|
-
const wrapper =
|
|
116
|
-
appKey: mockAppKey,
|
|
117
|
-
});
|
|
118
|
-
|
|
99
|
+
const wrapper = createYVWrapper();
|
|
119
100
|
const options: GetLanguagesOptions = {
|
|
120
101
|
country: 'US',
|
|
121
102
|
page_size: 10,
|
|
@@ -128,15 +109,12 @@ describe('useLanguages', () => {
|
|
|
128
109
|
expect(result.current.loading).toBe(false);
|
|
129
110
|
});
|
|
130
111
|
|
|
131
|
-
expect(mockGetLanguages).toHaveBeenCalledWith(options);
|
|
132
|
-
expect(result.current.languages).toEqual(mockLanguages);
|
|
112
|
+
expect.soft(mockGetLanguages).toHaveBeenCalledWith(options);
|
|
113
|
+
expect.soft(result.current.languages).toEqual(mockLanguages);
|
|
133
114
|
});
|
|
134
115
|
|
|
135
116
|
it('should refetch when options change', async () => {
|
|
136
|
-
const wrapper =
|
|
137
|
-
appKey: mockAppKey,
|
|
138
|
-
});
|
|
139
|
-
|
|
117
|
+
const wrapper = createYVWrapper();
|
|
140
118
|
const { result, rerender } = renderHook(({ options }) => useLanguages(options), {
|
|
141
119
|
wrapper,
|
|
142
120
|
initialProps: { options: { country: 'US' } },
|
|
@@ -154,15 +132,12 @@ describe('useLanguages', () => {
|
|
|
154
132
|
expect(result.current.loading).toBe(false);
|
|
155
133
|
});
|
|
156
134
|
|
|
157
|
-
expect(mockGetLanguages).toHaveBeenCalledTimes(2);
|
|
158
|
-
expect(mockGetLanguages).toHaveBeenLastCalledWith({ country: 'ES' });
|
|
135
|
+
expect.soft(mockGetLanguages).toHaveBeenCalledTimes(2);
|
|
136
|
+
expect.soft(mockGetLanguages).toHaveBeenLastCalledWith({ country: 'ES' });
|
|
159
137
|
});
|
|
160
138
|
|
|
161
139
|
it('should not fetch when enabled is false', async () => {
|
|
162
|
-
const wrapper =
|
|
163
|
-
appKey: mockAppKey,
|
|
164
|
-
});
|
|
165
|
-
|
|
140
|
+
const wrapper = createYVWrapper();
|
|
166
141
|
const { result } = renderHook(() => useLanguages({ country: 'US' }, { enabled: false }), {
|
|
167
142
|
wrapper,
|
|
168
143
|
});
|
|
@@ -171,33 +146,27 @@ describe('useLanguages', () => {
|
|
|
171
146
|
expect(result.current.loading).toBe(false);
|
|
172
147
|
});
|
|
173
148
|
|
|
174
|
-
expect(mockGetLanguages).not.toHaveBeenCalled();
|
|
175
|
-
expect(result.current.languages).toBe(null);
|
|
149
|
+
expect.soft(mockGetLanguages).not.toHaveBeenCalled();
|
|
150
|
+
expect.soft(result.current.languages).toBe(null);
|
|
176
151
|
});
|
|
177
152
|
|
|
178
153
|
it('should handle fetch errors', async () => {
|
|
154
|
+
const wrapper = createYVWrapper();
|
|
179
155
|
const error = new Error('Failed to fetch languages');
|
|
180
156
|
mockGetLanguages.mockRejectedValueOnce(error);
|
|
181
157
|
|
|
182
|
-
const wrapper = createWrapper({
|
|
183
|
-
appKey: mockAppKey,
|
|
184
|
-
});
|
|
185
|
-
|
|
186
158
|
const { result } = renderHook(() => useLanguages({ country: 'US' }), { wrapper });
|
|
187
159
|
|
|
188
160
|
await waitFor(() => {
|
|
189
161
|
expect(result.current.loading).toBe(false);
|
|
190
162
|
});
|
|
191
163
|
|
|
192
|
-
expect(result.current.error).toEqual(error);
|
|
193
|
-
expect(result.current.languages).toBe(null);
|
|
164
|
+
expect.soft(result.current.error).toEqual(error);
|
|
165
|
+
expect.soft(result.current.languages).toBe(null);
|
|
194
166
|
});
|
|
195
167
|
|
|
196
168
|
it('should support manual refetch', async () => {
|
|
197
|
-
const wrapper =
|
|
198
|
-
appKey: mockAppKey,
|
|
199
|
-
});
|
|
200
|
-
|
|
169
|
+
const wrapper = createYVWrapper();
|
|
201
170
|
const { result } = renderHook(() => useLanguages({ country: 'US' }), { wrapper });
|
|
202
171
|
|
|
203
172
|
await waitFor(() => {
|
|
@@ -216,10 +185,7 @@ describe('useLanguages', () => {
|
|
|
216
185
|
});
|
|
217
186
|
|
|
218
187
|
it('should fetch languages with fields filter', async () => {
|
|
219
|
-
const wrapper =
|
|
220
|
-
appKey: mockAppKey,
|
|
221
|
-
});
|
|
222
|
-
|
|
188
|
+
const wrapper = createYVWrapper();
|
|
223
189
|
const options: GetLanguagesOptions = {
|
|
224
190
|
fields: ['id', 'language', 'script'],
|
|
225
191
|
page_size: '*',
|
|
@@ -231,15 +197,12 @@ describe('useLanguages', () => {
|
|
|
231
197
|
expect(result.current.loading).toBe(false);
|
|
232
198
|
});
|
|
233
199
|
|
|
234
|
-
expect(mockGetLanguages).toHaveBeenCalledWith(options);
|
|
235
|
-
expect(result.current.languages).toEqual(mockLanguages);
|
|
200
|
+
expect.soft(mockGetLanguages).toHaveBeenCalledWith(options);
|
|
201
|
+
expect.soft(result.current.languages).toEqual(mockLanguages);
|
|
236
202
|
});
|
|
237
203
|
|
|
238
204
|
it('should refetch when fields change', async () => {
|
|
239
|
-
const wrapper =
|
|
240
|
-
appKey: mockAppKey,
|
|
241
|
-
});
|
|
242
|
-
|
|
205
|
+
const wrapper = createYVWrapper();
|
|
243
206
|
const { result, rerender } = renderHook(({ options }) => useLanguages(options), {
|
|
244
207
|
wrapper,
|
|
245
208
|
initialProps: { options: { fields: ['id', 'language'] } as GetLanguagesOptions },
|
|
@@ -257,8 +220,8 @@ describe('useLanguages', () => {
|
|
|
257
220
|
expect(result.current.loading).toBe(false);
|
|
258
221
|
});
|
|
259
222
|
|
|
260
|
-
expect(mockGetLanguages).toHaveBeenCalledTimes(2);
|
|
261
|
-
expect(mockGetLanguages).toHaveBeenLastCalledWith({
|
|
223
|
+
expect.soft(mockGetLanguages).toHaveBeenCalledTimes(2);
|
|
224
|
+
expect.soft(mockGetLanguages).toHaveBeenLastCalledWith({
|
|
262
225
|
fields: ['id', 'language', 'script'],
|
|
263
226
|
});
|
|
264
227
|
});
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { renderHook, waitFor, act } from '@testing-library/react';
|
|
2
|
+
import { describe, expect, vi, beforeEach, it } from 'vitest';
|
|
3
|
+
import { usePassage } from './usePassage';
|
|
4
|
+
import { type BibleClient } from '@youversion/platform-core';
|
|
5
|
+
import { useBibleClient } from './useBibleClient';
|
|
6
|
+
import { createYVWrapper } from './test/utils';
|
|
7
|
+
import { createMockPassage } from './__tests__/mocks/bibles';
|
|
8
|
+
|
|
9
|
+
vi.mock('./useBibleClient');
|
|
10
|
+
|
|
11
|
+
describe('usePassage', () => {
|
|
12
|
+
const mockGetPassage = vi.fn();
|
|
13
|
+
const mockPassage = createMockPassage();
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
mockGetPassage.mockResolvedValue(mockPassage);
|
|
17
|
+
|
|
18
|
+
const mockClient: Partial<BibleClient> = { getPassage: mockGetPassage };
|
|
19
|
+
vi.mocked(useBibleClient).mockReturnValue(mockClient as BibleClient);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('basic fetching', () => {
|
|
23
|
+
it('should show loading then passage data', async () => {
|
|
24
|
+
const wrapper = createYVWrapper();
|
|
25
|
+
const { result } = renderHook(() => usePassage({ versionId: 3034, usfm: 'JHN.3.16' }), {
|
|
26
|
+
wrapper,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(result.current.loading).toBe(true);
|
|
30
|
+
expect(result.current.passage).toBe(null);
|
|
31
|
+
|
|
32
|
+
await waitFor(() => {
|
|
33
|
+
expect(result.current.loading).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
expect.soft(result.current.passage).toEqual(mockPassage);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should call getPassage with correct default args', async () => {
|
|
40
|
+
const wrapper = createYVWrapper();
|
|
41
|
+
const { result } = renderHook(() => usePassage({ versionId: 3034, usfm: 'JHN.3.16' }), {
|
|
42
|
+
wrapper,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
await waitFor(() => {
|
|
46
|
+
expect(result.current.loading).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect.soft(mockGetPassage).toHaveBeenCalledWith(3034, 'JHN.3.16', 'html', false, false);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('USFM validation', () => {
|
|
54
|
+
it.each(['', 'undefined', 'null'])('should skip fetch when usfm is "%s"', async (usfm) => {
|
|
55
|
+
const wrapper = createYVWrapper();
|
|
56
|
+
const { result } = renderHook(() => usePassage({ versionId: 3034, usfm }), { wrapper });
|
|
57
|
+
|
|
58
|
+
await waitFor(() => {
|
|
59
|
+
expect(result.current.loading).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect.soft(mockGetPassage).not.toHaveBeenCalled();
|
|
63
|
+
expect.soft(result.current.passage).toBe(null);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('format options', () => {
|
|
68
|
+
it('should fetch with format text when specified', async () => {
|
|
69
|
+
const wrapper = createYVWrapper();
|
|
70
|
+
const { result } = renderHook(
|
|
71
|
+
() => usePassage({ versionId: 3034, usfm: 'JHN.3.16', format: 'text' }),
|
|
72
|
+
{ wrapper },
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
await waitFor(() => {
|
|
76
|
+
expect(result.current.loading).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect.soft(mockGetPassage).toHaveBeenCalledWith(3034, 'JHN.3.16', 'text', false, false);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('heading and notes options', () => {
|
|
84
|
+
it('should pass include_headings=true', async () => {
|
|
85
|
+
const wrapper = createYVWrapper();
|
|
86
|
+
const { result } = renderHook(
|
|
87
|
+
() => usePassage({ versionId: 3034, usfm: 'JHN.3.16', include_headings: true }),
|
|
88
|
+
{ wrapper },
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
await waitFor(() => {
|
|
92
|
+
expect(result.current.loading).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect.soft(mockGetPassage).toHaveBeenCalledWith(3034, 'JHN.3.16', 'html', true, false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should pass include_notes=true', async () => {
|
|
99
|
+
const wrapper = createYVWrapper();
|
|
100
|
+
const { result } = renderHook(
|
|
101
|
+
() => usePassage({ versionId: 3034, usfm: 'JHN.3.16', include_notes: true }),
|
|
102
|
+
{ wrapper },
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
await waitFor(() => {
|
|
106
|
+
expect(result.current.loading).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect.soft(mockGetPassage).toHaveBeenCalledWith(3034, 'JHN.3.16', 'html', false, true);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should pass all options combined', async () => {
|
|
113
|
+
const wrapper = createYVWrapper();
|
|
114
|
+
const { result } = renderHook(
|
|
115
|
+
() =>
|
|
116
|
+
usePassage({
|
|
117
|
+
versionId: 3034,
|
|
118
|
+
usfm: 'JHN.3',
|
|
119
|
+
format: 'text',
|
|
120
|
+
include_headings: true,
|
|
121
|
+
include_notes: true,
|
|
122
|
+
}),
|
|
123
|
+
{ wrapper },
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
await waitFor(() => {
|
|
127
|
+
expect(result.current.loading).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
expect.soft(mockGetPassage).toHaveBeenCalledWith(3034, 'JHN.3', 'text', true, true);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('parameter change refetching', () => {
|
|
135
|
+
it.each([
|
|
136
|
+
{
|
|
137
|
+
param: 'versionId',
|
|
138
|
+
HookFn: ({ val }: { val: number | string }) =>
|
|
139
|
+
usePassage({ versionId: val as number, usfm: 'JHN.3.16' }),
|
|
140
|
+
initial: { val: 1 },
|
|
141
|
+
updated: { val: 3034 },
|
|
142
|
+
expectedInitial: [1, 'JHN.3.16', 'html', false, false],
|
|
143
|
+
expectedUpdated: [3034, 'JHN.3.16', 'html', false, false],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
param: 'usfm',
|
|
147
|
+
HookFn: ({ val }: { val: number | string }) =>
|
|
148
|
+
usePassage({ versionId: 3034, usfm: val as string }),
|
|
149
|
+
initial: { val: 'JHN.3.16' },
|
|
150
|
+
updated: { val: 'GEN.1.1' },
|
|
151
|
+
expectedInitial: [3034, 'JHN.3.16', 'html', false, false],
|
|
152
|
+
expectedUpdated: [3034, 'GEN.1.1', 'html', false, false],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
param: 'format',
|
|
156
|
+
HookFn: ({ val }: { val: number | string }) =>
|
|
157
|
+
usePassage({ versionId: 3034, usfm: 'JHN.3.16', format: val as 'html' | 'text' }),
|
|
158
|
+
initial: { val: 'html' },
|
|
159
|
+
updated: { val: 'text' },
|
|
160
|
+
expectedInitial: [3034, 'JHN.3.16', 'html', false, false],
|
|
161
|
+
expectedUpdated: [3034, 'JHN.3.16', 'text', false, false],
|
|
162
|
+
},
|
|
163
|
+
])(
|
|
164
|
+
'should refetch when $param changes',
|
|
165
|
+
async ({ HookFn, initial, updated, expectedInitial, expectedUpdated }) => {
|
|
166
|
+
const wrapper = createYVWrapper();
|
|
167
|
+
const { result, rerender } = renderHook(HookFn, {
|
|
168
|
+
wrapper,
|
|
169
|
+
initialProps: initial,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await waitFor(() => {
|
|
173
|
+
expect(result.current.loading).toBe(false);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect.soft(mockGetPassage).toHaveBeenCalledTimes(1);
|
|
177
|
+
expect.soft(mockGetPassage).toHaveBeenLastCalledWith(...expectedInitial);
|
|
178
|
+
|
|
179
|
+
act(() => {
|
|
180
|
+
rerender(updated);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await waitFor(() => {
|
|
184
|
+
expect(result.current.loading).toBe(false);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect.soft(mockGetPassage).toHaveBeenCalledTimes(2);
|
|
188
|
+
expect.soft(mockGetPassage).toHaveBeenLastCalledWith(...expectedUpdated);
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('enabled option', () => {
|
|
194
|
+
it('should not fetch when enabled is false', async () => {
|
|
195
|
+
const wrapper = createYVWrapper();
|
|
196
|
+
const { result } = renderHook(
|
|
197
|
+
() =>
|
|
198
|
+
usePassage({
|
|
199
|
+
versionId: 3034,
|
|
200
|
+
usfm: 'JHN.3.16',
|
|
201
|
+
options: { enabled: false },
|
|
202
|
+
}),
|
|
203
|
+
{ wrapper },
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
await waitFor(() => {
|
|
207
|
+
expect(result.current.loading).toBe(false);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
expect.soft(mockGetPassage).not.toHaveBeenCalled();
|
|
211
|
+
expect.soft(result.current.passage).toBe(null);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should not fetch when both enabled is false and usfm is invalid', async () => {
|
|
215
|
+
const wrapper = createYVWrapper();
|
|
216
|
+
const { result } = renderHook(
|
|
217
|
+
() =>
|
|
218
|
+
usePassage({
|
|
219
|
+
versionId: 3034,
|
|
220
|
+
usfm: '',
|
|
221
|
+
options: { enabled: false },
|
|
222
|
+
}),
|
|
223
|
+
{ wrapper },
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
await waitFor(() => {
|
|
227
|
+
expect(result.current.loading).toBe(false);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expect.soft(mockGetPassage).not.toHaveBeenCalled();
|
|
231
|
+
expect.soft(result.current.passage).toBe(null);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('error handling', () => {
|
|
236
|
+
it('should surface fetch errors', async () => {
|
|
237
|
+
const wrapper = createYVWrapper();
|
|
238
|
+
const error = new Error('Failed to fetch passage');
|
|
239
|
+
mockGetPassage.mockRejectedValueOnce(error);
|
|
240
|
+
|
|
241
|
+
const { result } = renderHook(() => usePassage({ versionId: 3034, usfm: 'JHN.3.16' }), {
|
|
242
|
+
wrapper,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
await waitFor(() => {
|
|
246
|
+
expect(result.current.loading).toBe(false);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
expect.soft(result.current.error).toEqual(error);
|
|
250
|
+
expect.soft(result.current.passage).toBe(null);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should clear error on successful refetch', async () => {
|
|
254
|
+
const wrapper = createYVWrapper();
|
|
255
|
+
const error = new Error('Failed to fetch passage');
|
|
256
|
+
mockGetPassage.mockRejectedValueOnce(error).mockResolvedValueOnce(mockPassage);
|
|
257
|
+
|
|
258
|
+
const { result } = renderHook(() => usePassage({ versionId: 3034, usfm: 'JHN.3.16' }), {
|
|
259
|
+
wrapper,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
await waitFor(() => {
|
|
263
|
+
expect(result.current.loading).toBe(false);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect.soft(result.current.error).toEqual(error);
|
|
267
|
+
expect.soft(result.current.passage).toBe(null);
|
|
268
|
+
|
|
269
|
+
act(() => {
|
|
270
|
+
result.current.refetch();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
await waitFor(() => {
|
|
274
|
+
expect(result.current.loading).toBe(false);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
expect.soft(result.current.error).toBe(null);
|
|
278
|
+
expect.soft(result.current.passage).toEqual(mockPassage);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
describe('refetch', () => {
|
|
283
|
+
it('should support manual refetch', async () => {
|
|
284
|
+
const wrapper = createYVWrapper();
|
|
285
|
+
const { result } = renderHook(() => usePassage({ versionId: 3034, usfm: 'JHN.3.16' }), {
|
|
286
|
+
wrapper,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
await waitFor(() => {
|
|
290
|
+
expect(result.current.loading).toBe(false);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
expect(mockGetPassage).toHaveBeenCalledTimes(1);
|
|
294
|
+
|
|
295
|
+
act(() => {
|
|
296
|
+
result.current.refetch();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
await waitFor(() => {
|
|
300
|
+
expect(mockGetPassage).toHaveBeenCalledTimes(2);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { useTheme } from './useTheme';
|
|
4
|
+
import { createYVWrapper } from './test/utils';
|
|
5
|
+
|
|
6
|
+
describe('useTheme', () => {
|
|
7
|
+
it('should return light when used outside provider', () => {
|
|
8
|
+
const { result } = renderHook(() => useTheme());
|
|
9
|
+
expect(result.current).toBe('light');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should return light when theme is undefined in provider', () => {
|
|
13
|
+
const { result } = renderHook(() => useTheme(), {
|
|
14
|
+
wrapper: createYVWrapper(),
|
|
15
|
+
});
|
|
16
|
+
expect(result.current).toBe('light');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should return light when theme is light', () => {
|
|
20
|
+
const { result } = renderHook(() => useTheme(), {
|
|
21
|
+
wrapper: createYVWrapper('test-app-key', { theme: 'light' }),
|
|
22
|
+
});
|
|
23
|
+
expect(result.current).toBe('light');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should return dark when theme is dark', () => {
|
|
27
|
+
const { result } = renderHook(() => useTheme(), {
|
|
28
|
+
wrapper: createYVWrapper('test-app-key', { theme: 'dark' }),
|
|
29
|
+
});
|
|
30
|
+
expect(result.current).toBe('dark');
|
|
31
|
+
});
|
|
32
|
+
});
|