@youversion/platform-core 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +7 -0
- package/.env.local +10 -0
- package/.turbo/turbo-build.log +18 -0
- package/CHANGELOG.md +7 -0
- package/LICENSE +201 -0
- package/README.md +369 -0
- package/dist/index.cjs +1330 -0
- package/dist/index.d.cts +737 -0
- package/dist/index.d.ts +737 -0
- package/dist/index.js +1286 -0
- package/package.json +46 -0
- package/src/AuthenticationStrategy.ts +78 -0
- package/src/SignInWithYouVersionResult.ts +53 -0
- package/src/StorageStrategy.ts +81 -0
- package/src/URLBuilder.ts +71 -0
- package/src/Users.ts +137 -0
- package/src/WebAuthenticationStrategy.ts +127 -0
- package/src/YouVersionAPI.ts +27 -0
- package/src/YouVersionPlatformConfiguration.ts +80 -0
- package/src/YouVersionUserInfo.ts +49 -0
- package/src/__tests__/StorageStrategy.test.ts +404 -0
- package/src/__tests__/URLBuilder.test.ts +289 -0
- package/src/__tests__/YouVersionPlatformConfiguration.test.ts +150 -0
- package/src/__tests__/authentication.test.ts +174 -0
- package/src/__tests__/bible.test.ts +356 -0
- package/src/__tests__/client.test.ts +109 -0
- package/src/__tests__/handlers.ts +41 -0
- package/src/__tests__/highlights.test.ts +485 -0
- package/src/__tests__/languages.test.ts +139 -0
- package/src/__tests__/setup.ts +17 -0
- package/src/authentication.ts +27 -0
- package/src/bible.ts +272 -0
- package/src/client.ts +162 -0
- package/src/highlight.ts +16 -0
- package/src/highlights.ts +173 -0
- package/src/index.ts +20 -0
- package/src/languages.ts +80 -0
- package/src/schemas/bible-index.ts +48 -0
- package/src/schemas/book.ts +34 -0
- package/src/schemas/chapter.ts +24 -0
- package/src/schemas/collection.ts +28 -0
- package/src/schemas/highlight.ts +23 -0
- package/src/schemas/index.ts +11 -0
- package/src/schemas/language.ts +38 -0
- package/src/schemas/passage.ts +14 -0
- package/src/schemas/user.ts +10 -0
- package/src/schemas/verse.ts +17 -0
- package/src/schemas/version.ts +31 -0
- package/src/schemas/votd.ts +10 -0
- package/src/types/api-config.ts +9 -0
- package/src/types/auth.ts +15 -0
- package/src/types/book.ts +116 -0
- package/src/types/chapter.ts +5 -0
- package/src/types/highlight.ts +9 -0
- package/src/types/index.ts +22 -0
- package/src/utils/constants.ts +219 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +12 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { YouVersionUserInfo } from './YouVersionUserInfo';
|
|
2
|
+
|
|
3
|
+
export class YouVersionPlatformConfiguration {
|
|
4
|
+
private static _appId: string | null = null;
|
|
5
|
+
private static _installationId: string | null = null;
|
|
6
|
+
private static _accessToken: string | null = null;
|
|
7
|
+
private static _apiHost: string = 'api-dev.youversion.com';
|
|
8
|
+
private static _isPreviewMode: boolean = false;
|
|
9
|
+
private static _previewUserInfo: YouVersionUserInfo | null = null;
|
|
10
|
+
|
|
11
|
+
private static getOrSetInstallationId(): string {
|
|
12
|
+
if (typeof window === 'undefined') {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const existingId = localStorage.getItem('x-yvp-installation-id');
|
|
17
|
+
if (existingId) {
|
|
18
|
+
return existingId;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const newId = crypto.randomUUID();
|
|
22
|
+
localStorage.setItem('x-yvp-installation-id', newId);
|
|
23
|
+
return newId;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static get appId(): string | null {
|
|
27
|
+
return this._appId;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static set appId(value: string | null) {
|
|
31
|
+
this._appId = value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static get installationId(): string {
|
|
35
|
+
if (!this._installationId) {
|
|
36
|
+
this._installationId = this.getOrSetInstallationId();
|
|
37
|
+
}
|
|
38
|
+
return this._installationId;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static set installationId(value: string | null) {
|
|
42
|
+
this._installationId = value || this.getOrSetInstallationId();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static setAccessToken(token: string | null): void {
|
|
46
|
+
// Validate token: if not null, must be a non-empty string
|
|
47
|
+
if (token !== null && (typeof token !== 'string' || token.trim().length === 0)) {
|
|
48
|
+
throw new Error('Access token must be a non-empty string or null');
|
|
49
|
+
}
|
|
50
|
+
this._accessToken = token;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static get accessToken(): string | null {
|
|
54
|
+
return this._accessToken;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static get apiHost(): string {
|
|
58
|
+
return this._apiHost;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static set apiHost(value: string) {
|
|
62
|
+
this._apiHost = value;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static get isPreviewMode(): boolean {
|
|
66
|
+
return this._isPreviewMode;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static set isPreviewMode(value: boolean) {
|
|
70
|
+
this._isPreviewMode = value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static get previewUserInfo(): YouVersionUserInfo | null {
|
|
74
|
+
return this._previewUserInfo;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
static set previewUserInfo(value: YouVersionUserInfo | null) {
|
|
78
|
+
this._previewUserInfo = value;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface YouVersionUserInfoJSON {
|
|
2
|
+
first_name?: string;
|
|
3
|
+
last_name?: string;
|
|
4
|
+
id?: string;
|
|
5
|
+
avatar_url?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class YouVersionUserInfo {
|
|
9
|
+
readonly firstName?: string;
|
|
10
|
+
readonly lastName?: string;
|
|
11
|
+
readonly userId?: string;
|
|
12
|
+
readonly avatarUrlFormat?: string;
|
|
13
|
+
|
|
14
|
+
constructor(data: YouVersionUserInfoJSON) {
|
|
15
|
+
if (!data || typeof data !== 'object') {
|
|
16
|
+
throw new Error('Invalid user data provided');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
this.firstName = data.first_name;
|
|
20
|
+
this.lastName = data.last_name;
|
|
21
|
+
this.userId = data.id;
|
|
22
|
+
this.avatarUrlFormat = data.avatar_url;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getAvatarUrl(width: number = 200, height: number = 200): URL | null {
|
|
26
|
+
if (!this.avatarUrlFormat) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let urlString = this.avatarUrlFormat;
|
|
31
|
+
|
|
32
|
+
if (urlString.startsWith('//')) {
|
|
33
|
+
urlString = 'https:' + urlString;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
urlString = urlString.replace('{width}', width.toString());
|
|
37
|
+
urlString = urlString.replace('{height}', height.toString());
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
return new URL(urlString);
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get avatarUrl(): URL | null {
|
|
47
|
+
return this.getAvatarUrl();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
5
|
+
import { SessionStorageStrategy, MemoryStorageStrategy } from '../StorageStrategy';
|
|
6
|
+
import { WebAuthenticationStrategy } from '../WebAuthenticationStrategy';
|
|
7
|
+
|
|
8
|
+
describe('SessionStorageStrategy', () => {
|
|
9
|
+
let strategy: SessionStorageStrategy;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
strategy = new SessionStorageStrategy();
|
|
13
|
+
sessionStorage.clear();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
sessionStorage.clear();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('setItem', () => {
|
|
21
|
+
it('should store item in sessionStorage', () => {
|
|
22
|
+
strategy.setItem('testKey', 'testValue');
|
|
23
|
+
|
|
24
|
+
expect(sessionStorage.getItem('testKey')).toBe('testValue');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should overwrite existing item', () => {
|
|
28
|
+
strategy.setItem('testKey', 'firstValue');
|
|
29
|
+
strategy.setItem('testKey', 'secondValue');
|
|
30
|
+
|
|
31
|
+
expect(sessionStorage.getItem('testKey')).toBe('secondValue');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should handle empty string value', () => {
|
|
35
|
+
strategy.setItem('emptyKey', '');
|
|
36
|
+
|
|
37
|
+
expect(sessionStorage.getItem('emptyKey')).toBe('');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should handle special characters in key and value', () => {
|
|
41
|
+
const key = 'key-with-special_chars.123';
|
|
42
|
+
const value = 'value with spaces & special chars!';
|
|
43
|
+
|
|
44
|
+
strategy.setItem(key, value);
|
|
45
|
+
|
|
46
|
+
expect(sessionStorage.getItem(key)).toBe(value);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('getItem', () => {
|
|
51
|
+
it('should retrieve item from sessionStorage', () => {
|
|
52
|
+
sessionStorage.setItem('testKey', 'testValue');
|
|
53
|
+
|
|
54
|
+
const result = strategy.getItem('testKey');
|
|
55
|
+
|
|
56
|
+
expect(result).toBe('testValue');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return null for non-existent key', () => {
|
|
60
|
+
const result = strategy.getItem('nonExistentKey');
|
|
61
|
+
|
|
62
|
+
expect(result).toBeNull();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should return empty string for empty value', () => {
|
|
66
|
+
sessionStorage.setItem('emptyKey', '');
|
|
67
|
+
|
|
68
|
+
const result = strategy.getItem('emptyKey');
|
|
69
|
+
|
|
70
|
+
expect(result).toBe('');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('removeItem', () => {
|
|
75
|
+
it('should remove item from sessionStorage', () => {
|
|
76
|
+
sessionStorage.setItem('testKey', 'testValue');
|
|
77
|
+
|
|
78
|
+
strategy.removeItem('testKey');
|
|
79
|
+
|
|
80
|
+
expect(sessionStorage.getItem('testKey')).toBeNull();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should not throw error when removing non-existent key', () => {
|
|
84
|
+
expect(() => {
|
|
85
|
+
strategy.removeItem('nonExistentKey');
|
|
86
|
+
}).not.toThrow();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should only remove specified key', () => {
|
|
90
|
+
sessionStorage.setItem('key1', 'value1');
|
|
91
|
+
sessionStorage.setItem('key2', 'value2');
|
|
92
|
+
|
|
93
|
+
strategy.removeItem('key1');
|
|
94
|
+
|
|
95
|
+
expect(sessionStorage.getItem('key1')).toBeNull();
|
|
96
|
+
expect(sessionStorage.getItem('key2')).toBe('value2');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('clear', () => {
|
|
101
|
+
it('should clear all items from sessionStorage', () => {
|
|
102
|
+
sessionStorage.setItem('key1', 'value1');
|
|
103
|
+
sessionStorage.setItem('key2', 'value2');
|
|
104
|
+
sessionStorage.setItem('key3', 'value3');
|
|
105
|
+
|
|
106
|
+
strategy.clear();
|
|
107
|
+
|
|
108
|
+
expect(sessionStorage.length).toBe(0);
|
|
109
|
+
expect(sessionStorage.getItem('key1')).toBeNull();
|
|
110
|
+
expect(sessionStorage.getItem('key2')).toBeNull();
|
|
111
|
+
expect(sessionStorage.getItem('key3')).toBeNull();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should work when sessionStorage is already empty', () => {
|
|
115
|
+
expect(() => {
|
|
116
|
+
strategy.clear();
|
|
117
|
+
}).not.toThrow();
|
|
118
|
+
|
|
119
|
+
expect(sessionStorage.length).toBe(0);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('Environment compatibility', () => {
|
|
124
|
+
it('should handle environment without sessionStorage gracefully', () => {
|
|
125
|
+
const originalSessionStorage = global.sessionStorage;
|
|
126
|
+
// @ts-expect-error - Testing undefined sessionStorage
|
|
127
|
+
delete global.sessionStorage;
|
|
128
|
+
|
|
129
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
|
|
130
|
+
|
|
131
|
+
const strategy = new SessionStorageStrategy();
|
|
132
|
+
|
|
133
|
+
// Should not throw
|
|
134
|
+
expect(() => strategy.setItem('test', 'value')).not.toThrow();
|
|
135
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
136
|
+
'SessionStorage is not available in this environment',
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
expect(strategy.getItem('test')).toBeNull();
|
|
140
|
+
expect(() => strategy.removeItem('test')).not.toThrow();
|
|
141
|
+
expect(() => strategy.clear()).not.toThrow();
|
|
142
|
+
|
|
143
|
+
consoleSpy.mockRestore();
|
|
144
|
+
global.sessionStorage = originalSessionStorage;
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('MemoryStorageStrategy', () => {
|
|
150
|
+
let strategy: MemoryStorageStrategy;
|
|
151
|
+
|
|
152
|
+
beforeEach(() => {
|
|
153
|
+
strategy = new MemoryStorageStrategy();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('setItem', () => {
|
|
157
|
+
it('should store item in memory', () => {
|
|
158
|
+
strategy.setItem('testKey', 'testValue');
|
|
159
|
+
|
|
160
|
+
expect(strategy.getItem('testKey')).toBe('testValue');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should overwrite existing item', () => {
|
|
164
|
+
strategy.setItem('testKey', 'firstValue');
|
|
165
|
+
strategy.setItem('testKey', 'secondValue');
|
|
166
|
+
|
|
167
|
+
expect(strategy.getItem('testKey')).toBe('secondValue');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should handle empty string value', () => {
|
|
171
|
+
strategy.setItem('emptyKey', '');
|
|
172
|
+
|
|
173
|
+
expect(strategy.getItem('emptyKey')).toBe('');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should handle multiple keys independently', () => {
|
|
177
|
+
strategy.setItem('key1', 'value1');
|
|
178
|
+
strategy.setItem('key2', 'value2');
|
|
179
|
+
|
|
180
|
+
expect(strategy.getItem('key1')).toBe('value1');
|
|
181
|
+
expect(strategy.getItem('key2')).toBe('value2');
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('getItem', () => {
|
|
186
|
+
it('should retrieve item from memory', () => {
|
|
187
|
+
strategy.setItem('testKey', 'testValue');
|
|
188
|
+
|
|
189
|
+
const result = strategy.getItem('testKey');
|
|
190
|
+
|
|
191
|
+
expect(result).toBe('testValue');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should return null for non-existent key', () => {
|
|
195
|
+
const result = strategy.getItem('nonExistentKey');
|
|
196
|
+
|
|
197
|
+
expect(result).toBeNull();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should return empty string for empty value', () => {
|
|
201
|
+
strategy.setItem('emptyKey', '');
|
|
202
|
+
|
|
203
|
+
const result = strategy.getItem('emptyKey');
|
|
204
|
+
|
|
205
|
+
expect(result).toBe('');
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('removeItem', () => {
|
|
210
|
+
it('should remove item from memory', () => {
|
|
211
|
+
strategy.setItem('testKey', 'testValue');
|
|
212
|
+
|
|
213
|
+
strategy.removeItem('testKey');
|
|
214
|
+
|
|
215
|
+
expect(strategy.getItem('testKey')).toBeNull();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('should not throw error when removing non-existent key', () => {
|
|
219
|
+
expect(() => {
|
|
220
|
+
strategy.removeItem('nonExistentKey');
|
|
221
|
+
}).not.toThrow();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should only remove specified key', () => {
|
|
225
|
+
strategy.setItem('key1', 'value1');
|
|
226
|
+
strategy.setItem('key2', 'value2');
|
|
227
|
+
|
|
228
|
+
strategy.removeItem('key1');
|
|
229
|
+
|
|
230
|
+
expect(strategy.getItem('key1')).toBeNull();
|
|
231
|
+
expect(strategy.getItem('key2')).toBe('value2');
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe('clear', () => {
|
|
236
|
+
it('should clear all items from memory', () => {
|
|
237
|
+
strategy.setItem('key1', 'value1');
|
|
238
|
+
strategy.setItem('key2', 'value2');
|
|
239
|
+
strategy.setItem('key3', 'value3');
|
|
240
|
+
|
|
241
|
+
strategy.clear();
|
|
242
|
+
|
|
243
|
+
expect(strategy.getItem('key1')).toBeNull();
|
|
244
|
+
expect(strategy.getItem('key2')).toBeNull();
|
|
245
|
+
expect(strategy.getItem('key3')).toBeNull();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should work when storage is already empty', () => {
|
|
249
|
+
expect(() => {
|
|
250
|
+
strategy.clear();
|
|
251
|
+
}).not.toThrow();
|
|
252
|
+
|
|
253
|
+
expect(strategy.getItem('anyKey')).toBeNull();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('Isolation', () => {
|
|
258
|
+
it('should not share data between instances', () => {
|
|
259
|
+
const strategy1 = new MemoryStorageStrategy();
|
|
260
|
+
const strategy2 = new MemoryStorageStrategy();
|
|
261
|
+
|
|
262
|
+
strategy1.setItem('key', 'value1');
|
|
263
|
+
strategy2.setItem('key', 'value2');
|
|
264
|
+
|
|
265
|
+
expect(strategy1.getItem('key')).toBe('value1');
|
|
266
|
+
expect(strategy2.getItem('key')).toBe('value2');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should not persist data after instance is garbage collected', () => {
|
|
270
|
+
let tempStrategy: MemoryStorageStrategy | null = new MemoryStorageStrategy();
|
|
271
|
+
tempStrategy.setItem('key', 'value');
|
|
272
|
+
|
|
273
|
+
// Create new instance
|
|
274
|
+
tempStrategy = new MemoryStorageStrategy();
|
|
275
|
+
|
|
276
|
+
// Data from old instance should not be accessible
|
|
277
|
+
expect(tempStrategy.getItem('key')).toBeNull();
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe('Memory vs SessionStorage isolation', () => {
|
|
282
|
+
it('should not affect sessionStorage', () => {
|
|
283
|
+
const memStrategy = new MemoryStorageStrategy();
|
|
284
|
+
sessionStorage.clear();
|
|
285
|
+
|
|
286
|
+
memStrategy.setItem('testKey', 'testValue');
|
|
287
|
+
|
|
288
|
+
// Should not affect sessionStorage
|
|
289
|
+
expect(sessionStorage.getItem('testKey')).toBeNull();
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe('WebAuthenticationStrategy - Custom Storage', () => {
|
|
295
|
+
beforeEach(() => {
|
|
296
|
+
sessionStorage.clear();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
afterEach(() => {
|
|
300
|
+
sessionStorage.clear();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('should use SessionStorageStrategy by default', () => {
|
|
304
|
+
const strategy = new WebAuthenticationStrategy();
|
|
305
|
+
|
|
306
|
+
// The strategy should use sessionStorage internally (we can't access private field directly)
|
|
307
|
+
// But we can verify it works by checking that data persists in sessionStorage
|
|
308
|
+
// This is an indirect test
|
|
309
|
+
expect(strategy).toBeInstanceOf(WebAuthenticationStrategy);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should accept custom storage strategy in constructor', () => {
|
|
313
|
+
const customStorage = new MemoryStorageStrategy();
|
|
314
|
+
|
|
315
|
+
const strategy = new WebAuthenticationStrategy({
|
|
316
|
+
storage: customStorage,
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
expect(strategy).toBeInstanceOf(WebAuthenticationStrategy);
|
|
320
|
+
// The strategy should use the custom storage
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should use custom MemoryStorageStrategy when provided', () => {
|
|
324
|
+
const memoryStorage = new MemoryStorageStrategy();
|
|
325
|
+
|
|
326
|
+
const strategy = new WebAuthenticationStrategy({
|
|
327
|
+
storage: memoryStorage,
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
expect(strategy).toBeInstanceOf(WebAuthenticationStrategy);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should support custom storage implementations', () => {
|
|
334
|
+
// Create a custom storage implementation
|
|
335
|
+
class CustomStorageStrategy {
|
|
336
|
+
private data = new Map<string, string>();
|
|
337
|
+
|
|
338
|
+
setItem(key: string, value: string): void {
|
|
339
|
+
this.data.set(key, value);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
getItem(key: string): string | null {
|
|
343
|
+
return this.data.get(key) ?? null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
removeItem(key: string): void {
|
|
347
|
+
this.data.delete(key);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
clear(): void {
|
|
351
|
+
this.data.clear();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const customStorage = new CustomStorageStrategy();
|
|
356
|
+
|
|
357
|
+
const strategy = new WebAuthenticationStrategy({
|
|
358
|
+
storage: customStorage,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
expect(strategy).toBeInstanceOf(WebAuthenticationStrategy);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
describe('StorageStrategy Interface Compliance', () => {
|
|
366
|
+
const testCases = [
|
|
367
|
+
{ name: 'SessionStorageStrategy', factory: () => new SessionStorageStrategy() },
|
|
368
|
+
{ name: 'MemoryStorageStrategy', factory: () => new MemoryStorageStrategy() },
|
|
369
|
+
];
|
|
370
|
+
|
|
371
|
+
testCases.forEach(({ name, factory }) => {
|
|
372
|
+
describe(name, () => {
|
|
373
|
+
it('should implement setItem method', () => {
|
|
374
|
+
const strategy = factory();
|
|
375
|
+
expect(typeof strategy.setItem).toBe('function');
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('should implement getItem method', () => {
|
|
379
|
+
const strategy = factory();
|
|
380
|
+
expect(typeof strategy.getItem).toBe('function');
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('should implement removeItem method', () => {
|
|
384
|
+
const strategy = factory();
|
|
385
|
+
expect(typeof strategy.removeItem).toBe('function');
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('should implement clear method', () => {
|
|
389
|
+
const strategy = factory();
|
|
390
|
+
expect(typeof strategy.clear).toBe('function');
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('should support complete lifecycle: set -> get -> remove', () => {
|
|
394
|
+
const strategy = factory();
|
|
395
|
+
|
|
396
|
+
strategy.setItem('lifecycleKey', 'lifecycleValue');
|
|
397
|
+
expect(strategy.getItem('lifecycleKey')).toBe('lifecycleValue');
|
|
398
|
+
|
|
399
|
+
strategy.removeItem('lifecycleKey');
|
|
400
|
+
expect(strategy.getItem('lifecycleKey')).toBeNull();
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
});
|