@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.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +3 -7
- package/CHANGELOG.md +32 -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/useYVAuth.test.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
2
|
import { renderHook, act } from '@testing-library/react';
|
|
4
3
|
import { YouVersionAPIUsers, YouVersionPlatformConfiguration } from '@youversion/platform-core';
|
|
5
4
|
import { useYVAuth } from './useYVAuth';
|
|
@@ -8,117 +7,10 @@ import { createMockUserInfo, createMockAuthResult } from './__tests__/mocks/auth
|
|
|
8
7
|
import { createAuthProviderWrapper } from './__tests__/utils/test-utils';
|
|
9
8
|
import type { AuthenticationScopes } from '@youversion/platform-core';
|
|
10
9
|
|
|
11
|
-
// Mock the core modules
|
|
12
|
-
vi.mock('@youversion/platform-core', () => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
accessToken: null as string | null,
|
|
16
|
-
idToken: null as string | null,
|
|
17
|
-
refreshToken: null as string | null,
|
|
18
|
-
appKey: '',
|
|
19
|
-
apiHost: 'test-api.example.com',
|
|
20
|
-
installationId: null as string | null,
|
|
21
|
-
clearAuthTokens: vi.fn(() => {
|
|
22
|
-
mockConfiguration.accessToken = null;
|
|
23
|
-
mockConfiguration.idToken = null;
|
|
24
|
-
mockConfiguration.refreshToken = null;
|
|
25
|
-
}),
|
|
26
|
-
saveAuthData: vi.fn(
|
|
27
|
-
(
|
|
28
|
-
accessToken: string | null,
|
|
29
|
-
refreshToken: string | null,
|
|
30
|
-
idToken: string | null,
|
|
31
|
-
installationId: string | null,
|
|
32
|
-
) => {
|
|
33
|
-
mockConfiguration.accessToken = accessToken;
|
|
34
|
-
mockConfiguration.refreshToken = refreshToken;
|
|
35
|
-
mockConfiguration.idToken = idToken;
|
|
36
|
-
mockConfiguration.installationId = installationId;
|
|
37
|
-
},
|
|
38
|
-
),
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
YouVersionAPIUsers: {
|
|
43
|
-
signIn: vi.fn(),
|
|
44
|
-
handleAuthCallback: vi.fn(),
|
|
45
|
-
userInfo: vi.fn(),
|
|
46
|
-
refreshTokenIfNeeded: vi.fn(),
|
|
47
|
-
},
|
|
48
|
-
YouVersionPlatformConfiguration: mockConfiguration,
|
|
49
|
-
SignInWithYouVersionPermission: {
|
|
50
|
-
bibles: 'bibles',
|
|
51
|
-
highlights: 'highlights',
|
|
52
|
-
user: 'user',
|
|
53
|
-
},
|
|
54
|
-
YouVersionUserInfo: class YouVersionUserInfo {
|
|
55
|
-
readonly name?: string;
|
|
56
|
-
readonly userId?: string;
|
|
57
|
-
readonly email?: string;
|
|
58
|
-
readonly avatarUrlFormat?: string;
|
|
59
|
-
|
|
60
|
-
constructor(data: any) {
|
|
61
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
62
|
-
this.name = data.name;
|
|
63
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
64
|
-
this.userId = data.id;
|
|
65
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
66
|
-
this.email = data.email;
|
|
67
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
68
|
-
this.avatarUrlFormat = data.avatar_url;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
getAvatarUrl(width: number = 200, height: number = 200): URL | null {
|
|
72
|
-
if (!this.avatarUrlFormat) {
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
try {
|
|
76
|
-
let urlString = this.avatarUrlFormat;
|
|
77
|
-
urlString = urlString.replace('{width}', width.toString());
|
|
78
|
-
urlString = urlString.replace('{height}', height.toString());
|
|
79
|
-
return new URL(urlString);
|
|
80
|
-
} catch {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
get avatarUrl(): URL | null {
|
|
86
|
-
return this.getAvatarUrl();
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
SignInWithYouVersionResult: class SignInWithYouVersionResult {
|
|
90
|
-
accessToken: string | undefined;
|
|
91
|
-
expiryDate: Date | undefined;
|
|
92
|
-
refreshToken: string | undefined;
|
|
93
|
-
idToken: string | undefined;
|
|
94
|
-
yvpUserId: string | undefined;
|
|
95
|
-
name: string | undefined;
|
|
96
|
-
profilePicture: string | undefined;
|
|
97
|
-
email: string | undefined;
|
|
98
|
-
|
|
99
|
-
constructor(props: {
|
|
100
|
-
accessToken?: string;
|
|
101
|
-
expiresIn?: number;
|
|
102
|
-
refreshToken?: string;
|
|
103
|
-
idToken?: string;
|
|
104
|
-
yvpUserId?: string;
|
|
105
|
-
name?: string;
|
|
106
|
-
profilePicture?: string;
|
|
107
|
-
email?: string;
|
|
108
|
-
}) {
|
|
109
|
-
this.accessToken = props.accessToken;
|
|
110
|
-
this.expiryDate = props.expiresIn
|
|
111
|
-
? new Date(Date.now() + props.expiresIn * 1000)
|
|
112
|
-
: new Date();
|
|
113
|
-
this.refreshToken = props.refreshToken;
|
|
114
|
-
this.idToken = props.idToken;
|
|
115
|
-
this.yvpUserId = props.yvpUserId;
|
|
116
|
-
this.name = props.name;
|
|
117
|
-
this.profilePicture = props.profilePicture;
|
|
118
|
-
this.email = props.email;
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
};
|
|
10
|
+
// Mock the core modules using shared factory
|
|
11
|
+
vi.mock('@youversion/platform-core', async () => {
|
|
12
|
+
const { createSimpleCoreMockFactory } = await import('./__tests__/mocks/core-mock-factory');
|
|
13
|
+
return createSimpleCoreMockFactory();
|
|
122
14
|
});
|
|
123
15
|
|
|
124
16
|
const mockUserInfo = createMockUserInfo();
|
|
@@ -150,8 +42,6 @@ const renderAuthHook = async () => {
|
|
|
150
42
|
|
|
151
43
|
describe('useYVAuth', () => {
|
|
152
44
|
beforeEach(() => {
|
|
153
|
-
vi.resetAllMocks();
|
|
154
|
-
|
|
155
45
|
// Setup window mock
|
|
156
46
|
vi.stubGlobal('window', mockWindow);
|
|
157
47
|
mockWindow.location.search = '';
|
|
@@ -161,11 +51,6 @@ describe('useYVAuth', () => {
|
|
|
161
51
|
YouVersionPlatformConfiguration.installationId = null;
|
|
162
52
|
});
|
|
163
53
|
|
|
164
|
-
afterEach(() => {
|
|
165
|
-
vi.clearAllMocks();
|
|
166
|
-
vi.unstubAllGlobals();
|
|
167
|
-
});
|
|
168
|
-
|
|
169
54
|
describe('initialization', () => {
|
|
170
55
|
it('should return unauthenticated state when no user info available', async () => {
|
|
171
56
|
const { result } = await renderAuthHook();
|
|
@@ -198,7 +83,7 @@ describe('useYVAuth', () => {
|
|
|
198
83
|
await result.current.signIn({ redirectUrl, scopes: ['profile'] });
|
|
199
84
|
});
|
|
200
85
|
|
|
201
|
-
expect(vi.mocked(YouVersionAPIUsers.signIn)
|
|
86
|
+
expect(vi.mocked(YouVersionAPIUsers).signIn).toHaveBeenCalledWith(redirectUrl, ['profile']);
|
|
202
87
|
});
|
|
203
88
|
|
|
204
89
|
it('should call signIn with empty scopes when not provided', async () => {
|
|
@@ -209,7 +94,7 @@ describe('useYVAuth', () => {
|
|
|
209
94
|
await result.current.signIn({ redirectUrl });
|
|
210
95
|
});
|
|
211
96
|
|
|
212
|
-
expect(vi.mocked(YouVersionAPIUsers.signIn)
|
|
97
|
+
expect(vi.mocked(YouVersionAPIUsers).signIn).toHaveBeenCalledWith(redirectUrl);
|
|
213
98
|
});
|
|
214
99
|
|
|
215
100
|
it('should call YouVersionAPIUsers.signIn exactly once with scopes', async () => {
|
|
@@ -221,8 +106,8 @@ describe('useYVAuth', () => {
|
|
|
221
106
|
await result.current.signIn({ redirectUrl, scopes });
|
|
222
107
|
});
|
|
223
108
|
|
|
224
|
-
expect(vi.mocked(YouVersionAPIUsers.signIn)
|
|
225
|
-
expect(vi.mocked(YouVersionAPIUsers.signIn)
|
|
109
|
+
expect(vi.mocked(YouVersionAPIUsers).signIn).toHaveBeenCalledTimes(1);
|
|
110
|
+
expect(vi.mocked(YouVersionAPIUsers).signIn).toHaveBeenCalledWith(redirectUrl, scopes);
|
|
226
111
|
});
|
|
227
112
|
|
|
228
113
|
it('should call YouVersionAPIUsers.signIn exactly once without scopes', async () => {
|
|
@@ -233,14 +118,15 @@ describe('useYVAuth', () => {
|
|
|
233
118
|
await result.current.signIn({ redirectUrl });
|
|
234
119
|
});
|
|
235
120
|
|
|
236
|
-
expect(vi.mocked(YouVersionAPIUsers.signIn)
|
|
237
|
-
expect(vi.mocked(YouVersionAPIUsers.signIn)
|
|
121
|
+
expect(vi.mocked(YouVersionAPIUsers).signIn).toHaveBeenCalledTimes(1);
|
|
122
|
+
expect(vi.mocked(YouVersionAPIUsers).signIn).toHaveBeenCalledWith(redirectUrl);
|
|
238
123
|
});
|
|
239
124
|
|
|
240
125
|
it('should throw error when signIn fails', async () => {
|
|
241
126
|
const { result } = await renderAuthHook();
|
|
242
127
|
const error = new Error('Sign in failed');
|
|
243
|
-
vi.
|
|
128
|
+
const signInMock = vi.spyOn(YouVersionAPIUsers, 'signIn');
|
|
129
|
+
signInMock.mockRejectedValue(error);
|
|
244
130
|
|
|
245
131
|
await expect(
|
|
246
132
|
act(async () => {
|
|
@@ -256,7 +142,7 @@ describe('useYVAuth', () => {
|
|
|
256
142
|
await result.current.signIn();
|
|
257
143
|
});
|
|
258
144
|
|
|
259
|
-
expect(vi.mocked(YouVersionAPIUsers.signIn)
|
|
145
|
+
expect(vi.mocked(YouVersionAPIUsers).signIn).toHaveBeenCalledWith('http://test.example.com');
|
|
260
146
|
});
|
|
261
147
|
|
|
262
148
|
it('should use redirectUri from provider with scopes when redirectUrl is not passed', async () => {
|
|
@@ -267,7 +153,7 @@ describe('useYVAuth', () => {
|
|
|
267
153
|
await result.current.signIn({ scopes });
|
|
268
154
|
});
|
|
269
155
|
|
|
270
|
-
expect(vi.mocked(YouVersionAPIUsers.signIn)
|
|
156
|
+
expect(vi.mocked(YouVersionAPIUsers).signIn).toHaveBeenCalledWith(
|
|
271
157
|
'http://test.example.com',
|
|
272
158
|
scopes,
|
|
273
159
|
);
|
|
@@ -277,21 +163,23 @@ describe('useYVAuth', () => {
|
|
|
277
163
|
describe('processCallback', () => {
|
|
278
164
|
it('should call handleAuthCallback and return result', async () => {
|
|
279
165
|
const { result } = await renderAuthHook();
|
|
280
|
-
vi.
|
|
166
|
+
const callbackMock = vi.spyOn(YouVersionAPIUsers, 'handleAuthCallback');
|
|
167
|
+
callbackMock.mockResolvedValue(mockAuthResult);
|
|
281
168
|
|
|
282
169
|
let callbackResult;
|
|
283
170
|
await act(async () => {
|
|
284
171
|
callbackResult = await result.current.processCallback();
|
|
285
172
|
});
|
|
286
173
|
|
|
287
|
-
expect(
|
|
174
|
+
expect(callbackMock).toHaveBeenCalled();
|
|
288
175
|
expect(callbackResult).toEqual(mockAuthResult);
|
|
289
176
|
});
|
|
290
177
|
|
|
291
178
|
it('should throw error when callback processing fails', async () => {
|
|
292
179
|
const { result } = await renderAuthHook();
|
|
293
180
|
const error = new Error('Callback processing failed');
|
|
294
|
-
vi.
|
|
181
|
+
const callbackMock = vi.spyOn(YouVersionAPIUsers, 'handleAuthCallback');
|
|
182
|
+
callbackMock.mockRejectedValue(error);
|
|
295
183
|
|
|
296
184
|
await expect(
|
|
297
185
|
act(async () => {
|
|
@@ -302,7 +190,8 @@ describe('useYVAuth', () => {
|
|
|
302
190
|
|
|
303
191
|
it('should return null when no result from callback', async () => {
|
|
304
192
|
const { result } = await renderAuthHook();
|
|
305
|
-
vi.
|
|
193
|
+
const callbackMock = vi.spyOn(YouVersionAPIUsers, 'handleAuthCallback');
|
|
194
|
+
callbackMock.mockResolvedValue(null);
|
|
306
195
|
|
|
307
196
|
let callbackResult;
|
|
308
197
|
await act(async () => {
|
|
@@ -317,11 +206,14 @@ describe('useYVAuth', () => {
|
|
|
317
206
|
it('should call clearAuthTokens and reset user info', async () => {
|
|
318
207
|
const { result } = await renderAuthHook();
|
|
319
208
|
|
|
209
|
+
const clearAuthTokensSpy = vi.spyOn(YouVersionPlatformConfiguration, 'clearAuthTokens');
|
|
210
|
+
clearAuthTokensSpy.mockClear();
|
|
211
|
+
|
|
320
212
|
act(() => {
|
|
321
213
|
result.current.signOut();
|
|
322
214
|
});
|
|
323
215
|
|
|
324
|
-
expect(
|
|
216
|
+
expect(clearAuthTokensSpy).toHaveBeenCalledTimes(1);
|
|
325
217
|
});
|
|
326
218
|
});
|
|
327
219
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, it, expect, test } from 'vitest';
|
|
2
|
+
import { getDayOfYear } from './getDayOfYear';
|
|
3
|
+
|
|
4
|
+
describe('getDayOfYear', () => {
|
|
5
|
+
// Group 1: Standard days
|
|
6
|
+
test.for([
|
|
7
|
+
{ date: new Date(2025, 0, 1), expected: 1, label: 'Jan 1' },
|
|
8
|
+
{ date: new Date(2025, 0, 2), expected: 2, label: 'Jan 2' },
|
|
9
|
+
{ date: new Date(2025, 1, 1), expected: 32, label: 'Feb 1' },
|
|
10
|
+
{ date: new Date(2025, 6, 4), expected: 185, label: 'Jul 4 (non-leap)' },
|
|
11
|
+
{ date: new Date(2025, 11, 31), expected: 365, label: 'Dec 31 (non-leap)' },
|
|
12
|
+
])('should return $expected for $label', ({ date, expected }, { expect }) => {
|
|
13
|
+
expect(getDayOfYear(date)).toBe(expected);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Group 2: Leap year handling
|
|
17
|
+
test.for([
|
|
18
|
+
{ date: new Date(2024, 1, 28), expected: 59, label: 'Feb 28 (leap)' },
|
|
19
|
+
{ date: new Date(2024, 1, 29), expected: 60, label: 'Feb 29 (leap)' },
|
|
20
|
+
{ date: new Date(2024, 2, 1), expected: 61, label: 'Mar 1 (leap)' },
|
|
21
|
+
{ date: new Date(2025, 2, 1), expected: 60, label: 'Mar 1 (non-leap)' },
|
|
22
|
+
{ date: new Date(2024, 11, 31), expected: 366, label: 'Dec 31 (leap)' },
|
|
23
|
+
])('should return $expected for $label', ({ date, expected }, { expect }) => {
|
|
24
|
+
expect(getDayOfYear(date)).toBe(expected);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Group 3: Time-of-day stability
|
|
28
|
+
test.for([
|
|
29
|
+
{ date: new Date(2025, 0, 1, 0, 0, 0), expected: 1, label: 'midnight' },
|
|
30
|
+
{ date: new Date(2025, 0, 1, 23, 59, 59), expected: 1, label: 'end of day' },
|
|
31
|
+
{ date: new Date(2025, 5, 15, 12, 0, 0), expected: 166, label: 'noon Jun 15' },
|
|
32
|
+
])('should return $expected at $label', ({ date, expected }, { expect }) => {
|
|
33
|
+
expect(getDayOfYear(date)).toBe(expected);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Group 4: Edge cases
|
|
37
|
+
it('should handle century non-leap year (1900)', () => {
|
|
38
|
+
expect(getDayOfYear(new Date(1900, 2, 1))).toBe(60);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should handle century leap year (2000)', () => {
|
|
42
|
+
expect(getDayOfYear(new Date(2000, 2, 1))).toBe(61);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should handle far future dates', () => {
|
|
46
|
+
expect(getDayOfYear(new Date(2100, 0, 1))).toBe(1);
|
|
47
|
+
});
|
|
48
|
+
});
|
package/vitest.config.ts
CHANGED
|
@@ -4,15 +4,27 @@ import react from '@vitejs/plugin-react';
|
|
|
4
4
|
export default defineConfig({
|
|
5
5
|
plugins: [react()],
|
|
6
6
|
test: {
|
|
7
|
+
env: { TZ: 'UTC' }, // Pin to UTC so date-arithmetic tests are timezone-independent
|
|
7
8
|
environment: 'jsdom',
|
|
8
9
|
setupFiles: ['./src/test/setup.ts'],
|
|
9
10
|
globals: true,
|
|
11
|
+
mockReset: true,
|
|
12
|
+
unstubGlobals: true,
|
|
10
13
|
coverage: {
|
|
11
14
|
provider: 'v8',
|
|
12
15
|
reporter: ['text', 'json-summary'],
|
|
13
16
|
reportsDirectory: './coverage',
|
|
14
17
|
all: true,
|
|
15
18
|
include: ['src/**/*.{ts,tsx}'],
|
|
19
|
+
exclude: [
|
|
20
|
+
'src/useInitData.ts',
|
|
21
|
+
'src/useChapterNavigation.ts',
|
|
22
|
+
'src/useVerseSelection.ts',
|
|
23
|
+
'src/context/ReaderContext.tsx',
|
|
24
|
+
'src/context/ReaderProvider.tsx',
|
|
25
|
+
'src/context/VerseSelectionContext.tsx',
|
|
26
|
+
'src/context/VerseSelectionProvider.tsx',
|
|
27
|
+
],
|
|
16
28
|
},
|
|
17
29
|
},
|
|
18
30
|
});
|
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { renderHook, act } from '@testing-library/react';
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import { useReaderContext } from './ReaderContext';
|
|
5
|
-
import { ReaderProvider } from './ReaderProvider';
|
|
6
|
-
import {
|
|
7
|
-
createMockBook,
|
|
8
|
-
createMockChapter,
|
|
9
|
-
createMockVerse,
|
|
10
|
-
createMockVersion,
|
|
11
|
-
} from '../__tests__/mocks/bibles';
|
|
12
|
-
|
|
13
|
-
// Mock Bible data
|
|
14
|
-
const mockVersion = createMockVersion();
|
|
15
|
-
const mockBook = createMockBook();
|
|
16
|
-
const mockChapter = createMockChapter();
|
|
17
|
-
const mockVerse = createMockVerse();
|
|
18
|
-
|
|
19
|
-
// Alternative mock data for update tests
|
|
20
|
-
const mockVersion2 = createMockVersion({
|
|
21
|
-
id: 2,
|
|
22
|
-
title: 'New International Version',
|
|
23
|
-
abbreviation: 'NIV',
|
|
24
|
-
localized_abbreviation: 'NIV',
|
|
25
|
-
localized_title: 'New International Version',
|
|
26
|
-
youversion_deep_link: 'https://www.bible.com/versions/2',
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const mockBook2 = createMockBook({
|
|
30
|
-
id: 'JHN',
|
|
31
|
-
title: 'John',
|
|
32
|
-
full_title: 'The Gospel According to John',
|
|
33
|
-
abbreviation: 'Jn',
|
|
34
|
-
canon: 'new_testament',
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const mockChapter2 = createMockChapter({
|
|
38
|
-
id: '3',
|
|
39
|
-
passage_id: 'JHN.3',
|
|
40
|
-
title: '3',
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
const mockVerse2 = createMockVerse({
|
|
44
|
-
id: '16',
|
|
45
|
-
passage_id: 'JHN.3.16',
|
|
46
|
-
title: '16',
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// Test wrappers
|
|
50
|
-
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
51
|
-
<ReaderProvider
|
|
52
|
-
currentVersion={mockVersion}
|
|
53
|
-
currentBook={mockBook}
|
|
54
|
-
currentChapter={mockChapter}
|
|
55
|
-
currentVerse={mockVerse}
|
|
56
|
-
>
|
|
57
|
-
{children}
|
|
58
|
-
</ReaderProvider>
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
const wrapperWithNullVerse = ({ children }: { children: React.ReactNode }) => (
|
|
62
|
-
<ReaderProvider
|
|
63
|
-
currentVersion={mockVersion}
|
|
64
|
-
currentBook={mockBook}
|
|
65
|
-
currentChapter={mockChapter}
|
|
66
|
-
currentVerse={null}
|
|
67
|
-
>
|
|
68
|
-
{children}
|
|
69
|
-
</ReaderProvider>
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
describe('ReaderProvider', () => {
|
|
73
|
-
describe('initialization', () => {
|
|
74
|
-
it('should initialize with provided version', () => {
|
|
75
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper });
|
|
76
|
-
|
|
77
|
-
expect(result.current.currentVersion).toEqual(mockVersion);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('should initialize with provided book', () => {
|
|
81
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper });
|
|
82
|
-
|
|
83
|
-
expect(result.current.currentBook).toEqual(mockBook);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('should initialize with provided chapter', () => {
|
|
87
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper });
|
|
88
|
-
|
|
89
|
-
expect(result.current.currentChapter).toEqual(mockChapter);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should initialize with provided verse', () => {
|
|
93
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper });
|
|
94
|
-
|
|
95
|
-
expect(result.current.currentVerse).toEqual(mockVerse);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('should initialize with null verse when provided', () => {
|
|
99
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper: wrapperWithNullVerse });
|
|
100
|
-
|
|
101
|
-
expect(result.current.currentVerse).toBeNull();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('should throw error when useReaderContext is used outside provider', () => {
|
|
105
|
-
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => undefined);
|
|
106
|
-
|
|
107
|
-
expect(() => renderHook(() => useReaderContext())).toThrow(
|
|
108
|
-
'useReaderContext() must be used within a ReaderProvider',
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
consoleError.mockRestore();
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
describe('setVersion', () => {
|
|
116
|
-
it('should update version when setVersion is called', () => {
|
|
117
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper });
|
|
118
|
-
|
|
119
|
-
act(() => {
|
|
120
|
-
result.current.setVersion(mockVersion2);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
expect(result.current.currentVersion).toEqual(mockVersion2);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('should preserve other state when version changes', () => {
|
|
127
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper });
|
|
128
|
-
|
|
129
|
-
act(() => {
|
|
130
|
-
result.current.setVersion(mockVersion2);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
expect(result.current.currentVersion).toMatchObject({ abbreviation: 'NIV' });
|
|
134
|
-
expect(result.current.currentBook).toEqual(mockBook);
|
|
135
|
-
expect(result.current.currentChapter).toEqual(mockChapter);
|
|
136
|
-
expect(result.current.currentVerse).toEqual(mockVerse);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
describe('setBook', () => {
|
|
141
|
-
it('should update book when setBook is called', () => {
|
|
142
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper });
|
|
143
|
-
|
|
144
|
-
act(() => {
|
|
145
|
-
result.current.setBook(mockBook2);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
expect(result.current.currentBook).toEqual(mockBook2);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('should preserve other state when book changes', () => {
|
|
152
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper });
|
|
153
|
-
|
|
154
|
-
act(() => {
|
|
155
|
-
result.current.setBook(mockBook2);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
expect(result.current.currentBook).toMatchObject({ id: 'JHN' });
|
|
159
|
-
expect(result.current.currentVersion).toEqual(mockVersion);
|
|
160
|
-
expect(result.current.currentChapter).toEqual(mockChapter);
|
|
161
|
-
expect(result.current.currentVerse).toEqual(mockVerse);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
describe('setChapter', () => {
|
|
166
|
-
it('should update chapter when setChapter is called', () => {
|
|
167
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper });
|
|
168
|
-
|
|
169
|
-
act(() => {
|
|
170
|
-
result.current.setChapter(mockChapter2);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
expect(result.current.currentChapter).toEqual(mockChapter2);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('should preserve other state when chapter changes', () => {
|
|
177
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper });
|
|
178
|
-
|
|
179
|
-
act(() => {
|
|
180
|
-
result.current.setChapter(mockChapter2);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
expect(result.current.currentChapter).toMatchObject({ id: '3' });
|
|
184
|
-
expect(result.current.currentVersion).toEqual(mockVersion);
|
|
185
|
-
expect(result.current.currentBook).toEqual(mockBook);
|
|
186
|
-
expect(result.current.currentVerse).toEqual(mockVerse);
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
describe('setVerse', () => {
|
|
191
|
-
it('should update verse when setVerse is called', () => {
|
|
192
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper });
|
|
193
|
-
|
|
194
|
-
act(() => {
|
|
195
|
-
result.current.setVerse(mockVerse2);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
expect(result.current.currentVerse).toEqual(mockVerse2);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it('should update verse to null when setVerse is called with null', () => {
|
|
202
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper });
|
|
203
|
-
|
|
204
|
-
act(() => {
|
|
205
|
-
result.current.setVerse(null);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
expect(result.current.currentVerse).toBeNull();
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('should preserve other state when verse changes', () => {
|
|
212
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper });
|
|
213
|
-
|
|
214
|
-
act(() => {
|
|
215
|
-
result.current.setVerse(mockVerse2);
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
expect(result.current.currentVerse).toMatchObject({ id: '16' });
|
|
219
|
-
expect(result.current.currentVersion).toEqual(mockVersion);
|
|
220
|
-
expect(result.current.currentBook).toEqual(mockBook);
|
|
221
|
-
expect(result.current.currentChapter).toEqual(mockChapter);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it('should preserve other state when verse changes to null', () => {
|
|
225
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper });
|
|
226
|
-
|
|
227
|
-
act(() => {
|
|
228
|
-
result.current.setVerse(null);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
expect(result.current.currentVerse).toBeNull();
|
|
232
|
-
expect(result.current.currentVersion).toEqual(mockVersion);
|
|
233
|
-
expect(result.current.currentBook).toEqual(mockBook);
|
|
234
|
-
expect(result.current.currentChapter).toEqual(mockChapter);
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
describe('state persistence', () => {
|
|
239
|
-
it('should initialize with all provided props', () => {
|
|
240
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper });
|
|
241
|
-
|
|
242
|
-
expect(result.current.currentVersion).toEqual(mockVersion);
|
|
243
|
-
expect(result.current.currentBook).toEqual(mockBook);
|
|
244
|
-
expect(result.current.currentChapter).toEqual(mockChapter);
|
|
245
|
-
expect(result.current.currentVerse).toEqual(mockVerse);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it('should handle rapid successive state updates without corruption', () => {
|
|
249
|
-
const { result } = renderHook(() => useReaderContext(), { wrapper });
|
|
250
|
-
|
|
251
|
-
act(() => {
|
|
252
|
-
result.current.setVersion(mockVersion2);
|
|
253
|
-
result.current.setBook(mockBook2);
|
|
254
|
-
result.current.setChapter(mockChapter2);
|
|
255
|
-
result.current.setVerse(mockVerse2);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
expect(result.current.currentVersion).toEqual(mockVersion2);
|
|
259
|
-
expect(result.current.currentBook).toEqual(mockBook2);
|
|
260
|
-
expect(result.current.currentChapter).toEqual(mockChapter2);
|
|
261
|
-
expect(result.current.currentVerse).toEqual(mockVerse2);
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
});
|