@umituz/react-native-storage 2.0.0 → 2.3.2
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/package.json +23 -7
- package/src/__tests__/integration.test.ts +391 -0
- package/src/__tests__/mocks/asyncStorage.mock.ts +52 -0
- package/src/__tests__/performance.test.ts +351 -0
- package/src/__tests__/setup.ts +63 -0
- package/src/application/ports/IStorageRepository.ts +0 -12
- package/src/domain/entities/StorageResult.ts +1 -3
- package/src/domain/entities/__tests__/CachedValue.test.ts +149 -0
- package/src/domain/entities/__tests__/StorageResult.test.ts +122 -0
- package/src/domain/errors/StorageError.ts +0 -2
- package/src/domain/errors/__tests__/StorageError.test.ts +127 -0
- package/src/domain/utils/__tests__/devUtils.test.ts +97 -0
- package/src/domain/utils/devUtils.ts +37 -0
- package/src/domain/value-objects/StorageKey.ts +27 -29
- package/src/index.ts +9 -1
- package/src/infrastructure/adapters/StorageService.ts +8 -6
- package/src/infrastructure/repositories/AsyncStorageRepository.ts +27 -108
- package/src/infrastructure/repositories/BaseStorageOperations.ts +101 -0
- package/src/infrastructure/repositories/BatchStorageOperations.ts +42 -0
- package/src/infrastructure/repositories/StringStorageOperations.ts +44 -0
- package/src/infrastructure/repositories/__tests__/AsyncStorageRepository.test.ts +169 -0
- package/src/infrastructure/repositories/__tests__/BaseStorageOperations.test.ts +200 -0
- package/src/presentation/hooks/CacheStorageOperations.ts +95 -0
- package/src/presentation/hooks/__tests__/usePersistentCache.test.ts +404 -0
- package/src/presentation/hooks/__tests__/useStorage.test.ts +246 -0
- package/src/presentation/hooks/__tests__/useStorageState.test.ts +292 -0
- package/src/presentation/hooks/useCacheState.ts +55 -0
- package/src/presentation/hooks/usePersistentCache.ts +30 -39
- package/src/presentation/hooks/useStorage.ts +4 -3
- package/src/presentation/hooks/useStorageState.ts +24 -8
- package/src/presentation/hooks/useStore.ts +3 -1
- package/src/types/global.d.ts +40 -0
- package/LICENSE +0 -22
- package/src/presentation/hooks/usePersistedState.ts +0 -34
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-storage",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "Zustand state management with AsyncStorage persistence and type-safe storage operations for React Native",
|
|
5
|
-
"main": "./
|
|
6
|
-
"types": "./
|
|
5
|
+
"main": "./lib/index.js",
|
|
6
|
+
"types": "./lib/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
8
9
|
"typecheck": "tsc --noEmit",
|
|
9
|
-
"lint": "tsc --noEmit"
|
|
10
|
+
"lint": "tsc --noEmit",
|
|
11
|
+
"test": "jest",
|
|
12
|
+
"test:watch": "jest --watch",
|
|
13
|
+
"test:coverage": "jest --coverage",
|
|
14
|
+
"test:ci": "jest --ci --coverage --watchAll=false"
|
|
10
15
|
},
|
|
11
16
|
"keywords": [
|
|
12
17
|
"react-native",
|
|
@@ -27,16 +32,27 @@
|
|
|
27
32
|
},
|
|
28
33
|
"peerDependencies": {
|
|
29
34
|
"@react-native-async-storage/async-storage": "^1.21.0",
|
|
30
|
-
"zustand": "^5.0.0",
|
|
31
35
|
"react": ">=18.2.0",
|
|
32
|
-
"react-native": ">=0.74.0"
|
|
36
|
+
"react-native": ">=0.74.0",
|
|
37
|
+
"zustand": "^5.0.0"
|
|
33
38
|
},
|
|
34
39
|
"devDependencies": {
|
|
35
40
|
"@react-native-async-storage/async-storage": "^1.24.0",
|
|
41
|
+
"@testing-library/jest-native": "^5.4.3",
|
|
42
|
+
"@testing-library/react": "^13.4.0",
|
|
43
|
+
"@testing-library/react-hooks": "^8.0.1",
|
|
44
|
+
"@types/jest": "^29.5.8",
|
|
36
45
|
"@types/react": "^18.2.45",
|
|
37
46
|
"@types/react-native": "^0.73.0",
|
|
47
|
+
"find-up": "^8.0.0",
|
|
48
|
+
"jest": "^29.7.0",
|
|
49
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
50
|
+
"p-limit": "^7.2.0",
|
|
51
|
+
"p-try": "^3.0.0",
|
|
52
|
+
"path-exists": "^5.0.0",
|
|
38
53
|
"react": "^18.2.0",
|
|
39
54
|
"react-native": "^0.74.0",
|
|
55
|
+
"ts-jest": "^29.4.6",
|
|
40
56
|
"typescript": "^5.3.3",
|
|
41
57
|
"zustand": "^5.0.0"
|
|
42
58
|
},
|
|
@@ -44,9 +60,9 @@
|
|
|
44
60
|
"access": "public"
|
|
45
61
|
},
|
|
46
62
|
"files": [
|
|
63
|
+
"lib",
|
|
47
64
|
"src",
|
|
48
65
|
"README.md",
|
|
49
66
|
"LICENSE"
|
|
50
67
|
]
|
|
51
68
|
}
|
|
52
|
-
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* End-to-end tests for the storage system
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { renderHook, act } from '@testing-library/react-hooks';
|
|
8
|
+
import { useStorage, useStorageState, usePersistentCache } from '../../index';
|
|
9
|
+
import { StorageKey } from '../../domain/value-objects/StorageKey';
|
|
10
|
+
import { AsyncStorage } from '../mocks/asyncStorage.mock';
|
|
11
|
+
|
|
12
|
+
describe('Integration Tests', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
(AsyncStorage as any).__clear();
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
jest.spyOn(Date, 'now').mockReturnValue(1000000);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
jest.restoreAllMocks();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('Storage Operations Integration', () => {
|
|
24
|
+
it('should work with complex data types', async () => {
|
|
25
|
+
const { result: storageHook } = renderHook(() => useStorage());
|
|
26
|
+
|
|
27
|
+
// Test with complex object
|
|
28
|
+
const complexData = {
|
|
29
|
+
user: {
|
|
30
|
+
id: 1,
|
|
31
|
+
name: 'John Doe',
|
|
32
|
+
preferences: {
|
|
33
|
+
theme: 'dark',
|
|
34
|
+
notifications: true,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
metadata: {
|
|
38
|
+
version: '1.0.0',
|
|
39
|
+
timestamp: Date.now(),
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Set complex data
|
|
44
|
+
const setSuccess = await storageHook.current.setItem('complex-data', complexData);
|
|
45
|
+
expect(setSuccess).toBe(true);
|
|
46
|
+
|
|
47
|
+
// Get complex data
|
|
48
|
+
const retrievedData = await storageHook.current.getItem('complex-data', {});
|
|
49
|
+
expect(retrievedData).toEqual(complexData);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should work with arrays', async () => {
|
|
53
|
+
const { result: storageHook } = renderHook(() => useStorage());
|
|
54
|
+
|
|
55
|
+
const arrayData = [
|
|
56
|
+
{ id: 1, name: 'Item 1' },
|
|
57
|
+
{ id: 2, name: 'Item 2' },
|
|
58
|
+
{ id: 3, name: 'Item 3' },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
// Set array
|
|
62
|
+
const setSuccess = await storageHook.current.setItem('array-data', arrayData);
|
|
63
|
+
expect(setSuccess).toBe(true);
|
|
64
|
+
|
|
65
|
+
// Get array
|
|
66
|
+
const retrievedData = await storageHook.current.getItem('array-data', []);
|
|
67
|
+
expect(retrievedData).toEqual(arrayData);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should work with StorageKey enum', async () => {
|
|
71
|
+
const { result: storageHook } = renderHook(() => useStorage());
|
|
72
|
+
|
|
73
|
+
const preferences = {
|
|
74
|
+
theme: 'light',
|
|
75
|
+
language: 'en',
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Set with enum key
|
|
79
|
+
const setSuccess = await storageHook.current.setItem(
|
|
80
|
+
StorageKey.USER_PREFERENCES,
|
|
81
|
+
preferences
|
|
82
|
+
);
|
|
83
|
+
expect(setSuccess).toBe(true);
|
|
84
|
+
|
|
85
|
+
// Get with enum key
|
|
86
|
+
const retrievedData = await storageHook.current.getItem(
|
|
87
|
+
StorageKey.USER_PREFERENCES,
|
|
88
|
+
{}
|
|
89
|
+
);
|
|
90
|
+
expect(retrievedData).toEqual(preferences);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('State Management Integration', () => {
|
|
95
|
+
it('should sync state with storage', async () => {
|
|
96
|
+
const key = 'sync-test';
|
|
97
|
+
const initialValue = 'initial';
|
|
98
|
+
const updatedValue = 'updated';
|
|
99
|
+
|
|
100
|
+
// Set initial value in storage
|
|
101
|
+
await AsyncStorage.setItem(key, JSON.stringify(initialValue));
|
|
102
|
+
|
|
103
|
+
const { result: stateHook, waitForNextUpdate } = renderHook(() =>
|
|
104
|
+
useStorageState(key, initialValue)
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// Wait for initial load
|
|
108
|
+
await act(async () => {
|
|
109
|
+
await waitForNextUpdate();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(stateHook.current[0]).toBe(initialValue);
|
|
113
|
+
|
|
114
|
+
// Update state
|
|
115
|
+
await act(async () => {
|
|
116
|
+
await stateHook.current[1](updatedValue);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(stateHook.current[0]).toBe(updatedValue);
|
|
120
|
+
|
|
121
|
+
// Verify storage
|
|
122
|
+
const stored = await AsyncStorage.getItem(key);
|
|
123
|
+
expect(JSON.parse(stored!)).toBe(updatedValue);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should handle multiple state hooks', async () => {
|
|
127
|
+
const { result: hook1, waitForNextUpdate: waitFor1 } = renderHook(() =>
|
|
128
|
+
useStorageState('key1', 'value1')
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const { result: hook2, waitForNextUpdate: waitFor2 } = renderHook(() =>
|
|
132
|
+
useStorageState('key2', 'value2')
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// Wait for both to load
|
|
136
|
+
await act(async () => {
|
|
137
|
+
await Promise.all([waitFor1(), waitFor2()]);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(hook1.current[0]).toBe('value1');
|
|
141
|
+
expect(hook2.current[0]).toBe('value2');
|
|
142
|
+
|
|
143
|
+
// Update first hook
|
|
144
|
+
await act(async () => {
|
|
145
|
+
await hook1.current[1]('updated1');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
expect(hook1.current[0]).toBe('updated1');
|
|
149
|
+
expect(hook2.current[0]).toBe('value2'); // Should not affect second hook
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('Cache Integration', () => {
|
|
154
|
+
it('should cache and retrieve data with TTL', async () => {
|
|
155
|
+
const key = 'cache-integration';
|
|
156
|
+
const data = { posts: [{ id: 1, title: 'Test Post' }] };
|
|
157
|
+
const ttl = 60000; // 1 minute
|
|
158
|
+
|
|
159
|
+
const { result: cacheHook, waitForNextUpdate } = renderHook(() =>
|
|
160
|
+
usePersistentCache(key, { ttl })
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
await act(async () => {
|
|
164
|
+
await waitForNextUpdate();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Should be empty initially
|
|
168
|
+
expect(cacheHook.current.data).toBeNull();
|
|
169
|
+
expect(cacheHook.current.isExpired).toBe(true);
|
|
170
|
+
|
|
171
|
+
// Set data
|
|
172
|
+
await act(async () => {
|
|
173
|
+
await cacheHook.current.setData(data);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect(cacheHook.current.data).toEqual(data);
|
|
177
|
+
expect(cacheHook.current.isExpired).toBe(false);
|
|
178
|
+
|
|
179
|
+
// Create new hook instance to test persistence
|
|
180
|
+
const { result: newCacheHook, waitForNextUpdate: waitForNew } = renderHook(() =>
|
|
181
|
+
usePersistentCache(key, { ttl })
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
await act(async () => {
|
|
185
|
+
await waitForNew();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(newCacheHook.current.data).toEqual(data);
|
|
189
|
+
expect(newCacheHook.current.isExpired).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should handle cache expiration', async () => {
|
|
193
|
+
const key = 'cache-expiration';
|
|
194
|
+
const data = { test: 'value' };
|
|
195
|
+
const ttl = 1000; // 1 second
|
|
196
|
+
|
|
197
|
+
const { result: cacheHook, waitForNextUpdate } = renderHook(() =>
|
|
198
|
+
usePersistentCache(key, { ttl })
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Set data
|
|
202
|
+
await act(async () => {
|
|
203
|
+
await waitForNextUpdate();
|
|
204
|
+
await cacheHook.current.setData(data);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(cacheHook.current.isExpired).toBe(false);
|
|
208
|
+
|
|
209
|
+
// Simulate time passage
|
|
210
|
+
jest.spyOn(Date, 'now').mockReturnValue(1000000 + 2000); // 2 seconds later
|
|
211
|
+
|
|
212
|
+
// Create new hook to test expiration
|
|
213
|
+
const { result: newCacheHook, waitForNextUpdate: waitForNew } = renderHook(() =>
|
|
214
|
+
usePersistentCache(key, { ttl })
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
await act(async () => {
|
|
218
|
+
await waitForNew();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
expect(newCacheHook.current.data).toEqual(data);
|
|
222
|
+
expect(newCacheHook.current.isExpired).toBe(true);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should handle cache versioning', async () => {
|
|
226
|
+
const key = 'cache-versioning';
|
|
227
|
+
const dataV1 = { version: 1, data: 'old' };
|
|
228
|
+
const dataV2 = { version: 2, data: 'new' };
|
|
229
|
+
|
|
230
|
+
// Set cache with version 1
|
|
231
|
+
await AsyncStorage.setItem(key, JSON.stringify({
|
|
232
|
+
value: dataV1,
|
|
233
|
+
timestamp: 1000000 - 10000,
|
|
234
|
+
ttl: 60000,
|
|
235
|
+
version: 1,
|
|
236
|
+
}));
|
|
237
|
+
|
|
238
|
+
// Load with version 1
|
|
239
|
+
const { result: cacheHook1, waitForNextUpdate: waitFor1 } = renderHook(() =>
|
|
240
|
+
usePersistentCache(key, { version: 1 })
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
await act(async () => {
|
|
244
|
+
await waitFor1();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
expect(cacheHook1.current.data).toEqual(dataV1);
|
|
248
|
+
expect(cacheHook1.current.isExpired).toBe(false);
|
|
249
|
+
|
|
250
|
+
// Load with version 2 (should be expired)
|
|
251
|
+
const { result: cacheHook2, waitForNextUpdate: waitFor2 } = renderHook(() =>
|
|
252
|
+
usePersistentCache(key, { version: 2 })
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
await act(async () => {
|
|
256
|
+
await waitFor2();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
expect(cacheHook2.current.data).toEqual(dataV1);
|
|
260
|
+
expect(cacheHook2.current.isExpired).toBe(true);
|
|
261
|
+
|
|
262
|
+
// Update with version 2
|
|
263
|
+
await act(async () => {
|
|
264
|
+
await cacheHook2.current.setData(dataV2);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(cacheHook2.current.data).toEqual(dataV2);
|
|
268
|
+
expect(cacheHook2.current.isExpired).toBe(false);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('Error Recovery Integration', () => {
|
|
273
|
+
it('should recover from storage errors', async () => {
|
|
274
|
+
const key = 'error-recovery';
|
|
275
|
+
const defaultValue = 'default';
|
|
276
|
+
|
|
277
|
+
// Mock storage error for first call
|
|
278
|
+
let callCount = 0;
|
|
279
|
+
(AsyncStorage.getItem as jest.Mock).mockImplementation(() => {
|
|
280
|
+
callCount++;
|
|
281
|
+
if (callCount === 1) {
|
|
282
|
+
return Promise.reject(new Error('Storage error'));
|
|
283
|
+
}
|
|
284
|
+
return Promise.resolve(JSON.stringify('recovered-value'));
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const { result: stateHook, waitForNextUpdate } = renderHook(() =>
|
|
288
|
+
useStorageState(key, defaultValue)
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
await act(async () => {
|
|
292
|
+
await waitForNextUpdate();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Should use default value on error
|
|
296
|
+
expect(stateHook.current[0]).toBe(defaultValue);
|
|
297
|
+
|
|
298
|
+
// Retry should work
|
|
299
|
+
await act(async () => {
|
|
300
|
+
await stateHook.current[1]('retry-value');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
expect(stateHook.current[0]).toBe('retry-value');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('should handle corrupted data gracefully', async () => {
|
|
307
|
+
const key = 'corrupted-data';
|
|
308
|
+
const defaultValue = { safe: 'default' };
|
|
309
|
+
|
|
310
|
+
// Set corrupted JSON
|
|
311
|
+
await AsyncStorage.setItem(key, '{"invalid": json}');
|
|
312
|
+
|
|
313
|
+
const { result: stateHook, waitForNextUpdate } = renderHook(() =>
|
|
314
|
+
useStorageState(key, defaultValue)
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
await act(async () => {
|
|
318
|
+
await waitForNextUpdate();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Should use default value
|
|
322
|
+
expect(stateHook.current[0]).toEqual(defaultValue);
|
|
323
|
+
|
|
324
|
+
// Should be able to set new data
|
|
325
|
+
const validData = { valid: 'data' };
|
|
326
|
+
await act(async () => {
|
|
327
|
+
await stateHook.current[1](validData);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
expect(stateHook.current[0]).toEqual(validData);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe('Performance Integration', () => {
|
|
335
|
+
it('should handle rapid state changes', async () => {
|
|
336
|
+
const key = 'rapid-changes';
|
|
337
|
+
const { result: stateHook, waitForNextUpdate } = renderHook(() =>
|
|
338
|
+
useStorageState(key, 'initial')
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
await act(async () => {
|
|
342
|
+
await waitForNextUpdate();
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Rapid changes
|
|
346
|
+
const changes = ['change1', 'change2', 'change3'];
|
|
347
|
+
|
|
348
|
+
for (const change of changes) {
|
|
349
|
+
await act(async () => {
|
|
350
|
+
await stateHook.current[1](change);
|
|
351
|
+
});
|
|
352
|
+
expect(stateHook.current[0]).toBe(change);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Verify final state in storage
|
|
356
|
+
const stored = await AsyncStorage.getItem(key);
|
|
357
|
+
expect(JSON.parse(stored!)).toBe('change3');
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('should not interfere with different keys', async () => {
|
|
361
|
+
const { result: hook1, waitForNextUpdate: waitFor1 } = renderHook(() =>
|
|
362
|
+
useStorageState('key1', 'value1')
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
const { result: hook2, waitForNextUpdate: waitFor2 } = renderHook(() =>
|
|
366
|
+
useStorageState('key2', 'value2')
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
await act(async () => {
|
|
370
|
+
await Promise.all([waitFor1(), waitFor2()]);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// Update both hooks rapidly
|
|
374
|
+
await act(async () => {
|
|
375
|
+
await Promise.all([
|
|
376
|
+
hook1.current[1]('updated1'),
|
|
377
|
+
hook2.current[1]('updated2'),
|
|
378
|
+
]);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
expect(hook1.current[0]).toBe('updated1');
|
|
382
|
+
expect(hook2.current[0]).toBe('updated2');
|
|
383
|
+
|
|
384
|
+
// Verify storage
|
|
385
|
+
const stored1 = await AsyncStorage.getItem('key1');
|
|
386
|
+
const stored2 = await AsyncStorage.getItem('key2');
|
|
387
|
+
expect(JSON.parse(stored1!)).toBe('updated1');
|
|
388
|
+
expect(JSON.parse(stored2!)).toBe('updated2');
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AsyncStorage Mock
|
|
3
|
+
*
|
|
4
|
+
* Mock implementation for testing
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export class AsyncStorageMock {
|
|
8
|
+
private storage: Map<string, string> = new Map();
|
|
9
|
+
|
|
10
|
+
async getItem(key: string): Promise<string | null> {
|
|
11
|
+
return this.storage.get(key) || null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async setItem(key: string, value: string): Promise<void> {
|
|
15
|
+
this.storage.set(key, value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async removeItem(key: string): Promise<void> {
|
|
19
|
+
this.storage.delete(key);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async clear(): Promise<void> {
|
|
23
|
+
this.storage.clear();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async getAllKeys(): Promise<readonly string[]> {
|
|
27
|
+
return Array.from(this.storage.keys());
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async multiGet(keys: readonly string[]): Promise<readonly (readonly [string, string | null])[]> {
|
|
31
|
+
return keys.map(key => [key, this.storage.get(key) || null]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Test utilities
|
|
35
|
+
__clear() {
|
|
36
|
+
this.storage.clear();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
__size() {
|
|
40
|
+
return this.storage.size;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
__has(key: string) {
|
|
44
|
+
return this.storage.has(key);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
__get(key: string) {
|
|
48
|
+
return this.storage.get(key);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const AsyncStorage = new AsyncStorageMock();
|