@youversion/platform-core 0.8.1 → 0.9.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 +8 -8
- package/CHANGELOG.md +23 -0
- package/dist/index.cjs +116 -169
- package/dist/index.d.cts +39 -35
- package/dist/index.d.ts +39 -35
- package/dist/index.js +116 -168
- package/package.json +2 -1
- package/src/SignInWithYouVersionPKCE.ts +7 -7
- package/src/SignInWithYouVersionResult.ts +0 -6
- package/src/Users.ts +5 -29
- package/src/__tests__/MockBibles.ts +1196 -1294
- package/src/__tests__/MockChapters.ts +171 -1950
- package/src/__tests__/MockPassages.ts +7 -14
- package/src/__tests__/MockVerses.ts +60 -75
- package/src/__tests__/MockVersions.ts +30 -30
- package/src/__tests__/SignInWithYouVersionPKCE.test.ts +8 -77
- package/src/__tests__/SignInWithYouVersionResult.test.ts +0 -2
- package/src/__tests__/Users.test.ts +4 -38
- package/src/__tests__/bible.test.ts +9 -18
- package/src/__tests__/client.test.ts +10 -1
- package/src/__tests__/setup.ts +12 -9
- package/src/bible.ts +1 -1
- package/src/index.ts +0 -1
- package/src/schemas/book.ts +5 -8
- package/src/schemas/chapter.ts +3 -12
- package/src/schemas/passage.ts +1 -3
- package/src/schemas/verse.ts +2 -7
- package/src/schemas/version.ts +2 -2
- package/src/types/auth.ts +2 -0
- package/src/types/index.ts +5 -1
- package/src/utils/constants.ts +103 -103
- package/src/URLBuilder.ts +0 -50
- package/src/__tests__/URLBuilder.test.ts +0 -190
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
2
|
import { SignInWithYouVersionPKCEAuthorizationRequestBuilder } from '../SignInWithYouVersionPKCE';
|
|
3
3
|
import { YouVersionPlatformConfiguration } from '../YouVersionPlatformConfiguration';
|
|
4
|
-
import { SignInWithYouVersionPermission } from '../SignInWithYouVersionResult';
|
|
5
|
-
import type { SignInWithYouVersionPermissionValues } from '../types/auth';
|
|
6
4
|
import { setupBrowserMocks, cleanupBrowserMocks } from './mocks/browser';
|
|
7
5
|
|
|
8
6
|
describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
|
|
@@ -61,15 +59,10 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
|
|
|
61
59
|
.mockReturnValueOnce('stateBase64==') // State
|
|
62
60
|
.mockReturnValueOnce('nonceBase64=='); // Nonce
|
|
63
61
|
|
|
64
|
-
const permissions = new Set<SignInWithYouVersionPermissionValues>([
|
|
65
|
-
SignInWithYouVersionPermission.bibles,
|
|
66
|
-
SignInWithYouVersionPermission.highlights,
|
|
67
|
-
]);
|
|
68
62
|
const redirectURL = new URL('https://example.com/callback');
|
|
69
63
|
|
|
70
64
|
const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
|
|
71
65
|
'test-app-key',
|
|
72
|
-
permissions,
|
|
73
66
|
redirectURL,
|
|
74
67
|
);
|
|
75
68
|
|
|
@@ -101,17 +94,14 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
|
|
|
101
94
|
mocks.crypto.subtle.digest.mockResolvedValue(new Uint8Array(32).buffer);
|
|
102
95
|
mocks.btoa.mockImplementation((str: string) => `base64_${callCount}_${str.length}`);
|
|
103
96
|
|
|
104
|
-
const permissions = new Set<SignInWithYouVersionPermissionValues>();
|
|
105
97
|
const redirectURL = new URL('https://example.com/callback');
|
|
106
98
|
|
|
107
99
|
const result1 = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
|
|
108
100
|
'app-key',
|
|
109
|
-
permissions,
|
|
110
101
|
redirectURL,
|
|
111
102
|
);
|
|
112
103
|
const result2 = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
|
|
113
104
|
'app-key',
|
|
114
|
-
permissions,
|
|
115
105
|
redirectURL,
|
|
116
106
|
);
|
|
117
107
|
|
|
@@ -136,14 +126,10 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
|
|
|
136
126
|
});
|
|
137
127
|
|
|
138
128
|
it('should build authorization URL with all required OAuth2 parameters', async () => {
|
|
139
|
-
const permissions = new Set<SignInWithYouVersionPermissionValues>([
|
|
140
|
-
SignInWithYouVersionPermission.bibles,
|
|
141
|
-
]);
|
|
142
129
|
const redirectURL = new URL('https://example.com/callback');
|
|
143
130
|
|
|
144
131
|
const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
|
|
145
132
|
'test-app-key',
|
|
146
|
-
permissions,
|
|
147
133
|
redirectURL,
|
|
148
134
|
);
|
|
149
135
|
|
|
@@ -163,12 +149,10 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
|
|
|
163
149
|
});
|
|
164
150
|
|
|
165
151
|
it('should handle redirect URL with trailing slash', async () => {
|
|
166
|
-
const permissions = new Set<SignInWithYouVersionPermissionValues>();
|
|
167
152
|
const redirectURL = new URL('https://example.com/callback/');
|
|
168
153
|
|
|
169
154
|
const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
|
|
170
155
|
'test-app-key',
|
|
171
|
-
permissions,
|
|
172
156
|
redirectURL,
|
|
173
157
|
);
|
|
174
158
|
|
|
@@ -177,12 +161,10 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
|
|
|
177
161
|
});
|
|
178
162
|
|
|
179
163
|
it('should include x-yvp-installation-id param', async () => {
|
|
180
|
-
const permissions = new Set<SignInWithYouVersionPermissionValues>();
|
|
181
164
|
const redirectURL = new URL('https://example.com/callback');
|
|
182
165
|
|
|
183
166
|
const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
|
|
184
167
|
'test-app-key',
|
|
185
|
-
permissions,
|
|
186
168
|
redirectURL,
|
|
187
169
|
);
|
|
188
170
|
|
|
@@ -190,73 +172,41 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
|
|
|
190
172
|
expect(params.get('x-yvp-installation-id')).not.toBeFalsy();
|
|
191
173
|
});
|
|
192
174
|
|
|
193
|
-
it('should create scope string with
|
|
194
|
-
const permissions = new Set<SignInWithYouVersionPermissionValues>([
|
|
195
|
-
SignInWithYouVersionPermission.bibles,
|
|
196
|
-
SignInWithYouVersionPermission.highlights,
|
|
197
|
-
]);
|
|
175
|
+
it('should create scope string with profile and openid', async () => {
|
|
198
176
|
const redirectURL = new URL('https://example.com/callback');
|
|
199
177
|
|
|
200
178
|
const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
|
|
201
179
|
'test-app-key',
|
|
202
|
-
permissions,
|
|
203
180
|
redirectURL,
|
|
181
|
+
['profile'],
|
|
204
182
|
);
|
|
205
183
|
|
|
206
184
|
const params = new URLSearchParams(result.url.search);
|
|
207
185
|
const scope = params.get('scope');
|
|
208
186
|
|
|
209
|
-
expect(scope).toContain('
|
|
210
|
-
expect(scope).toContain('highlights');
|
|
211
|
-
expect(scope).toContain('openid');
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('should sort permissions alphabetically', async () => {
|
|
215
|
-
const permissions = new Set<SignInWithYouVersionPermissionValues>([
|
|
216
|
-
SignInWithYouVersionPermission.votd,
|
|
217
|
-
SignInWithYouVersionPermission.bibles,
|
|
218
|
-
SignInWithYouVersionPermission.demographics,
|
|
219
|
-
]);
|
|
220
|
-
const redirectURL = new URL('https://example.com/callback');
|
|
221
|
-
|
|
222
|
-
const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
|
|
223
|
-
'test-app-key',
|
|
224
|
-
permissions,
|
|
225
|
-
redirectURL,
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
const params = new URLSearchParams(result.url.search);
|
|
229
|
-
const scope = params.get('scope');
|
|
230
|
-
|
|
231
|
-
// Should be sorted: bibles demographics votd openid
|
|
232
|
-
expect(scope).toBe('bibles demographics votd openid');
|
|
187
|
+
expect(scope).toContain('profile');
|
|
233
188
|
});
|
|
234
189
|
|
|
235
190
|
it('should add openid when not present', async () => {
|
|
236
|
-
const permissions = new Set<SignInWithYouVersionPermissionValues>([
|
|
237
|
-
SignInWithYouVersionPermission.bibles,
|
|
238
|
-
]);
|
|
239
191
|
const redirectURL = new URL('https://example.com/callback');
|
|
240
192
|
|
|
241
193
|
const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
|
|
242
194
|
'test-app-key',
|
|
243
|
-
permissions,
|
|
244
195
|
redirectURL,
|
|
196
|
+
['profile'],
|
|
245
197
|
);
|
|
246
198
|
|
|
247
199
|
const params = new URLSearchParams(result.url.search);
|
|
248
200
|
const scope = params.get('scope');
|
|
249
201
|
|
|
250
|
-
expect(scope).toBe('
|
|
202
|
+
expect(scope).toBe('profile openid');
|
|
251
203
|
});
|
|
252
204
|
|
|
253
|
-
it('should handle empty
|
|
254
|
-
const permissions = new Set<SignInWithYouVersionPermissionValues>();
|
|
205
|
+
it('should handle empty scope params', async () => {
|
|
255
206
|
const redirectURL = new URL('https://example.com/callback');
|
|
256
207
|
|
|
257
208
|
const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
|
|
258
209
|
'test-app-key',
|
|
259
|
-
permissions,
|
|
260
210
|
redirectURL,
|
|
261
211
|
);
|
|
262
212
|
|
|
@@ -267,16 +217,10 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
|
|
|
267
217
|
});
|
|
268
218
|
|
|
269
219
|
it('should not duplicate openid if already present', async () => {
|
|
270
|
-
// This test simulates if openid was somehow in the permissions set
|
|
271
|
-
const permissions = new Set<SignInWithYouVersionPermissionValues>([
|
|
272
|
-
'openid' as SignInWithYouVersionPermissionValues,
|
|
273
|
-
SignInWithYouVersionPermission.bibles,
|
|
274
|
-
]);
|
|
275
220
|
const redirectURL = new URL('https://example.com/callback');
|
|
276
221
|
|
|
277
222
|
const result = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
|
|
278
223
|
'test-app-key',
|
|
279
|
-
permissions,
|
|
280
224
|
redirectURL,
|
|
281
225
|
);
|
|
282
226
|
|
|
@@ -347,14 +291,9 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
|
|
|
347
291
|
});
|
|
348
292
|
|
|
349
293
|
it('should use crypto.getRandomValues for secure random generation', async () => {
|
|
350
|
-
const permissions = new Set<SignInWithYouVersionPermissionValues>();
|
|
351
294
|
const redirectURL = new URL('https://example.com/callback');
|
|
352
295
|
|
|
353
|
-
await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
|
|
354
|
-
'test-app-key',
|
|
355
|
-
permissions,
|
|
356
|
-
redirectURL,
|
|
357
|
-
);
|
|
296
|
+
await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make('test-app-key', redirectURL);
|
|
358
297
|
|
|
359
298
|
// Should call crypto.getRandomValues for code verifier, state, and nonce
|
|
360
299
|
expect(mocks.crypto.getRandomValues).toHaveBeenCalledTimes(3);
|
|
@@ -367,14 +306,9 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
|
|
|
367
306
|
});
|
|
368
307
|
|
|
369
308
|
it('should use SHA-256 for code challenge', async () => {
|
|
370
|
-
const permissions = new Set<SignInWithYouVersionPermissionValues>();
|
|
371
309
|
const redirectURL = new URL('https://example.com/callback');
|
|
372
310
|
|
|
373
|
-
await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
|
|
374
|
-
'test-app-key',
|
|
375
|
-
permissions,
|
|
376
|
-
redirectURL,
|
|
377
|
-
);
|
|
311
|
+
await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make('test-app-key', redirectURL);
|
|
378
312
|
|
|
379
313
|
expect(mocks.crypto.subtle.digest).toHaveBeenCalledWith('SHA-256', expect.any(Uint8Array));
|
|
380
314
|
});
|
|
@@ -383,17 +317,14 @@ describe('SignInWithYouVersionPKCEAuthorizationRequestBuilder', () => {
|
|
|
383
317
|
// Use real crypto for this test to verify actual randomness
|
|
384
318
|
cleanupBrowserMocks();
|
|
385
319
|
|
|
386
|
-
const permissions = new Set<SignInWithYouVersionPermissionValues>();
|
|
387
320
|
const redirectURL = new URL('https://example.com/callback');
|
|
388
321
|
|
|
389
322
|
const result1 = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
|
|
390
323
|
'test-app-key',
|
|
391
|
-
permissions,
|
|
392
324
|
redirectURL,
|
|
393
325
|
);
|
|
394
326
|
const result2 = await SignInWithYouVersionPKCEAuthorizationRequestBuilder.make(
|
|
395
327
|
'test-app-key',
|
|
396
|
-
permissions,
|
|
397
328
|
redirectURL,
|
|
398
329
|
);
|
|
399
330
|
|
|
@@ -9,7 +9,6 @@ describe('SignInWithYouVersionResult', () => {
|
|
|
9
9
|
accessToken: 'test-access-token',
|
|
10
10
|
expiresIn: 3600,
|
|
11
11
|
refreshToken: 'test-refresh-token',
|
|
12
|
-
permissions: ['votd', 'bibles'],
|
|
13
12
|
yvpUserId: 'test-user-id',
|
|
14
13
|
name: 'test user',
|
|
15
14
|
profilePicture: 'https://this-is-a-test-picture.com',
|
|
@@ -19,7 +18,6 @@ describe('SignInWithYouVersionResult', () => {
|
|
|
19
18
|
expect(result.accessToken).toBe('test-access-token');
|
|
20
19
|
expect(result.expiryDate).toStrictEqual(new Date(fixedDate.getTime() + 60 * 60 * 1000));
|
|
21
20
|
expect(result.refreshToken).toBe('test-refresh-token');
|
|
22
|
-
expect(result.permissions).toStrictEqual(['votd', 'bibles']);
|
|
23
21
|
expect(result.yvpUserId).toBe('test-user-id');
|
|
24
22
|
expect(result.name).toBe('test user');
|
|
25
23
|
expect(result.profilePicture).toBe('https://this-is-a-test-picture.com');
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
2
|
import { YouVersionAPIUsers } from '../Users';
|
|
3
3
|
import { YouVersionPlatformConfiguration } from '../YouVersionPlatformConfiguration';
|
|
4
|
-
import { SignInWithYouVersionPermission } from '../SignInWithYouVersionResult';
|
|
5
4
|
import { YouVersionUserInfo } from '../YouVersionUserInfo';
|
|
6
|
-
import type { SignInWithYouVersionPermissionValues } from '../types/auth';
|
|
7
5
|
import { setupBrowserMocks, cleanupBrowserMocks } from './mocks/browser';
|
|
8
6
|
|
|
9
7
|
const mockFetch = vi.fn();
|
|
@@ -40,13 +38,9 @@ describe('YouVersionAPIUsers', () => {
|
|
|
40
38
|
it('should throw error when appKey is not set', async () => {
|
|
41
39
|
YouVersionPlatformConfiguration.appKey = null;
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
await expect(
|
|
48
|
-
YouVersionAPIUsers.signIn(permissions, 'https://example.com/callback'),
|
|
49
|
-
).rejects.toThrow('YouVersionPlatformConfiguration.appKey must be set before calling signIn');
|
|
41
|
+
await expect(YouVersionAPIUsers.signIn('https://example.com/callback')).rejects.toThrow(
|
|
42
|
+
'YouVersionPlatformConfiguration.appKey must be set before calling signIn',
|
|
43
|
+
);
|
|
50
44
|
});
|
|
51
45
|
|
|
52
46
|
it('should create authorization request and redirect on successful signIn', async () => {
|
|
@@ -62,13 +56,9 @@ describe('YouVersionAPIUsers', () => {
|
|
|
62
56
|
vi.spyOn(crypto.subtle, 'digest').mockResolvedValue(new Uint8Array(32).buffer);
|
|
63
57
|
mocks.btoa.mockReturnValue('mockBase64Value');
|
|
64
58
|
|
|
65
|
-
const permissions = new Set<SignInWithYouVersionPermissionValues>([
|
|
66
|
-
SignInWithYouVersionPermission.bibles,
|
|
67
|
-
SignInWithYouVersionPermission.highlights,
|
|
68
|
-
]);
|
|
69
59
|
const redirectURL = 'https://example.com/callback';
|
|
70
60
|
|
|
71
|
-
await YouVersionAPIUsers.signIn(
|
|
61
|
+
await YouVersionAPIUsers.signIn(redirectURL);
|
|
72
62
|
|
|
73
63
|
// Verify localStorage items stored
|
|
74
64
|
expect(mocks.localStorage.setItem).toHaveBeenCalledWith(
|
|
@@ -210,7 +200,6 @@ describe('YouVersionAPIUsers', () => {
|
|
|
210
200
|
expect(result).toBeTruthy();
|
|
211
201
|
expect(result?.accessToken).toBe('access-token-123');
|
|
212
202
|
expect(result?.refreshToken).toBe('refresh-token-456');
|
|
213
|
-
expect(result?.permissions).toEqual(['bibles', 'highlights']);
|
|
214
203
|
expect(result?.yvpUserId).toBe('1234567890');
|
|
215
204
|
expect(result?.name).toBe('John Doe');
|
|
216
205
|
expect(result?.email).toBe('john@example.com');
|
|
@@ -323,32 +312,11 @@ describe('YouVersionAPIUsers', () => {
|
|
|
323
312
|
expect(result.accessToken).toBe('access-token-123');
|
|
324
313
|
expect(result.expiryDate).toStrictEqual(new Date(fixedDate.getTime() + 60 * 60 * 1000));
|
|
325
314
|
expect(result.refreshToken).toBe('refresh-token-456');
|
|
326
|
-
expect(result.permissions).toEqual(['bibles', 'highlights']);
|
|
327
315
|
expect(result.yvpUserId).toBe('1234567890');
|
|
328
316
|
expect(result.name).toBe('John Doe');
|
|
329
317
|
expect(result.email).toBe('john@example.com');
|
|
330
318
|
expect(result.profilePicture).toBe('https://example.com/avatar.jpg');
|
|
331
319
|
});
|
|
332
|
-
|
|
333
|
-
it('should filter out unknown permissions', () => {
|
|
334
|
-
const tokens = {
|
|
335
|
-
access_token: 'token',
|
|
336
|
-
expires_in: 3600,
|
|
337
|
-
id_token:
|
|
338
|
-
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U',
|
|
339
|
-
refresh_token: 'refresh',
|
|
340
|
-
scope: 'bibles unknown_permission highlights invalid_scope',
|
|
341
|
-
token_type: 'Bearer',
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
// Mock JWT decoding
|
|
345
|
-
vi.mocked(atob).mockReturnValue(JSON.stringify({ sub: 'user123' }));
|
|
346
|
-
|
|
347
|
-
// @ts-expect-error - accessing private method for testing
|
|
348
|
-
const result = YouVersionAPIUsers.extractSignInResult(tokens);
|
|
349
|
-
|
|
350
|
-
expect(result.permissions).toEqual(['bibles', 'highlights']);
|
|
351
|
-
});
|
|
352
320
|
});
|
|
353
321
|
|
|
354
322
|
describe('decodeJWT', () => {
|
|
@@ -557,8 +525,6 @@ describe('YouVersionAPIUsers', () => {
|
|
|
557
525
|
// Assert that id_token is preserved (same as original)
|
|
558
526
|
expect(result?.idToken).toBe(existingIdToken);
|
|
559
527
|
|
|
560
|
-
expect(result?.permissions).toEqual(['bibles', 'highlights']);
|
|
561
|
-
|
|
562
528
|
// Verify the refresh token request was made correctly
|
|
563
529
|
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
564
530
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
@@ -75,8 +75,10 @@ describe('BibleClient', () => {
|
|
|
75
75
|
expect(books.data).toHaveLength(66);
|
|
76
76
|
expect(books.data[0]).toHaveProperty('id', 'GEN');
|
|
77
77
|
expect(books.data[0]).toHaveProperty('title', 'Genesis');
|
|
78
|
+
expect(books.data[0]).toHaveProperty('full_title', 'The First Book of Moses, Called Genesis');
|
|
78
79
|
expect(books.data[0]).toHaveProperty('abbreviation', 'Gen');
|
|
79
|
-
expect(books.data[0]).toHaveProperty('canon', '
|
|
80
|
+
expect(books.data[0]).toHaveProperty('canon', 'old_testament');
|
|
81
|
+
expect(books.data[0]?.chapters).toHaveLength(50);
|
|
80
82
|
});
|
|
81
83
|
});
|
|
82
84
|
|
|
@@ -91,7 +93,7 @@ describe('BibleClient', () => {
|
|
|
91
93
|
expect(book).toHaveProperty('id', 'GEN');
|
|
92
94
|
expect(book).toHaveProperty('title', 'Genesis');
|
|
93
95
|
expect(book).toHaveProperty('abbreviation', 'Gen');
|
|
94
|
-
expect(book).toHaveProperty('canon', '
|
|
96
|
+
expect(book).toHaveProperty('canon', 'old_testament');
|
|
95
97
|
});
|
|
96
98
|
|
|
97
99
|
it('should throw an error for invalid inputs', async () => {
|
|
@@ -116,7 +118,6 @@ describe('BibleClient', () => {
|
|
|
116
118
|
|
|
117
119
|
expect(chapters.data).toHaveLength(50);
|
|
118
120
|
expect(chapters.data[0]).toHaveProperty('id', '1');
|
|
119
|
-
expect(chapters.data[0]).toHaveProperty('book_id', 'GEN');
|
|
120
121
|
expect(chapters.data[0]).toHaveProperty('passage_id', 'GEN.1');
|
|
121
122
|
expect(chapters.data[0]).toHaveProperty('title', '1');
|
|
122
123
|
expect(chapters.data[0]?.verses).toHaveLength(31);
|
|
@@ -131,7 +132,6 @@ describe('BibleClient', () => {
|
|
|
131
132
|
expect(success).toBe(true);
|
|
132
133
|
|
|
133
134
|
expect(chapter).toHaveProperty('id', '1');
|
|
134
|
-
expect(chapter).toHaveProperty('book_id', 'GEN');
|
|
135
135
|
expect(chapter).toHaveProperty('passage_id', 'GEN.1');
|
|
136
136
|
expect(chapter).toHaveProperty('title', '1');
|
|
137
137
|
expect(chapter.verses).toHaveLength(31);
|
|
@@ -154,11 +154,9 @@ describe('BibleClient', () => {
|
|
|
154
154
|
const { success } = BibleVerseSchema.safeParse(verses.data[0]);
|
|
155
155
|
expect(success).toBe(true);
|
|
156
156
|
|
|
157
|
-
expect(verses.data).toHaveLength(
|
|
157
|
+
expect(verses.data).toHaveLength(31);
|
|
158
158
|
expect(verses.data[0]).toHaveProperty('id', '1');
|
|
159
|
-
expect(verses.data[0]).toHaveProperty('
|
|
160
|
-
expect(verses.data[0]).toHaveProperty('book_id', 'GEN');
|
|
161
|
-
expect(verses.data[0]).toHaveProperty('chapter_id', '1');
|
|
159
|
+
expect(verses.data[0]).toHaveProperty('title', '1');
|
|
162
160
|
expect(verses.data[0]).toHaveProperty('passage_id', 'GEN.1.1');
|
|
163
161
|
});
|
|
164
162
|
});
|
|
@@ -171,9 +169,7 @@ describe('BibleClient', () => {
|
|
|
171
169
|
expect(success).toBe(true);
|
|
172
170
|
|
|
173
171
|
expect(verse).toHaveProperty('id', '1');
|
|
174
|
-
expect(verse).toHaveProperty('
|
|
175
|
-
expect(verse).toHaveProperty('book_id', 'GEN');
|
|
176
|
-
expect(verse).toHaveProperty('chapter_id', '1');
|
|
172
|
+
expect(verse).toHaveProperty('title', '1');
|
|
177
173
|
expect(verse).toHaveProperty('passage_id', 'GEN.1.1');
|
|
178
174
|
});
|
|
179
175
|
|
|
@@ -210,8 +206,7 @@ describe('BibleClient', () => {
|
|
|
210
206
|
id: 'GEN.1.1',
|
|
211
207
|
content:
|
|
212
208
|
'<div><div class="pi"><span class="yv-v" v="1"></span><span class="yv-vlbl">1</span>In the beginning God created the heavens and the earth. </div></div>',
|
|
213
|
-
|
|
214
|
-
human_reference: 'Genesis 1:1',
|
|
209
|
+
reference: 'Genesis 1:1',
|
|
215
210
|
});
|
|
216
211
|
});
|
|
217
212
|
|
|
@@ -222,8 +217,7 @@ describe('BibleClient', () => {
|
|
|
222
217
|
expect(success).toBe(true);
|
|
223
218
|
|
|
224
219
|
expect(passage).toHaveProperty('id', 'GEN.1');
|
|
225
|
-
expect(passage).toHaveProperty('
|
|
226
|
-
expect(passage).toHaveProperty('human_reference', 'Genesis 1');
|
|
220
|
+
expect(passage).toHaveProperty('reference', 'Genesis 1');
|
|
227
221
|
});
|
|
228
222
|
|
|
229
223
|
it('should fetch a passage with html format by default', async () => {
|
|
@@ -242,7 +236,6 @@ describe('BibleClient', () => {
|
|
|
242
236
|
const passage = await bibleClient.getPassage(111, 'ROM.1', 'html', true);
|
|
243
237
|
|
|
244
238
|
expect(passage.id).toBe('ROM.1');
|
|
245
|
-
expect(passage.bible_id).toBe(111);
|
|
246
239
|
expect(passage.content).toContain('yv-h');
|
|
247
240
|
expect(passage.content).not.toContain('yv-n');
|
|
248
241
|
});
|
|
@@ -251,7 +244,6 @@ describe('BibleClient', () => {
|
|
|
251
244
|
const passage = await bibleClient.getPassage(111, 'ROM.1', 'html', undefined, true);
|
|
252
245
|
|
|
253
246
|
expect(passage.id).toBe('ROM.1');
|
|
254
|
-
expect(passage.bible_id).toBe(111);
|
|
255
247
|
expect(passage.content).toContain('yv-n');
|
|
256
248
|
expect(passage.content).not.toContain('yv-h');
|
|
257
249
|
});
|
|
@@ -260,7 +252,6 @@ describe('BibleClient', () => {
|
|
|
260
252
|
const passage = await bibleClient.getPassage(111, 'ROM.1', 'html', true, true);
|
|
261
253
|
|
|
262
254
|
expect(passage.id).toBe('ROM.1');
|
|
263
|
-
expect(passage.bible_id).toBe(111);
|
|
264
255
|
expect(passage.content).toContain('yv-n');
|
|
265
256
|
expect(passage.content).toContain('yv-h');
|
|
266
257
|
});
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
1
|
+
import { describe, it, expect, beforeEach, beforeAll, afterEach, afterAll } from 'vitest';
|
|
2
2
|
import { ApiClient } from '../client';
|
|
3
3
|
import { http, HttpResponse } from 'msw';
|
|
4
4
|
import { server } from './setup';
|
|
5
5
|
|
|
6
|
+
// We always want this test to hit msw since this is only testing
|
|
7
|
+
// the setup of and ApiClient instance and not actually hitting
|
|
8
|
+
// any real APIs.
|
|
9
|
+
if (process.env.INTEGRATION_TESTS) {
|
|
10
|
+
beforeAll(() => server.listen());
|
|
11
|
+
afterEach(() => server.resetHandlers());
|
|
12
|
+
afterAll(() => server.close());
|
|
13
|
+
}
|
|
14
|
+
|
|
6
15
|
describe('ApiClient', () => {
|
|
7
16
|
let apiClient: ApiClient;
|
|
8
17
|
|
package/src/__tests__/setup.ts
CHANGED
|
@@ -4,14 +4,17 @@ import { handlers } from './handlers';
|
|
|
4
4
|
|
|
5
5
|
export const server = setupServer(...handlers);
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
// Only setup MSW if INTEGRATION_TESTS env var is not set
|
|
8
|
+
if (!process.env.INTEGRATION_TESTS) {
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
server.listen();
|
|
11
|
+
});
|
|
10
12
|
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
|
|
13
|
-
});
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
server.resetHandlers();
|
|
15
|
+
});
|
|
14
16
|
|
|
15
|
-
afterAll(() => {
|
|
16
|
-
|
|
17
|
-
});
|
|
17
|
+
afterAll(() => {
|
|
18
|
+
server.close();
|
|
19
|
+
});
|
|
20
|
+
}
|
package/src/bible.ts
CHANGED
|
@@ -81,7 +81,7 @@ export class BibleClient {
|
|
|
81
81
|
/**
|
|
82
82
|
* Fetches all books for a given Bible version.
|
|
83
83
|
* @param versionId The version ID.
|
|
84
|
-
* @param canon Optional canon filter (
|
|
84
|
+
* @param canon Optional canon filter ("old_testament", 'new_testament', 'deuterocanon').
|
|
85
85
|
* @returns An array of BibleBook objects.
|
|
86
86
|
*/
|
|
87
87
|
async getBooks(versionId: number, canon?: CANON): Promise<Collection<BibleBook>> {
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,6 @@ export * from './Users';
|
|
|
11
11
|
export * from './YouVersionUserInfo';
|
|
12
12
|
export * from './SignInWithYouVersionResult';
|
|
13
13
|
export * from './YouVersionAPI';
|
|
14
|
-
export * from './URLBuilder';
|
|
15
14
|
export * from './YouVersionPlatformConfiguration';
|
|
16
15
|
export * from './types';
|
|
17
16
|
export * from './utils/constants';
|
package/src/schemas/book.ts
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { BOOK_IDS } from '../utils/constants';
|
|
3
|
+
import { BibleChapterSchema } from './chapter';
|
|
3
4
|
|
|
4
|
-
export const CanonSchema = z.enum([
|
|
5
|
-
'ot', // Old Testament
|
|
6
|
-
'nt', // New Testament
|
|
7
|
-
'dc', // Deuterocanon (Apocrypha)
|
|
8
|
-
]);
|
|
5
|
+
export const CanonSchema = z.enum(['old_testament', 'new_testament', 'deuterocanon']);
|
|
9
6
|
export type Canon = z.infer<typeof CanonSchema>;
|
|
10
7
|
|
|
11
8
|
// https://github.com/colinhacks/zod/discussions/4934#discussioncomment-13858053
|
|
@@ -20,14 +17,14 @@ export const BibleBookSchema = z.object({
|
|
|
20
17
|
id: BookUsfmSchema,
|
|
21
18
|
/** Book title (e.g., "Genesis") */
|
|
22
19
|
title: z.string(),
|
|
20
|
+
/** Full Book title (e.g., "The First Book of Moses, Commonly Called Genesis") */
|
|
21
|
+
full_title: z.string(),
|
|
23
22
|
/** Book abbreviation (e.g., "Gen") */
|
|
24
23
|
abbreviation: z.string().optional(),
|
|
25
24
|
/** Canonical section (new_testament, old_testament, deuterocanon) */
|
|
26
25
|
canon: CanonSchema,
|
|
27
26
|
/** Array of chapter identifiers (e.g., ["GEN.1", "GEN.2", "GEN.3"]) */
|
|
28
|
-
chapters: z
|
|
29
|
-
.array(z.string().regex(/^\w{3}\.\d+$/, { message: 'Chapter must be an integer string' }))
|
|
30
|
-
.optional(),
|
|
27
|
+
chapters: z.array(BibleChapterSchema).optional(),
|
|
31
28
|
});
|
|
32
29
|
|
|
33
30
|
export type BibleBook = z.infer<typeof BibleBookSchema>;
|
package/src/schemas/chapter.ts
CHANGED
|
@@ -1,24 +1,15 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { BibleVerseSchema } from './verse';
|
|
3
3
|
|
|
4
4
|
export const BibleChapterSchema = z.object({
|
|
5
5
|
/** Chapter identifier (e.g., "1") */
|
|
6
6
|
id: z.string(),
|
|
7
|
-
/** Book identifier (e.g., "MAT") */
|
|
8
|
-
book_id: BookUsfmSchema,
|
|
9
7
|
/** Passage identifier (e.g., "MAT.1") */
|
|
10
8
|
passage_id: z.string(),
|
|
11
9
|
/** Chapter title (e.g., "1") */
|
|
12
10
|
title: z.string(),
|
|
13
|
-
/** Array of
|
|
14
|
-
verses: z
|
|
15
|
-
.array(
|
|
16
|
-
z
|
|
17
|
-
.string()
|
|
18
|
-
.regex(/^\d+$/, { message: 'Verse must be an integer string' })
|
|
19
|
-
.transform((val) => val as `${number}`),
|
|
20
|
-
)
|
|
21
|
-
.optional(),
|
|
11
|
+
/** Array of verses */
|
|
12
|
+
verses: z.array(BibleVerseSchema).optional(),
|
|
22
13
|
});
|
|
23
14
|
|
|
24
15
|
export type BibleChapter = z.infer<typeof BibleChapterSchema>;
|
package/src/schemas/passage.ts
CHANGED
|
@@ -5,10 +5,8 @@ export const BiblePassageSchema = z.object({
|
|
|
5
5
|
id: z.string(),
|
|
6
6
|
/** Passage content text */
|
|
7
7
|
content: z.string(),
|
|
8
|
-
/** Bible version identifier */
|
|
9
|
-
bible_id: z.number().int().positive(),
|
|
10
8
|
/** Human-readable reference (e.g., "Matthew 1:1") */
|
|
11
|
-
|
|
9
|
+
reference: z.string(),
|
|
12
10
|
});
|
|
13
11
|
|
|
14
12
|
export type BiblePassage = z.infer<typeof BiblePassageSchema>;
|
package/src/schemas/verse.ts
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { BookUsfmSchema } from './book';
|
|
3
2
|
|
|
4
3
|
export const BibleVerseSchema = z.object({
|
|
5
4
|
/** Verse identifier (e.g., "1") */
|
|
6
5
|
id: z.string(),
|
|
7
|
-
/** Book identifier (e.g., "MAT") */
|
|
8
|
-
book_id: BookUsfmSchema,
|
|
9
|
-
/** Chapter identifier (e.g., "1") */
|
|
10
|
-
chapter_id: z.string(),
|
|
11
6
|
/** Passage identifier (e.g., "MAT.1.1") */
|
|
12
7
|
passage_id: z.string(),
|
|
13
|
-
/**
|
|
14
|
-
|
|
8
|
+
/** Verse Number (e.g., "1") */
|
|
9
|
+
title: z.string(),
|
|
15
10
|
});
|
|
16
11
|
|
|
17
12
|
export type BibleVerse = z.infer<typeof BibleVerseSchema>;
|
package/src/schemas/version.ts
CHANGED
|
@@ -17,9 +17,9 @@ export const BibleVersionSchema = z.object({
|
|
|
17
17
|
/** Language tag (e.g., "en") */
|
|
18
18
|
language_tag: z.string(),
|
|
19
19
|
/** Localized abbreviation */
|
|
20
|
-
|
|
20
|
+
localized_abbreviation: z.string(),
|
|
21
21
|
/** Localized title */
|
|
22
|
-
|
|
22
|
+
localized_title: z.string(),
|
|
23
23
|
/** Organization ID of publisher */
|
|
24
24
|
organization_id: z.string().nullable().optional(),
|
|
25
25
|
/** Full title */
|
package/src/types/auth.ts
CHANGED
package/src/types/index.ts
CHANGED
|
@@ -18,5 +18,9 @@ export type { Collection } from '../schemas/collection';
|
|
|
18
18
|
|
|
19
19
|
// Re-export internal/non-API types
|
|
20
20
|
export type { ApiConfig } from './api-config';
|
|
21
|
-
export type {
|
|
21
|
+
export type {
|
|
22
|
+
AuthenticationState,
|
|
23
|
+
SignInWithYouVersionPermissionValues,
|
|
24
|
+
AuthenticationScopes,
|
|
25
|
+
} from './auth';
|
|
22
26
|
export type { HighlightColor } from './highlight';
|