@youversion/platform-core 0.6.0 → 0.8.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 +46 -0
- package/dist/index.cjs +473 -346
- package/dist/index.d.cts +106 -134
- package/dist/index.d.ts +106 -134
- package/dist/index.js +473 -343
- package/package.json +2 -1
- package/src/SignInWithYouVersionPKCE.ts +122 -0
- package/src/SignInWithYouVersionResult.ts +40 -39
- package/src/URLBuilder.ts +0 -21
- package/src/Users.ts +375 -94
- package/src/YouVersionPlatformConfiguration.ts +69 -25
- package/src/YouVersionUserInfo.ts +6 -6
- package/src/__tests__/SignInWithYouVersionPKCE.test.ts +418 -0
- package/src/__tests__/SignInWithYouVersionResult.test.ts +28 -0
- package/src/__tests__/StorageStrategy.test.ts +0 -72
- package/src/__tests__/URLBuilder.test.ts +0 -100
- package/src/__tests__/Users.test.ts +737 -0
- package/src/__tests__/YouVersionPlatformConfiguration.test.ts +192 -30
- package/src/__tests__/YouVersionUserInfo.test.ts +347 -0
- package/src/__tests__/highlights.test.ts +12 -12
- package/src/__tests__/mocks/browser.ts +90 -0
- package/src/__tests__/mocks/configuration.ts +53 -0
- package/src/__tests__/mocks/jwt.ts +93 -0
- package/src/__tests__/mocks/tokens.ts +69 -0
- package/src/index.ts +0 -3
- package/src/types/auth.ts +1 -0
- package/tsconfig.build.json +1 -1
- package/tsconfig.json +1 -1
- package/src/AuthenticationStrategy.ts +0 -78
- package/src/WebAuthenticationStrategy.ts +0 -127
- package/src/__tests__/authentication.test.ts +0 -185
- package/src/authentication.ts +0 -27
|
@@ -6,11 +6,23 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
|
6
6
|
// Mock globals before importing the module
|
|
7
7
|
const mockUUID = '550e8400-e29b-41d4-a716-446655440000';
|
|
8
8
|
const mockRandomUUID = vi.fn(() => mockUUID);
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
|
|
10
|
+
// Create a working localStorage mock
|
|
11
|
+
const mockStorage: Record<string, string> = {};
|
|
12
|
+
const mockGetItem = vi.fn((key: string) => mockStorage[key] || null);
|
|
13
|
+
const mockSetItem = vi.fn((key: string, value: string) => {
|
|
14
|
+
mockStorage[key] = value;
|
|
15
|
+
});
|
|
16
|
+
const mockRemoveItem = vi.fn((key: string) => {
|
|
17
|
+
delete mockStorage[key];
|
|
18
|
+
});
|
|
11
19
|
|
|
12
20
|
vi.stubGlobal('crypto', { randomUUID: mockRandomUUID });
|
|
13
|
-
vi.stubGlobal('localStorage', {
|
|
21
|
+
vi.stubGlobal('localStorage', {
|
|
22
|
+
getItem: mockGetItem,
|
|
23
|
+
setItem: mockSetItem,
|
|
24
|
+
removeItem: mockRemoveItem,
|
|
25
|
+
});
|
|
14
26
|
|
|
15
27
|
import { YouVersionPlatformConfiguration } from '../YouVersionPlatformConfiguration';
|
|
16
28
|
|
|
@@ -23,21 +35,20 @@ if (!envApiHost) {
|
|
|
23
35
|
|
|
24
36
|
describe('YouVersionPlatformConfiguration', () => {
|
|
25
37
|
beforeEach(() => {
|
|
26
|
-
// Reset all static properties
|
|
27
|
-
YouVersionPlatformConfiguration.appKey = null;
|
|
28
|
-
YouVersionPlatformConfiguration.installationId = null;
|
|
29
|
-
YouVersionPlatformConfiguration.setAccessToken(null);
|
|
30
|
-
YouVersionPlatformConfiguration.apiHost = envApiHost;
|
|
31
|
-
YouVersionPlatformConfiguration.isPreviewMode = false;
|
|
32
|
-
YouVersionPlatformConfiguration.previewUserInfo = null;
|
|
33
|
-
|
|
34
38
|
// Clear call history but keep implementation
|
|
35
39
|
mockRandomUUID.mockClear();
|
|
36
40
|
mockGetItem.mockClear();
|
|
37
41
|
mockSetItem.mockClear();
|
|
42
|
+
mockRemoveItem.mockClear();
|
|
43
|
+
|
|
44
|
+
// Clear mock storage
|
|
45
|
+
Object.keys(mockStorage).forEach((key) => delete mockStorage[key]);
|
|
38
46
|
|
|
39
|
-
//
|
|
40
|
-
|
|
47
|
+
// Reset all static properties
|
|
48
|
+
YouVersionPlatformConfiguration.appKey = null;
|
|
49
|
+
YouVersionPlatformConfiguration.installationId = null;
|
|
50
|
+
YouVersionPlatformConfiguration.saveAuthData(null, null, null, null);
|
|
51
|
+
YouVersionPlatformConfiguration.apiHost = envApiHost;
|
|
41
52
|
});
|
|
42
53
|
|
|
43
54
|
afterEach(() => {
|
|
@@ -80,7 +91,10 @@ describe('YouVersionPlatformConfiguration', () => {
|
|
|
80
91
|
|
|
81
92
|
it('should use existing UUID from localStorage when installationId is null', () => {
|
|
82
93
|
const existingUUID = 'existing-uuid-from-storage';
|
|
83
|
-
|
|
94
|
+
mockStorage['x-yvp-installation-id'] = existingUUID;
|
|
95
|
+
|
|
96
|
+
// Clear the call history after setting up localStorage but before the test
|
|
97
|
+
mockRandomUUID.mockClear();
|
|
84
98
|
|
|
85
99
|
YouVersionPlatformConfiguration.installationId = null;
|
|
86
100
|
|
|
@@ -96,16 +110,176 @@ describe('YouVersionPlatformConfiguration', () => {
|
|
|
96
110
|
});
|
|
97
111
|
});
|
|
98
112
|
|
|
99
|
-
describe('
|
|
100
|
-
it('should
|
|
113
|
+
describe('saveAuthData', () => {
|
|
114
|
+
it('should save and retrieve access token', () => {
|
|
101
115
|
expect(YouVersionPlatformConfiguration.accessToken).toBeNull();
|
|
102
116
|
|
|
103
117
|
const token = 'test-access-token';
|
|
104
|
-
YouVersionPlatformConfiguration.
|
|
118
|
+
YouVersionPlatformConfiguration.saveAuthData(token, null, null, null);
|
|
105
119
|
expect(YouVersionPlatformConfiguration.accessToken).toBe(token);
|
|
106
120
|
|
|
107
|
-
YouVersionPlatformConfiguration.
|
|
121
|
+
YouVersionPlatformConfiguration.saveAuthData(null, null, null, null);
|
|
122
|
+
expect(YouVersionPlatformConfiguration.accessToken).toBeNull();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should save and retrieve refresh token', () => {
|
|
126
|
+
expect(YouVersionPlatformConfiguration.refreshToken).toBeNull();
|
|
127
|
+
|
|
128
|
+
const refreshToken = 'test-refresh-token';
|
|
129
|
+
YouVersionPlatformConfiguration.saveAuthData(null, refreshToken, null, null);
|
|
130
|
+
expect(YouVersionPlatformConfiguration.refreshToken).toBe(refreshToken);
|
|
131
|
+
|
|
132
|
+
YouVersionPlatformConfiguration.saveAuthData(null, null, null, null);
|
|
133
|
+
expect(YouVersionPlatformConfiguration.refreshToken).toBeNull();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should save and retrieve expiry date', () => {
|
|
137
|
+
expect(YouVersionPlatformConfiguration.tokenExpiryDate).toBeNull();
|
|
138
|
+
|
|
139
|
+
const expiryDate = new Date('2024-12-31T23:59:59Z');
|
|
140
|
+
YouVersionPlatformConfiguration.saveAuthData(null, null, null, expiryDate);
|
|
141
|
+
expect(YouVersionPlatformConfiguration.tokenExpiryDate).toEqual(expiryDate);
|
|
142
|
+
|
|
143
|
+
YouVersionPlatformConfiguration.saveAuthData(null, null, null, null);
|
|
144
|
+
expect(YouVersionPlatformConfiguration.tokenExpiryDate).toBeNull();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should save all auth data together', () => {
|
|
148
|
+
const accessToken = 'test-access-token';
|
|
149
|
+
const refreshToken = 'test-refresh-token';
|
|
150
|
+
const idToken = 'test-id-token';
|
|
151
|
+
const expiryDate = new Date('2024-12-31T23:59:59Z');
|
|
152
|
+
|
|
153
|
+
YouVersionPlatformConfiguration.saveAuthData(accessToken, refreshToken, idToken, expiryDate);
|
|
154
|
+
|
|
155
|
+
expect(YouVersionPlatformConfiguration.accessToken).toBe(accessToken);
|
|
156
|
+
expect(YouVersionPlatformConfiguration.refreshToken).toBe(refreshToken);
|
|
157
|
+
expect(YouVersionPlatformConfiguration.idToken).toBe(idToken);
|
|
158
|
+
expect(YouVersionPlatformConfiguration.tokenExpiryDate).toEqual(expiryDate);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should handle partial updates without affecting other tokens', () => {
|
|
162
|
+
// Set up initial state
|
|
163
|
+
const initialAccess = 'initial-access';
|
|
164
|
+
const initialRefresh = 'initial-refresh';
|
|
165
|
+
const initialIdToken = 'initial-id-token';
|
|
166
|
+
const initialExpiry = new Date('2024-01-01T00:00:00Z');
|
|
167
|
+
YouVersionPlatformConfiguration.saveAuthData(
|
|
168
|
+
initialAccess,
|
|
169
|
+
initialRefresh,
|
|
170
|
+
initialIdToken,
|
|
171
|
+
initialExpiry,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
// Update only access token
|
|
175
|
+
const newAccess = 'new-access-token';
|
|
176
|
+
YouVersionPlatformConfiguration.saveAuthData(newAccess, null, null, null);
|
|
177
|
+
|
|
178
|
+
expect(YouVersionPlatformConfiguration.accessToken).toBe(newAccess);
|
|
179
|
+
expect(YouVersionPlatformConfiguration.refreshToken).toBeNull();
|
|
180
|
+
expect(YouVersionPlatformConfiguration.idToken).toBeNull();
|
|
181
|
+
expect(YouVersionPlatformConfiguration.tokenExpiryDate).toBeNull();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should properly serialize and deserialize dates', () => {
|
|
185
|
+
const originalDate = new Date('2024-06-15T14:30:45.123Z');
|
|
186
|
+
YouVersionPlatformConfiguration.saveAuthData(null, null, null, originalDate);
|
|
187
|
+
|
|
188
|
+
const retrievedDate = YouVersionPlatformConfiguration.tokenExpiryDate;
|
|
189
|
+
expect(retrievedDate).toEqual(originalDate);
|
|
190
|
+
expect(retrievedDate?.getTime()).toBe(originalDate.getTime());
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should call localStorage methods correctly', () => {
|
|
194
|
+
const accessToken = 'test-access';
|
|
195
|
+
const refreshToken = 'test-refresh';
|
|
196
|
+
const idToken = 'test-id';
|
|
197
|
+
const expiryDate = new Date('2024-12-31T23:59:59Z');
|
|
198
|
+
|
|
199
|
+
YouVersionPlatformConfiguration.saveAuthData(accessToken, refreshToken, idToken, expiryDate);
|
|
200
|
+
|
|
201
|
+
expect(mockSetItem).toHaveBeenCalledWith('accessToken', accessToken);
|
|
202
|
+
expect(mockSetItem).toHaveBeenCalledWith('refreshToken', refreshToken);
|
|
203
|
+
expect(mockSetItem).toHaveBeenCalledWith('idToken', idToken);
|
|
204
|
+
expect(mockSetItem).toHaveBeenCalledWith('expiryDate', expiryDate.toISOString());
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should call removeItem when tokens are null', () => {
|
|
208
|
+
// First set some values
|
|
209
|
+
YouVersionPlatformConfiguration.saveAuthData('access', 'refresh', 'id-token', new Date());
|
|
210
|
+
|
|
211
|
+
// Clear the mock calls
|
|
212
|
+
mockRemoveItem.mockClear();
|
|
213
|
+
|
|
214
|
+
// Now set to null
|
|
215
|
+
YouVersionPlatformConfiguration.saveAuthData(null, null, null, null);
|
|
216
|
+
|
|
217
|
+
expect(mockRemoveItem).toHaveBeenCalledWith('accessToken');
|
|
218
|
+
expect(mockRemoveItem).toHaveBeenCalledWith('refreshToken');
|
|
219
|
+
expect(mockRemoveItem).toHaveBeenCalledWith('idToken');
|
|
220
|
+
expect(mockRemoveItem).toHaveBeenCalledWith('expiryDate');
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('clearAuthTokens', () => {
|
|
225
|
+
it('should clear all auth tokens', () => {
|
|
226
|
+
// Set up initial auth data
|
|
227
|
+
const accessToken = 'test-access-token';
|
|
228
|
+
const refreshToken = 'test-refresh-token';
|
|
229
|
+
const idToken = 'test-id-token';
|
|
230
|
+
const expiryDate = new Date('2024-12-31T23:59:59Z');
|
|
231
|
+
YouVersionPlatformConfiguration.saveAuthData(accessToken, refreshToken, idToken, expiryDate);
|
|
232
|
+
|
|
233
|
+
// Verify data is set
|
|
234
|
+
expect(YouVersionPlatformConfiguration.accessToken).toBe(accessToken);
|
|
235
|
+
expect(YouVersionPlatformConfiguration.refreshToken).toBe(refreshToken);
|
|
236
|
+
expect(YouVersionPlatformConfiguration.idToken).toBe(idToken);
|
|
237
|
+
expect(YouVersionPlatformConfiguration.tokenExpiryDate).toEqual(expiryDate);
|
|
238
|
+
|
|
239
|
+
// Clear all tokens
|
|
240
|
+
YouVersionPlatformConfiguration.clearAuthTokens();
|
|
241
|
+
|
|
242
|
+
// Verify all tokens are null
|
|
108
243
|
expect(YouVersionPlatformConfiguration.accessToken).toBeNull();
|
|
244
|
+
expect(YouVersionPlatformConfiguration.refreshToken).toBeNull();
|
|
245
|
+
expect(YouVersionPlatformConfiguration.idToken).toBeNull();
|
|
246
|
+
expect(YouVersionPlatformConfiguration.tokenExpiryDate).toBeNull();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should call removeItem for all token types', () => {
|
|
250
|
+
// Set up some auth data first
|
|
251
|
+
YouVersionPlatformConfiguration.saveAuthData('access', 'refresh', 'id-token', new Date());
|
|
252
|
+
|
|
253
|
+
// Clear mock calls
|
|
254
|
+
mockRemoveItem.mockClear();
|
|
255
|
+
|
|
256
|
+
// Clear auth tokens
|
|
257
|
+
YouVersionPlatformConfiguration.clearAuthTokens();
|
|
258
|
+
|
|
259
|
+
// Verify removeItem was called for each token type
|
|
260
|
+
expect(mockRemoveItem).toHaveBeenCalledWith('accessToken');
|
|
261
|
+
expect(mockRemoveItem).toHaveBeenCalledWith('refreshToken');
|
|
262
|
+
expect(mockRemoveItem).toHaveBeenCalledWith('idToken');
|
|
263
|
+
expect(mockRemoveItem).toHaveBeenCalledWith('expiryDate');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should work when no tokens are previously set', () => {
|
|
267
|
+
// Ensure clean state
|
|
268
|
+
expect(YouVersionPlatformConfiguration.accessToken).toBeNull();
|
|
269
|
+
expect(YouVersionPlatformConfiguration.refreshToken).toBeNull();
|
|
270
|
+
expect(YouVersionPlatformConfiguration.idToken).toBeNull();
|
|
271
|
+
expect(YouVersionPlatformConfiguration.tokenExpiryDate).toBeNull();
|
|
272
|
+
|
|
273
|
+
// Should not throw when clearing empty state
|
|
274
|
+
expect(() => {
|
|
275
|
+
YouVersionPlatformConfiguration.clearAuthTokens();
|
|
276
|
+
}).not.toThrow();
|
|
277
|
+
|
|
278
|
+
// State should remain null
|
|
279
|
+
expect(YouVersionPlatformConfiguration.accessToken).toBeNull();
|
|
280
|
+
expect(YouVersionPlatformConfiguration.refreshToken).toBeNull();
|
|
281
|
+
expect(YouVersionPlatformConfiguration.idToken).toBeNull();
|
|
282
|
+
expect(YouVersionPlatformConfiguration.tokenExpiryDate).toBeNull();
|
|
109
283
|
});
|
|
110
284
|
});
|
|
111
285
|
|
|
@@ -120,18 +294,6 @@ describe('YouVersionPlatformConfiguration', () => {
|
|
|
120
294
|
});
|
|
121
295
|
});
|
|
122
296
|
|
|
123
|
-
describe('isPreviewMode', () => {
|
|
124
|
-
it('should get and set preview mode', () => {
|
|
125
|
-
expect(YouVersionPlatformConfiguration.isPreviewMode).toBe(false);
|
|
126
|
-
|
|
127
|
-
YouVersionPlatformConfiguration.isPreviewMode = true;
|
|
128
|
-
expect(YouVersionPlatformConfiguration.isPreviewMode).toBe(true);
|
|
129
|
-
|
|
130
|
-
YouVersionPlatformConfiguration.isPreviewMode = false;
|
|
131
|
-
expect(YouVersionPlatformConfiguration.isPreviewMode).toBe(false);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
297
|
describe('UUID generation edge cases', () => {
|
|
136
298
|
it('should generate valid UUID format', () => {
|
|
137
299
|
YouVersionPlatformConfiguration.installationId = null;
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import type { YouVersionUserInfoJSON } from '../YouVersionUserInfo';
|
|
3
|
+
import { YouVersionUserInfo } from '../YouVersionUserInfo';
|
|
4
|
+
|
|
5
|
+
describe('YouVersionUserInfo', () => {
|
|
6
|
+
describe('constructor', () => {
|
|
7
|
+
it('should create instance with valid data', () => {
|
|
8
|
+
const userData: YouVersionUserInfoJSON = {
|
|
9
|
+
name: 'John Doe',
|
|
10
|
+
id: 'user123',
|
|
11
|
+
avatar_url: 'https://example.com/avatar/{width}x{height}.jpg',
|
|
12
|
+
email: 'john.doe@example.com',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
16
|
+
|
|
17
|
+
expect(userInfo.name).toBe('John Doe');
|
|
18
|
+
expect(userInfo.userId).toBe('user123');
|
|
19
|
+
expect(userInfo.email).toBe('john.doe@example.com');
|
|
20
|
+
expect(userInfo.avatarUrlFormat).toBe('https://example.com/avatar/{width}x{height}.jpg');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should create instance with partial data', () => {
|
|
24
|
+
const userData: YouVersionUserInfoJSON = {
|
|
25
|
+
name: 'Jane',
|
|
26
|
+
id: 'user456',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
30
|
+
|
|
31
|
+
expect(userInfo.name).toBe('Jane');
|
|
32
|
+
expect(userInfo.email).toBeUndefined();
|
|
33
|
+
expect(userInfo.userId).toBe('user456');
|
|
34
|
+
expect(userInfo.avatarUrlFormat).toBeUndefined();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should create instance with empty object', () => {
|
|
38
|
+
const userData: YouVersionUserInfoJSON = {};
|
|
39
|
+
|
|
40
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
41
|
+
|
|
42
|
+
expect(userInfo.name).toBeUndefined();
|
|
43
|
+
expect(userInfo.email).toBeUndefined();
|
|
44
|
+
expect(userInfo.userId).toBeUndefined();
|
|
45
|
+
expect(userInfo.avatarUrlFormat).toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should throw error for null data', () => {
|
|
49
|
+
expect(() => {
|
|
50
|
+
// @ts-expect-error - Testing invalid input type
|
|
51
|
+
new YouVersionUserInfo(null);
|
|
52
|
+
}).toThrow('Invalid user data provided');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should throw error for undefined data', () => {
|
|
56
|
+
expect(() => {
|
|
57
|
+
// @ts-expect-error - Testing invalid input type
|
|
58
|
+
new YouVersionUserInfo(undefined);
|
|
59
|
+
}).toThrow('Invalid user data provided');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should throw error for non-object data', () => {
|
|
63
|
+
expect(() => {
|
|
64
|
+
// @ts-expect-error - Testing invalid input type
|
|
65
|
+
new YouVersionUserInfo('invalid data');
|
|
66
|
+
}).toThrow('Invalid user data provided');
|
|
67
|
+
|
|
68
|
+
expect(() => {
|
|
69
|
+
// @ts-expect-error - Testing invalid input type
|
|
70
|
+
new YouVersionUserInfo(123);
|
|
71
|
+
}).toThrow('Invalid user data provided');
|
|
72
|
+
|
|
73
|
+
expect(() => {
|
|
74
|
+
// @ts-expect-error - Testing invalid input type
|
|
75
|
+
new YouVersionUserInfo(true);
|
|
76
|
+
}).toThrow('Invalid user data provided');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('getAvatarUrl', () => {
|
|
81
|
+
it('should return null when avatarUrlFormat is undefined', () => {
|
|
82
|
+
const userData: YouVersionUserInfoJSON = {
|
|
83
|
+
name: 'John',
|
|
84
|
+
id: 'user123',
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
88
|
+
const avatarUrl = userInfo.getAvatarUrl();
|
|
89
|
+
|
|
90
|
+
expect(avatarUrl).toBeNull();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should return null when avatarUrlFormat is empty string', () => {
|
|
94
|
+
const userData: YouVersionUserInfoJSON = {
|
|
95
|
+
name: 'John',
|
|
96
|
+
id: 'user123',
|
|
97
|
+
avatar_url: '',
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
101
|
+
const avatarUrl = userInfo.getAvatarUrl();
|
|
102
|
+
|
|
103
|
+
expect(avatarUrl).toBeNull();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should replace width and height placeholders with default values', () => {
|
|
107
|
+
const userData: YouVersionUserInfoJSON = {
|
|
108
|
+
name: 'John',
|
|
109
|
+
avatar_url: 'https://example.com/avatar/{width}x{height}.jpg',
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
113
|
+
const avatarUrl = userInfo.getAvatarUrl();
|
|
114
|
+
|
|
115
|
+
expect(avatarUrl).toBeInstanceOf(URL);
|
|
116
|
+
expect(avatarUrl?.toString()).toBe('https://example.com/avatar/200x200.jpg');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should replace width and height placeholders with custom values', () => {
|
|
120
|
+
const userData: YouVersionUserInfoJSON = {
|
|
121
|
+
name: 'John',
|
|
122
|
+
avatar_url: 'https://example.com/avatar/{width}x{height}.jpg',
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
126
|
+
const avatarUrl = userInfo.getAvatarUrl(100, 150);
|
|
127
|
+
|
|
128
|
+
expect(avatarUrl).toBeInstanceOf(URL);
|
|
129
|
+
expect(avatarUrl?.toString()).toBe('https://example.com/avatar/100x150.jpg');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should handle URL starting with // by adding https:', () => {
|
|
133
|
+
const userData: YouVersionUserInfoJSON = {
|
|
134
|
+
name: 'John',
|
|
135
|
+
avatar_url: '//example.com/avatar/{width}x{height}.jpg',
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
139
|
+
const avatarUrl = userInfo.getAvatarUrl(50, 75);
|
|
140
|
+
|
|
141
|
+
expect(avatarUrl).toBeInstanceOf(URL);
|
|
142
|
+
expect(avatarUrl?.toString()).toBe('https://example.com/avatar/50x75.jpg');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should handle URL without placeholders', () => {
|
|
146
|
+
const userData: YouVersionUserInfoJSON = {
|
|
147
|
+
name: 'John',
|
|
148
|
+
avatar_url: 'https://example.com/static-avatar.jpg',
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
152
|
+
const avatarUrl = userInfo.getAvatarUrl(100, 100);
|
|
153
|
+
|
|
154
|
+
expect(avatarUrl).toBeInstanceOf(URL);
|
|
155
|
+
expect(avatarUrl?.toString()).toBe('https://example.com/static-avatar.jpg');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should handle URL with only width placeholder', () => {
|
|
159
|
+
const userData: YouVersionUserInfoJSON = {
|
|
160
|
+
name: 'John',
|
|
161
|
+
avatar_url: 'https://example.com/avatar/w{width}.jpg',
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
165
|
+
const avatarUrl = userInfo.getAvatarUrl(300, 400);
|
|
166
|
+
|
|
167
|
+
expect(avatarUrl).toBeInstanceOf(URL);
|
|
168
|
+
expect(avatarUrl?.toString()).toBe('https://example.com/avatar/w300.jpg');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should handle URL with only height placeholder', () => {
|
|
172
|
+
const userData: YouVersionUserInfoJSON = {
|
|
173
|
+
name: 'John',
|
|
174
|
+
avatar_url: 'https://example.com/avatar/h{height}.jpg',
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
178
|
+
const avatarUrl = userInfo.getAvatarUrl(300, 400);
|
|
179
|
+
|
|
180
|
+
expect(avatarUrl).toBeInstanceOf(URL);
|
|
181
|
+
expect(avatarUrl?.toString()).toBe('https://example.com/avatar/h400.jpg');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should not handle multiple occurrences of placeholders', () => {
|
|
185
|
+
const userData: YouVersionUserInfoJSON = {
|
|
186
|
+
name: 'John',
|
|
187
|
+
avatar_url: 'https://example.com/avatar/{width}/thumb_{height}_{width}.jpg',
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
191
|
+
const avatarUrl = userInfo.getAvatarUrl(120, 80);
|
|
192
|
+
|
|
193
|
+
expect(avatarUrl).toBeInstanceOf(URL);
|
|
194
|
+
// URL constructor encodes curly braces, so {width} becomes %7Bwidth%7D
|
|
195
|
+
expect(avatarUrl?.toString()).toBe('https://example.com/avatar/120/thumb_80_%7Bwidth%7D.jpg');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should return null for malformed URLs', () => {
|
|
199
|
+
const userData: YouVersionUserInfoJSON = {
|
|
200
|
+
name: 'John',
|
|
201
|
+
avatar_url: 'not-a-valid-url-{width}x{height}',
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
205
|
+
const avatarUrl = userInfo.getAvatarUrl();
|
|
206
|
+
|
|
207
|
+
expect(avatarUrl).toBeNull();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should handle zero width and height', () => {
|
|
211
|
+
const userData: YouVersionUserInfoJSON = {
|
|
212
|
+
name: 'John',
|
|
213
|
+
avatar_url: 'https://example.com/avatar/{width}x{height}.jpg',
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
217
|
+
const avatarUrl = userInfo.getAvatarUrl(0, 0);
|
|
218
|
+
|
|
219
|
+
expect(avatarUrl).toBeInstanceOf(URL);
|
|
220
|
+
expect(avatarUrl?.toString()).toBe('https://example.com/avatar/0x0.jpg');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should handle negative width and height', () => {
|
|
224
|
+
const userData: YouVersionUserInfoJSON = {
|
|
225
|
+
name: 'John',
|
|
226
|
+
avatar_url: 'https://example.com/avatar/{width}x{height}.jpg',
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
230
|
+
const avatarUrl = userInfo.getAvatarUrl(-100, -50);
|
|
231
|
+
|
|
232
|
+
expect(avatarUrl).toBeInstanceOf(URL);
|
|
233
|
+
expect(avatarUrl?.toString()).toBe('https://example.com/avatar/-100x-50.jpg');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should handle large width and height values', () => {
|
|
237
|
+
const userData: YouVersionUserInfoJSON = {
|
|
238
|
+
name: 'John',
|
|
239
|
+
avatar_url: 'https://example.com/avatar/{width}x{height}.jpg',
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
243
|
+
const avatarUrl = userInfo.getAvatarUrl(9999, 8888);
|
|
244
|
+
|
|
245
|
+
expect(avatarUrl).toBeInstanceOf(URL);
|
|
246
|
+
expect(avatarUrl?.toString()).toBe('https://example.com/avatar/9999x8888.jpg');
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('avatarUrl getter', () => {
|
|
251
|
+
it('should return same result as getAvatarUrl() with default parameters', () => {
|
|
252
|
+
const userData: YouVersionUserInfoJSON = {
|
|
253
|
+
name: 'John',
|
|
254
|
+
avatar_url: 'https://example.com/avatar/{width}x{height}.jpg',
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
258
|
+
const getterResult = userInfo.avatarUrl;
|
|
259
|
+
const methodResult = userInfo.getAvatarUrl();
|
|
260
|
+
|
|
261
|
+
expect(getterResult).toEqual(methodResult);
|
|
262
|
+
expect(getterResult?.toString()).toBe('https://example.com/avatar/200x200.jpg');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should return null when avatarUrlFormat is undefined', () => {
|
|
266
|
+
const userData: YouVersionUserInfoJSON = {
|
|
267
|
+
name: 'John',
|
|
268
|
+
id: 'user123',
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
272
|
+
|
|
273
|
+
expect(userInfo.avatarUrl).toBeNull();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should return null for malformed URLs', () => {
|
|
277
|
+
const userData: YouVersionUserInfoJSON = {
|
|
278
|
+
name: 'John',
|
|
279
|
+
avatar_url: 'invalid-url-format',
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
283
|
+
|
|
284
|
+
expect(userInfo.avatarUrl).toBeNull();
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe('edge cases and real-world scenarios', () => {
|
|
289
|
+
it('should handle typical YouVersion API response format', () => {
|
|
290
|
+
const userData: YouVersionUserInfoJSON = {
|
|
291
|
+
id: '12345',
|
|
292
|
+
name: 'Sarah Johnson',
|
|
293
|
+
avatar_url: '//images.youversion.com/users/12345/{width}x{height}/avatar.jpg',
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
297
|
+
|
|
298
|
+
expect(userInfo.userId).toBe('12345');
|
|
299
|
+
expect(userInfo.name).toBe('Sarah Johnson');
|
|
300
|
+
|
|
301
|
+
const avatarUrl = userInfo.getAvatarUrl(150, 150);
|
|
302
|
+
expect(avatarUrl?.toString()).toBe(
|
|
303
|
+
'https://images.youversion.com/users/12345/150x150/avatar.jpg',
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should handle user with single name', () => {
|
|
308
|
+
const userData: YouVersionUserInfoJSON = {
|
|
309
|
+
id: 'user789',
|
|
310
|
+
name: 'Madonna',
|
|
311
|
+
avatar_url: 'https://cdn.example.com/{width}/{height}/user.png',
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
315
|
+
|
|
316
|
+
expect(userInfo.name).toBe('Madonna');
|
|
317
|
+
expect(userInfo.userId).toBe('user789');
|
|
318
|
+
expect(userInfo.avatarUrl?.toString()).toBe('https://cdn.example.com/200/200/user.png');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should handle complex URL with query parameters', () => {
|
|
322
|
+
const userData: YouVersionUserInfoJSON = {
|
|
323
|
+
name: 'Alex',
|
|
324
|
+
avatar_url: 'https://api.service.com/v1/avatar?user=123&w={width}&h={height}&format=jpg',
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
328
|
+
const avatarUrl = userInfo.getAvatarUrl(64, 64);
|
|
329
|
+
|
|
330
|
+
expect(avatarUrl?.toString()).toBe(
|
|
331
|
+
'https://api.service.com/v1/avatar?user=123&w=64&h=64&format=jpg',
|
|
332
|
+
);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it('should handle URL with fragment identifier', () => {
|
|
336
|
+
const userData: YouVersionUserInfoJSON = {
|
|
337
|
+
name: 'Taylor',
|
|
338
|
+
avatar_url: 'https://example.com/avatar/{width}x{height}.jpg#section1',
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const userInfo = new YouVersionUserInfo(userData);
|
|
342
|
+
const avatarUrl = userInfo.getAvatarUrl(128, 128);
|
|
343
|
+
|
|
344
|
+
expect(avatarUrl?.toString()).toBe('https://example.com/avatar/128x128.jpg#section1');
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
});
|