@harkenapp/sdk-react-native 0.0.1-alpha.1 → 0.0.1-alpha.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/README.md +44 -7
- package/app.plugin.cjs +12 -17
- package/dist/__mocks__/async-storage.d.ts +16 -0
- package/dist/__mocks__/async-storage.d.ts.map +1 -0
- package/dist/__mocks__/async-storage.js +39 -0
- package/dist/__mocks__/async-storage.js.map +1 -0
- package/dist/__mocks__/expo-document-picker.d.ts +26 -0
- package/dist/__mocks__/expo-document-picker.d.ts.map +1 -0
- package/dist/__mocks__/expo-document-picker.js +25 -0
- package/dist/__mocks__/expo-document-picker.js.map +1 -0
- package/dist/__mocks__/expo-file-system.d.ts +42 -0
- package/dist/__mocks__/expo-file-system.d.ts.map +1 -0
- package/dist/__mocks__/expo-file-system.js +37 -0
- package/dist/__mocks__/expo-file-system.js.map +1 -0
- package/dist/__mocks__/expo-image-picker.d.ts +30 -0
- package/dist/__mocks__/expo-image-picker.d.ts.map +1 -0
- package/dist/__mocks__/expo-image-picker.js +30 -0
- package/dist/__mocks__/expo-image-picker.js.map +1 -0
- package/dist/__mocks__/expo-secure-store.d.ts +15 -0
- package/dist/__mocks__/expo-secure-store.d.ts.map +1 -0
- package/dist/__mocks__/expo-secure-store.js +30 -0
- package/dist/__mocks__/expo-secure-store.js.map +1 -0
- package/dist/__mocks__/react-native.d.ts +73 -0
- package/dist/__mocks__/react-native.d.ts.map +1 -0
- package/dist/__mocks__/react-native.js +45 -0
- package/dist/__mocks__/react-native.js.map +1 -0
- package/dist/api/client.d.ts +8 -8
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +17 -19
- package/dist/api/client.js.map +1 -1
- package/dist/api/client.test.d.ts +2 -0
- package/dist/api/client.test.d.ts.map +1 -0
- package/dist/api/client.test.js +417 -0
- package/dist/api/client.test.js.map +1 -0
- package/dist/api/errors.d.ts +3 -3
- package/dist/api/errors.d.ts.map +1 -1
- package/dist/api/errors.js +3 -3
- package/dist/api/errors.js.map +1 -1
- package/dist/api/errors.test.d.ts +2 -0
- package/dist/api/errors.test.d.ts.map +1 -0
- package/dist/api/errors.test.js +155 -0
- package/dist/api/errors.test.js.map +1 -0
- package/dist/api/index.d.ts +6 -6
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/retry.d.ts +1 -1
- package/dist/api/retry.d.ts.map +1 -1
- package/dist/api/retry.js.map +1 -1
- package/dist/api/retry.test.d.ts +2 -0
- package/dist/api/retry.test.d.ts.map +1 -0
- package/dist/api/retry.test.js +193 -0
- package/dist/api/retry.test.js.map +1 -0
- package/dist/attachments/FeedbackSheet.d.ts +36 -13
- package/dist/attachments/FeedbackSheet.d.ts.map +1 -1
- package/dist/attachments/FeedbackSheet.js +50 -30
- package/dist/attachments/FeedbackSheet.js.map +1 -1
- package/dist/attachments/index.d.ts +2 -2
- package/dist/components/AttachmentGrid.d.ts +12 -4
- package/dist/components/AttachmentGrid.d.ts.map +1 -1
- package/dist/components/AttachmentGrid.js +44 -34
- package/dist/components/AttachmentGrid.js.map +1 -1
- package/dist/components/AttachmentPicker.d.ts +3 -3
- package/dist/components/AttachmentPicker.d.ts.map +1 -1
- package/dist/components/AttachmentPicker.js +34 -36
- package/dist/components/AttachmentPicker.js.map +1 -1
- package/dist/components/AttachmentPreview.d.ts +10 -4
- package/dist/components/AttachmentPreview.d.ts.map +1 -1
- package/dist/components/AttachmentPreview.js +48 -34
- package/dist/components/AttachmentPreview.js.map +1 -1
- package/dist/components/CategorySelector.d.ts +3 -3
- package/dist/components/CategorySelector.d.ts.map +1 -1
- package/dist/components/CategorySelector.js +21 -27
- package/dist/components/CategorySelector.js.map +1 -1
- package/dist/components/FeedbackForm.d.ts +3 -3
- package/dist/components/FeedbackForm.d.ts.map +1 -1
- package/dist/components/FeedbackForm.js +7 -8
- package/dist/components/FeedbackForm.js.map +1 -1
- package/dist/components/FeedbackSheet.d.ts +34 -11
- package/dist/components/FeedbackSheet.d.ts.map +1 -1
- package/dist/components/FeedbackSheet.js +46 -28
- package/dist/components/FeedbackSheet.js.map +1 -1
- package/dist/components/ThemedButton.d.ts +16 -5
- package/dist/components/ThemedButton.d.ts.map +1 -1
- package/dist/components/ThemedButton.js +38 -29
- package/dist/components/ThemedButton.js.map +1 -1
- package/dist/components/ThemedText.d.ts +3 -3
- package/dist/components/ThemedText.d.ts.map +1 -1
- package/dist/components/ThemedText.js +1 -1
- package/dist/components/ThemedText.js.map +1 -1
- package/dist/components/ThemedTextInput.d.ts +11 -2
- package/dist/components/ThemedTextInput.d.ts.map +1 -1
- package/dist/components/ThemedTextInput.js +19 -9
- package/dist/components/ThemedTextInput.js.map +1 -1
- package/dist/components/UploadStatusOverlay.d.ts +11 -3
- package/dist/components/UploadStatusOverlay.d.ts.map +1 -1
- package/dist/components/UploadStatusOverlay.js +59 -76
- package/dist/components/UploadStatusOverlay.js.map +1 -1
- package/dist/components/index.d.ts +18 -18
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js.map +1 -1
- package/dist/context/HarkenContext.d.ts +20 -15
- package/dist/context/HarkenContext.d.ts.map +1 -1
- package/dist/context/HarkenContext.js +20 -17
- package/dist/context/HarkenContext.js.map +1 -1
- package/dist/context/index.d.ts +2 -2
- package/dist/domain/index.d.ts +2 -2
- package/dist/domain/index.d.ts.map +1 -1
- package/dist/domain/index.js.map +1 -1
- package/dist/hooks/index.d.ts +5 -5
- package/dist/hooks/useAnonymousId.js +1 -1
- package/dist/hooks/useAnonymousId.test.d.ts +2 -0
- package/dist/hooks/useAnonymousId.test.d.ts.map +1 -0
- package/dist/hooks/useAnonymousId.test.js +154 -0
- package/dist/hooks/useAnonymousId.test.js.map +1 -0
- package/dist/hooks/useAttachmentPicker.d.ts +3 -3
- package/dist/hooks/useAttachmentPicker.js +7 -7
- package/dist/hooks/useAttachmentStatus.d.ts +1 -1
- package/dist/hooks/useAttachmentStatus.d.ts.map +1 -1
- package/dist/hooks/useAttachmentStatus.js.map +1 -1
- package/dist/hooks/useAttachmentUpload.d.ts +2 -2
- package/dist/hooks/useAttachmentUpload.d.ts.map +1 -1
- package/dist/hooks/useAttachmentUpload.js +5 -5
- package/dist/hooks/useAttachmentUpload.js.map +1 -1
- package/dist/hooks/useAttachmentUpload.test.d.ts +2 -0
- package/dist/hooks/useAttachmentUpload.test.d.ts.map +1 -0
- package/dist/hooks/useAttachmentUpload.test.js +542 -0
- package/dist/hooks/useAttachmentUpload.test.js.map +1 -0
- package/dist/hooks/useFeedback.d.ts +4 -4
- package/dist/hooks/useFeedback.d.ts.map +1 -1
- package/dist/hooks/useFeedback.js +3 -5
- package/dist/hooks/useFeedback.js.map +1 -1
- package/dist/hooks/useFeedback.test.d.ts +2 -0
- package/dist/hooks/useFeedback.test.d.ts.map +1 -0
- package/dist/hooks/useFeedback.test.js +299 -0
- package/dist/hooks/useFeedback.test.js.map +1 -0
- package/dist/hooks/useHarkenContext.d.ts +1 -1
- package/dist/hooks/useHarkenContext.js +1 -1
- package/dist/hooks/useHarkenTheme.d.ts +27 -3
- package/dist/hooks/useHarkenTheme.d.ts.map +1 -1
- package/dist/hooks/useHarkenTheme.js +26 -2
- package/dist/hooks/useHarkenTheme.js.map +1 -1
- package/dist/index.d.ts +28 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/services/index.d.ts +3 -3
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js.map +1 -1
- package/dist/services/uploadQueueService.d.ts +2 -2
- package/dist/services/uploadQueueService.d.ts.map +1 -1
- package/dist/services/uploadQueueService.js +16 -17
- package/dist/services/uploadQueueService.js.map +1 -1
- package/dist/services/uploadQueueService.test.d.ts +2 -0
- package/dist/services/uploadQueueService.test.d.ts.map +1 -0
- package/dist/services/uploadQueueService.test.js +426 -0
- package/dist/services/uploadQueueService.test.js.map +1 -0
- package/dist/services/uploadQueueStorage.d.ts +1 -1
- package/dist/services/uploadQueueStorage.d.ts.map +1 -1
- package/dist/services/uploadQueueStorage.js +4 -4
- package/dist/services/uploadQueueStorage.js.map +1 -1
- package/dist/services/uploadQueueStorage.test.d.ts +2 -0
- package/dist/services/uploadQueueStorage.test.d.ts.map +1 -0
- package/dist/services/uploadQueueStorage.test.js +200 -0
- package/dist/services/uploadQueueStorage.test.js.map +1 -0
- package/dist/storage/IdentityStore.d.ts +1 -1
- package/dist/storage/IdentityStore.d.ts.map +1 -1
- package/dist/storage/IdentityStore.js.map +1 -1
- package/dist/storage/IdentityStore.test.d.ts +2 -0
- package/dist/storage/IdentityStore.test.d.ts.map +1 -0
- package/dist/storage/IdentityStore.test.js +176 -0
- package/dist/storage/IdentityStore.test.js.map +1 -0
- package/dist/storage/SecureStoreAdapter.d.ts +1 -1
- package/dist/storage/SecureStoreAdapter.test.d.ts +2 -0
- package/dist/storage/SecureStoreAdapter.test.d.ts.map +1 -0
- package/dist/storage/SecureStoreAdapter.test.js +114 -0
- package/dist/storage/SecureStoreAdapter.test.js.map +1 -0
- package/dist/storage/defaultStorage.d.ts +1 -1
- package/dist/storage/defaultStorage.js +4 -4
- package/dist/storage/defaultStorage.test.d.ts +2 -0
- package/dist/storage/defaultStorage.test.d.ts.map +1 -0
- package/dist/storage/defaultStorage.test.js +159 -0
- package/dist/storage/defaultStorage.test.js.map +1 -0
- package/dist/storage/index.d.ts +5 -5
- package/dist/storage/types.js +1 -1
- package/dist/theme/defaults.d.ts +14 -3
- package/dist/theme/defaults.d.ts.map +1 -1
- package/dist/theme/defaults.js +58 -43
- package/dist/theme/defaults.js.map +1 -1
- package/dist/theme/index.d.ts +3 -2
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +4 -1
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/resolver.d.ts +16 -0
- package/dist/theme/resolver.d.ts.map +1 -0
- package/dist/theme/resolver.js +375 -0
- package/dist/theme/resolver.js.map +1 -0
- package/dist/theme/resolver.test.d.ts +2 -0
- package/dist/theme/resolver.test.d.ts.map +1 -0
- package/dist/theme/resolver.test.js +344 -0
- package/dist/theme/resolver.test.js.map +1 -0
- package/dist/theme/types.d.ts +378 -5
- package/dist/theme/types.d.ts.map +1 -1
- package/dist/types/config.d.ts +4 -4
- package/dist/types/index.d.ts +2 -2
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/uuid.d.ts.map +1 -1
- package/dist/utils/uuid.js +4 -5
- package/dist/utils/uuid.js.map +1 -1
- package/dist/utils/uuid.test.d.ts +2 -0
- package/dist/utils/uuid.test.d.ts.map +1 -0
- package/dist/utils/uuid.test.js +78 -0
- package/dist/utils/uuid.test.js.map +1 -0
- package/package.json +21 -13
- package/src/@types/expo-file-system-legacy.d.ts +3 -3
- package/src/__mocks__/async-storage.ts +46 -0
- package/src/__mocks__/expo-document-picker.ts +41 -0
- package/src/__mocks__/expo-file-system.ts +62 -0
- package/src/__mocks__/expo-image-picker.ts +48 -0
- package/src/__mocks__/expo-secure-store.ts +29 -0
- package/src/__mocks__/react-native.ts +46 -0
- package/src/api/client.test.ts +515 -0
- package/src/api/client.ts +45 -64
- package/src/api/errors.test.ts +193 -0
- package/src/api/errors.ts +7 -11
- package/src/api/index.ts +6 -10
- package/src/api/retry.test.ts +251 -0
- package/src/api/retry.ts +3 -6
- package/src/attachments/FeedbackSheet.tsx +100 -80
- package/src/attachments/index.ts +2 -2
- package/src/components/AttachmentGrid.tsx +54 -45
- package/src/components/AttachmentPicker.tsx +43 -54
- package/src/components/AttachmentPreview.tsx +51 -47
- package/src/components/CategorySelector.tsx +29 -35
- package/src/components/FeedbackForm.tsx +23 -35
- package/src/components/FeedbackSheet.tsx +89 -68
- package/src/components/ThemedButton.tsx +49 -47
- package/src/components/ThemedText.tsx +7 -10
- package/src/components/ThemedTextInput.tsx +23 -13
- package/src/components/UploadStatusOverlay.tsx +66 -89
- package/src/components/index.ts +18 -21
- package/src/context/HarkenContext.tsx +29 -28
- package/src/context/index.ts +2 -2
- package/src/domain/index.ts +2 -5
- package/src/domain/upload-queue.ts +5 -5
- package/src/hooks/index.ts +5 -5
- package/src/hooks/useAnonymousId.test.ts +189 -0
- package/src/hooks/useAnonymousId.ts +3 -3
- package/src/hooks/useAttachmentPicker.ts +12 -12
- package/src/hooks/useAttachmentStatus.ts +12 -16
- package/src/hooks/useAttachmentUpload.test.ts +632 -0
- package/src/hooks/useAttachmentUpload.ts +45 -54
- package/src/hooks/useFeedback.test.ts +376 -0
- package/src/hooks/useFeedback.ts +12 -14
- package/src/hooks/useHarkenContext.ts +4 -4
- package/src/hooks/useHarkenTheme.ts +30 -6
- package/src/index.ts +28 -52
- package/src/services/index.ts +3 -9
- package/src/services/uploadQueueService.test.ts +489 -0
- package/src/services/uploadQueueService.ts +40 -56
- package/src/services/uploadQueueStorage.test.ts +243 -0
- package/src/services/uploadQueueStorage.ts +7 -9
- package/src/storage/IdentityStore.test.ts +173 -0
- package/src/storage/IdentityStore.ts +4 -5
- package/src/storage/SecureStoreAdapter.test.ts +147 -0
- package/src/storage/SecureStoreAdapter.ts +1 -1
- package/src/storage/defaultStorage.test.ts +159 -0
- package/src/storage/defaultStorage.ts +6 -6
- package/src/storage/index.ts +5 -5
- package/src/storage/types.ts +1 -1
- package/src/theme/defaults.ts +75 -46
- package/src/theme/index.ts +15 -2
- package/src/theme/resolver.test.ts +411 -0
- package/src/theme/resolver.ts +446 -0
- package/src/theme/types.ts +453 -15
- package/src/types/config.ts +4 -4
- package/src/types/index.ts +2 -2
- package/src/utils/index.ts +1 -1
- package/src/utils/uuid.test.ts +85 -0
- package/src/utils/uuid.ts +4 -7
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { createSecureStoreAdapter, createMemoryStorage } from "./SecureStoreAdapter";
|
|
3
|
+
|
|
4
|
+
describe("createSecureStoreAdapter", () => {
|
|
5
|
+
describe("when SecureStore is available", () => {
|
|
6
|
+
it("maps getItem to getItemAsync", async () => {
|
|
7
|
+
const mockSecureStore = {
|
|
8
|
+
getItemAsync: vi.fn().mockResolvedValue("stored-value"),
|
|
9
|
+
setItemAsync: vi.fn(),
|
|
10
|
+
deleteItemAsync: vi.fn(),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const adapter = createSecureStoreAdapter(mockSecureStore);
|
|
14
|
+
const result = await adapter.getItem("test-key");
|
|
15
|
+
|
|
16
|
+
expect(mockSecureStore.getItemAsync).toHaveBeenCalledWith("test-key");
|
|
17
|
+
expect(result).toBe("stored-value");
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("maps setItem to setItemAsync", async () => {
|
|
21
|
+
const mockSecureStore = {
|
|
22
|
+
getItemAsync: vi.fn(),
|
|
23
|
+
setItemAsync: vi.fn().mockResolvedValue(undefined),
|
|
24
|
+
deleteItemAsync: vi.fn(),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const adapter = createSecureStoreAdapter(mockSecureStore);
|
|
28
|
+
await adapter.setItem("test-key", "test-value");
|
|
29
|
+
|
|
30
|
+
expect(mockSecureStore.setItemAsync).toHaveBeenCalledWith("test-key", "test-value");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("maps deleteItem to deleteItemAsync", async () => {
|
|
34
|
+
const mockSecureStore = {
|
|
35
|
+
getItemAsync: vi.fn(),
|
|
36
|
+
setItemAsync: vi.fn(),
|
|
37
|
+
deleteItemAsync: vi.fn().mockResolvedValue(undefined),
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const adapter = createSecureStoreAdapter(mockSecureStore);
|
|
41
|
+
await adapter.deleteItem("test-key");
|
|
42
|
+
|
|
43
|
+
expect(mockSecureStore.deleteItemAsync).toHaveBeenCalledWith("test-key");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns null when key not found", async () => {
|
|
47
|
+
const mockSecureStore = {
|
|
48
|
+
getItemAsync: vi.fn().mockResolvedValue(null),
|
|
49
|
+
setItemAsync: vi.fn(),
|
|
50
|
+
deleteItemAsync: vi.fn(),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const adapter = createSecureStoreAdapter(mockSecureStore);
|
|
54
|
+
const result = await adapter.getItem("nonexistent");
|
|
55
|
+
|
|
56
|
+
expect(result).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("error handling", () => {
|
|
61
|
+
it("propagates getItemAsync errors", async () => {
|
|
62
|
+
const mockSecureStore = {
|
|
63
|
+
getItemAsync: vi.fn().mockRejectedValue(new Error("Storage error")),
|
|
64
|
+
setItemAsync: vi.fn(),
|
|
65
|
+
deleteItemAsync: vi.fn(),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const adapter = createSecureStoreAdapter(mockSecureStore);
|
|
69
|
+
|
|
70
|
+
await expect(adapter.getItem("test-key")).rejects.toThrow("Storage error");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("propagates setItemAsync errors", async () => {
|
|
74
|
+
const mockSecureStore = {
|
|
75
|
+
getItemAsync: vi.fn(),
|
|
76
|
+
setItemAsync: vi.fn().mockRejectedValue(new Error("Write error")),
|
|
77
|
+
deleteItemAsync: vi.fn(),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const adapter = createSecureStoreAdapter(mockSecureStore);
|
|
81
|
+
|
|
82
|
+
await expect(adapter.setItem("key", "value")).rejects.toThrow("Write error");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("propagates deleteItemAsync errors", async () => {
|
|
86
|
+
const mockSecureStore = {
|
|
87
|
+
getItemAsync: vi.fn(),
|
|
88
|
+
setItemAsync: vi.fn(),
|
|
89
|
+
deleteItemAsync: vi.fn().mockRejectedValue(new Error("Delete error")),
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const adapter = createSecureStoreAdapter(mockSecureStore);
|
|
93
|
+
|
|
94
|
+
await expect(adapter.deleteItem("key")).rejects.toThrow("Delete error");
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("createMemoryStorage", () => {
|
|
100
|
+
it("stores and retrieves values", async () => {
|
|
101
|
+
const storage = createMemoryStorage();
|
|
102
|
+
|
|
103
|
+
await storage.setItem("key1", "value1");
|
|
104
|
+
const result = await storage.getItem("key1");
|
|
105
|
+
|
|
106
|
+
expect(result).toBe("value1");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("returns null for non-existent keys", async () => {
|
|
110
|
+
const storage = createMemoryStorage();
|
|
111
|
+
|
|
112
|
+
const result = await storage.getItem("nonexistent");
|
|
113
|
+
|
|
114
|
+
expect(result).toBeNull();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("deletes values", async () => {
|
|
118
|
+
const storage = createMemoryStorage();
|
|
119
|
+
|
|
120
|
+
await storage.setItem("key", "value");
|
|
121
|
+
await storage.deleteItem("key");
|
|
122
|
+
const result = await storage.getItem("key");
|
|
123
|
+
|
|
124
|
+
expect(result).toBeNull();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("overwrites existing values", async () => {
|
|
128
|
+
const storage = createMemoryStorage();
|
|
129
|
+
|
|
130
|
+
await storage.setItem("key", "first");
|
|
131
|
+
await storage.setItem("key", "second");
|
|
132
|
+
const result = await storage.getItem("key");
|
|
133
|
+
|
|
134
|
+
expect(result).toBe("second");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("isolates storage between instances", async () => {
|
|
138
|
+
const storage1 = createMemoryStorage();
|
|
139
|
+
const storage2 = createMemoryStorage();
|
|
140
|
+
|
|
141
|
+
await storage1.setItem("key", "value1");
|
|
142
|
+
await storage2.setItem("key", "value2");
|
|
143
|
+
|
|
144
|
+
expect(await storage1.getItem("key")).toBe("value1");
|
|
145
|
+
expect(await storage2.getItem("key")).toBe("value2");
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
// We need to test the module in isolation, so we use dynamic imports
|
|
4
|
+
// and reset the module state between tests
|
|
5
|
+
|
|
6
|
+
describe("defaultStorage", () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
vi.resetModules();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
vi.restoreAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("getDefaultStorage", () => {
|
|
16
|
+
it("uses SecureStore when available", async () => {
|
|
17
|
+
// Mock expo-secure-store to be available
|
|
18
|
+
vi.doMock("expo-secure-store", () => ({
|
|
19
|
+
getItemAsync: vi.fn().mockResolvedValue("secure-value"),
|
|
20
|
+
setItemAsync: vi.fn().mockResolvedValue(undefined),
|
|
21
|
+
deleteItemAsync: vi.fn().mockResolvedValue(undefined),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const { getDefaultStorage } = await import("./defaultStorage");
|
|
25
|
+
const storage = await getDefaultStorage();
|
|
26
|
+
|
|
27
|
+
// The storage should use SecureStore
|
|
28
|
+
const result = await storage.getItem("test-key");
|
|
29
|
+
|
|
30
|
+
// Since we mocked getItemAsync to return "secure-value"
|
|
31
|
+
expect(result).toBe("secure-value");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("falls back to memory storage when SecureStore unavailable", async () => {
|
|
35
|
+
// Mock expo-secure-store to throw (simulating it not being installed)
|
|
36
|
+
vi.doMock("expo-secure-store", () => {
|
|
37
|
+
throw new Error("Cannot find module 'expo-secure-store'");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Suppress console.warn for this test
|
|
41
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
42
|
+
|
|
43
|
+
const { getDefaultStorage } = await import("./defaultStorage");
|
|
44
|
+
const storage = await getDefaultStorage();
|
|
45
|
+
|
|
46
|
+
// Should fall back to memory storage and warn
|
|
47
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
48
|
+
expect.stringContaining("expo-secure-store not available")
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Memory storage should work
|
|
52
|
+
await storage.setItem("key", "value");
|
|
53
|
+
const result = await storage.getItem("key");
|
|
54
|
+
expect(result).toBe("value");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("caches the storage instance", async () => {
|
|
58
|
+
vi.doMock("expo-secure-store", () => ({
|
|
59
|
+
getItemAsync: vi.fn().mockResolvedValue(null),
|
|
60
|
+
setItemAsync: vi.fn().mockResolvedValue(undefined),
|
|
61
|
+
deleteItemAsync: vi.fn().mockResolvedValue(undefined),
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
const { getDefaultStorage } = await import("./defaultStorage");
|
|
65
|
+
|
|
66
|
+
const storage1 = await getDefaultStorage();
|
|
67
|
+
const storage2 = await getDefaultStorage();
|
|
68
|
+
|
|
69
|
+
// Should return the same instance
|
|
70
|
+
expect(storage1).toBe(storage2);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("createDefaultStorage", () => {
|
|
75
|
+
it("returns a lazy wrapper that initializes on first use", async () => {
|
|
76
|
+
const mockGetItemAsync = vi.fn().mockResolvedValue("lazy-value");
|
|
77
|
+
|
|
78
|
+
vi.doMock("expo-secure-store", () => ({
|
|
79
|
+
getItemAsync: mockGetItemAsync,
|
|
80
|
+
setItemAsync: vi.fn().mockResolvedValue(undefined),
|
|
81
|
+
deleteItemAsync: vi.fn().mockResolvedValue(undefined),
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
const { createDefaultStorage } = await import("./defaultStorage");
|
|
85
|
+
const storage = createDefaultStorage();
|
|
86
|
+
|
|
87
|
+
// SecureStore shouldn't be called yet (lazy)
|
|
88
|
+
expect(mockGetItemAsync).not.toHaveBeenCalled();
|
|
89
|
+
|
|
90
|
+
// First use triggers initialization
|
|
91
|
+
const result = await storage.getItem("test-key");
|
|
92
|
+
|
|
93
|
+
expect(mockGetItemAsync).toHaveBeenCalledWith("test-key");
|
|
94
|
+
expect(result).toBe("lazy-value");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("caches initialization across method calls", async () => {
|
|
98
|
+
let initCount = 0;
|
|
99
|
+
|
|
100
|
+
vi.doMock("expo-secure-store", () => {
|
|
101
|
+
initCount++;
|
|
102
|
+
return {
|
|
103
|
+
getItemAsync: vi.fn().mockResolvedValue(null),
|
|
104
|
+
setItemAsync: vi.fn().mockResolvedValue(undefined),
|
|
105
|
+
deleteItemAsync: vi.fn().mockResolvedValue(undefined),
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const { createDefaultStorage } = await import("./defaultStorage");
|
|
110
|
+
const storage = createDefaultStorage();
|
|
111
|
+
|
|
112
|
+
// Multiple operations should only initialize once
|
|
113
|
+
await storage.getItem("key1");
|
|
114
|
+
await storage.setItem("key2", "value");
|
|
115
|
+
await storage.deleteItem("key3");
|
|
116
|
+
|
|
117
|
+
// Module should only be imported once
|
|
118
|
+
expect(initCount).toBe(1);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("handles concurrent initialization correctly", async () => {
|
|
122
|
+
vi.doMock("expo-secure-store", () => ({
|
|
123
|
+
getItemAsync: vi.fn().mockResolvedValue("concurrent-value"),
|
|
124
|
+
setItemAsync: vi.fn().mockResolvedValue(undefined),
|
|
125
|
+
deleteItemAsync: vi.fn().mockResolvedValue(undefined),
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
const { createDefaultStorage } = await import("./defaultStorage");
|
|
129
|
+
const storage = createDefaultStorage();
|
|
130
|
+
|
|
131
|
+
// Concurrent calls should all succeed
|
|
132
|
+
const [result1, result2, result3] = await Promise.all([
|
|
133
|
+
storage.getItem("key1"),
|
|
134
|
+
storage.getItem("key2"),
|
|
135
|
+
storage.getItem("key3"),
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
expect(result1).toBe("concurrent-value");
|
|
139
|
+
expect(result2).toBe("concurrent-value");
|
|
140
|
+
expect(result3).toBe("concurrent-value");
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("error handling", () => {
|
|
145
|
+
it("does not crash when SecureStore throws during operations", async () => {
|
|
146
|
+
vi.doMock("expo-secure-store", () => ({
|
|
147
|
+
getItemAsync: vi.fn().mockRejectedValue(new Error("SecureStore error")),
|
|
148
|
+
setItemAsync: vi.fn().mockRejectedValue(new Error("SecureStore error")),
|
|
149
|
+
deleteItemAsync: vi.fn().mockRejectedValue(new Error("SecureStore error")),
|
|
150
|
+
}));
|
|
151
|
+
|
|
152
|
+
const { createDefaultStorage } = await import("./defaultStorage");
|
|
153
|
+
const storage = createDefaultStorage();
|
|
154
|
+
|
|
155
|
+
// Errors from SecureStore should propagate (not swallowed)
|
|
156
|
+
await expect(storage.getItem("key")).rejects.toThrow("SecureStore error");
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { SecureStorage } from
|
|
2
|
-
import { createMemoryStorage } from
|
|
1
|
+
import type { SecureStorage } from "./types";
|
|
2
|
+
import { createMemoryStorage } from "./SecureStoreAdapter";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Cached default storage instance.
|
|
@@ -31,7 +31,7 @@ export async function getDefaultStorage(): Promise<SecureStorage> {
|
|
|
31
31
|
|
|
32
32
|
try {
|
|
33
33
|
// Dynamically import expo-secure-store
|
|
34
|
-
const SecureStore = await import(
|
|
34
|
+
const SecureStore = await import("expo-secure-store");
|
|
35
35
|
|
|
36
36
|
defaultStorageInstance = {
|
|
37
37
|
async getItem(key: string): Promise<string | null> {
|
|
@@ -49,9 +49,9 @@ export async function getDefaultStorage(): Promise<SecureStorage> {
|
|
|
49
49
|
} catch {
|
|
50
50
|
// expo-secure-store not available, fall back to memory storage
|
|
51
51
|
console.warn(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
"[Harken] expo-secure-store not available. Using in-memory storage. " +
|
|
53
|
+
"Anonymous IDs will not persist across app restarts. " +
|
|
54
|
+
"Install expo-secure-store for persistent storage."
|
|
55
55
|
);
|
|
56
56
|
}
|
|
57
57
|
}
|
package/src/storage/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export type { SecureStorage } from
|
|
2
|
-
export { STORAGE_KEYS } from
|
|
3
|
-
export { createSecureStoreAdapter, createMemoryStorage } from
|
|
4
|
-
export { createDefaultStorage } from
|
|
5
|
-
export { IdentityStore } from
|
|
1
|
+
export type { SecureStorage } from "./types";
|
|
2
|
+
export { STORAGE_KEYS } from "./types";
|
|
3
|
+
export { createSecureStoreAdapter, createMemoryStorage } from "./SecureStoreAdapter";
|
|
4
|
+
export { createDefaultStorage } from "./defaultStorage";
|
|
5
|
+
export { IdentityStore } from "./IdentityStore";
|
package/src/storage/types.ts
CHANGED
package/src/theme/defaults.ts
CHANGED
|
@@ -3,33 +3,36 @@ import type {
|
|
|
3
3
|
HarkenTypography,
|
|
4
4
|
HarkenSpacing,
|
|
5
5
|
HarkenRadii,
|
|
6
|
+
HarkenSizing,
|
|
7
|
+
HarkenOpacity,
|
|
6
8
|
HarkenTheme,
|
|
7
|
-
} from
|
|
9
|
+
} from "./types";
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Default light mode colors.
|
|
11
13
|
* Neutral, accessible palette with no hard-coded branding.
|
|
12
14
|
*/
|
|
13
15
|
export const lightColors: HarkenColors = {
|
|
14
|
-
primary:
|
|
15
|
-
primaryPressed:
|
|
16
|
-
background:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
16
|
+
primary: "#2563EB", // Blue 600
|
|
17
|
+
primaryPressed: "#1D4ED8", // Blue 700
|
|
18
|
+
background: "#FFFFFF",
|
|
19
|
+
surface: "#F9FAFB", // Gray 50 - container/modal surface
|
|
20
|
+
backgroundSecondary: "#F9FAFB", // Gray 50 - alias for surface (backwards compat)
|
|
21
|
+
text: "#111827", // Gray 900
|
|
22
|
+
textSecondary: "#6B7280", // Gray 500
|
|
23
|
+
textPlaceholder: "#9CA3AF", // Gray 400
|
|
24
|
+
textOnPrimary: "#FFFFFF",
|
|
25
|
+
border: "#E5E7EB", // Gray 200
|
|
26
|
+
borderFocused: "#2563EB", // Blue 600
|
|
27
|
+
error: "#DC2626", // Red 600
|
|
28
|
+
success: "#16A34A", // Green 600
|
|
29
|
+
warning: "#D97706", // Amber 600
|
|
30
|
+
info: "#2563EB", // Blue 600
|
|
31
|
+
overlay: "rgba(0, 0, 0, 0.3)",
|
|
32
|
+
overlayDark: "rgba(0, 0, 0, 0.6)",
|
|
33
|
+
accent1: "#2563EB", // Blue 600 (camera)
|
|
34
|
+
accent2: "#16A34A", // Green 600 (library)
|
|
35
|
+
accent3: "#D97706", // Amber 600 (files)
|
|
33
36
|
};
|
|
34
37
|
|
|
35
38
|
/**
|
|
@@ -37,25 +40,26 @@ export const lightColors: HarkenColors = {
|
|
|
37
40
|
* Inverted palette optimized for dark backgrounds.
|
|
38
41
|
*/
|
|
39
42
|
export const darkColors: HarkenColors = {
|
|
40
|
-
primary:
|
|
41
|
-
primaryPressed:
|
|
42
|
-
background:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
43
|
+
primary: "#3B82F6", // Blue 500
|
|
44
|
+
primaryPressed: "#2563EB", // Blue 600
|
|
45
|
+
background: "#111827", // Gray 900
|
|
46
|
+
surface: "#1F2937", // Gray 800 - container/modal surface
|
|
47
|
+
backgroundSecondary: "#1F2937", // Gray 800 - alias for surface (backwards compat)
|
|
48
|
+
text: "#F9FAFB", // Gray 50
|
|
49
|
+
textSecondary: "#9CA3AF", // Gray 400
|
|
50
|
+
textPlaceholder: "#6B7280", // Gray 500
|
|
51
|
+
textOnPrimary: "#FFFFFF",
|
|
52
|
+
border: "#374151", // Gray 700
|
|
53
|
+
borderFocused: "#3B82F6", // Blue 500
|
|
54
|
+
error: "#EF4444", // Red 500
|
|
55
|
+
success: "#22C55E", // Green 500
|
|
56
|
+
warning: "#F59E0B", // Amber 500
|
|
57
|
+
info: "#3B82F6", // Blue 500
|
|
58
|
+
overlay: "rgba(0, 0, 0, 0.5)",
|
|
59
|
+
overlayDark: "rgba(0, 0, 0, 0.8)",
|
|
60
|
+
accent1: "#3B82F6", // Blue 500 (camera)
|
|
61
|
+
accent2: "#22C55E", // Green 500 (library)
|
|
62
|
+
accent3: "#F59E0B", // Amber 500 (files)
|
|
59
63
|
};
|
|
60
64
|
|
|
61
65
|
/**
|
|
@@ -63,22 +67,22 @@ export const darkColors: HarkenColors = {
|
|
|
63
67
|
* Uses system font for maximum compatibility.
|
|
64
68
|
*/
|
|
65
69
|
export const defaultTypography: HarkenTypography = {
|
|
66
|
-
fontFamily:
|
|
70
|
+
fontFamily: "System",
|
|
67
71
|
fontFamilyHeading: undefined, // Falls back to fontFamily
|
|
68
72
|
|
|
69
73
|
titleSize: 20,
|
|
70
74
|
titleLineHeight: 1.3,
|
|
71
|
-
titleWeight:
|
|
75
|
+
titleWeight: "600",
|
|
72
76
|
|
|
73
77
|
bodySize: 16,
|
|
74
78
|
bodyLineHeight: 1.5,
|
|
75
|
-
bodyWeight:
|
|
79
|
+
bodyWeight: "normal",
|
|
76
80
|
|
|
77
81
|
labelSize: 14,
|
|
78
|
-
labelWeight:
|
|
82
|
+
labelWeight: "500",
|
|
79
83
|
|
|
80
84
|
captionSize: 12,
|
|
81
|
-
captionWeight:
|
|
85
|
+
captionWeight: "normal",
|
|
82
86
|
};
|
|
83
87
|
|
|
84
88
|
/**
|
|
@@ -126,26 +130,51 @@ export const darkTheme: HarkenTheme = {
|
|
|
126
130
|
radii: defaultRadii,
|
|
127
131
|
};
|
|
128
132
|
|
|
133
|
+
/** Extended theme type that includes optional sizing and opacity */
|
|
134
|
+
type ExtendedHarkenTheme = HarkenTheme & {
|
|
135
|
+
sizing?: Partial<HarkenSizing>;
|
|
136
|
+
opacity?: Partial<HarkenOpacity>;
|
|
137
|
+
};
|
|
138
|
+
|
|
129
139
|
/**
|
|
130
140
|
* Creates a theme by merging overrides with a base theme.
|
|
141
|
+
*
|
|
142
|
+
* Note: For full resolved theme with component aliases, use `resolveTheme` instead.
|
|
143
|
+
* This function creates a raw HarkenTheme suitable for passing to resolveTheme.
|
|
131
144
|
*/
|
|
132
145
|
export function createTheme(
|
|
133
|
-
baseTheme:
|
|
146
|
+
baseTheme: ExtendedHarkenTheme,
|
|
134
147
|
overrides?: {
|
|
135
148
|
colors?: Partial<HarkenColors>;
|
|
136
149
|
typography?: Partial<HarkenTypography>;
|
|
137
150
|
spacing?: Partial<HarkenSpacing>;
|
|
138
151
|
radii?: Partial<HarkenRadii>;
|
|
152
|
+
sizing?: Partial<HarkenSizing>;
|
|
153
|
+
opacity?: Partial<HarkenOpacity>;
|
|
139
154
|
}
|
|
140
|
-
):
|
|
155
|
+
): ExtendedHarkenTheme {
|
|
141
156
|
if (!overrides) {
|
|
142
157
|
return baseTheme;
|
|
143
158
|
}
|
|
144
159
|
|
|
160
|
+
// Merge sizing if either base or overrides has it
|
|
161
|
+
const baseSizing = baseTheme.sizing;
|
|
162
|
+
const overrideSizing = overrides.sizing;
|
|
163
|
+
const mergedSizing =
|
|
164
|
+
baseSizing || overrideSizing ? { ...baseSizing, ...overrideSizing } : undefined;
|
|
165
|
+
|
|
166
|
+
// Merge opacity if either base or overrides has it
|
|
167
|
+
const baseOpacity = baseTheme.opacity;
|
|
168
|
+
const overrideOpacity = overrides.opacity;
|
|
169
|
+
const mergedOpacity =
|
|
170
|
+
baseOpacity || overrideOpacity ? { ...baseOpacity, ...overrideOpacity } : undefined;
|
|
171
|
+
|
|
145
172
|
return {
|
|
146
173
|
colors: { ...baseTheme.colors, ...overrides.colors },
|
|
147
174
|
typography: { ...baseTheme.typography, ...overrides.typography },
|
|
148
175
|
spacing: { ...baseTheme.spacing, ...overrides.spacing },
|
|
149
176
|
radii: { ...baseTheme.radii, ...overrides.radii },
|
|
177
|
+
...(mergedSizing && { sizing: mergedSizing }),
|
|
178
|
+
...(mergedOpacity && { opacity: mergedOpacity }),
|
|
150
179
|
};
|
|
151
180
|
}
|
package/src/theme/index.ts
CHANGED
|
@@ -4,11 +4,21 @@ export type {
|
|
|
4
4
|
HarkenTypography,
|
|
5
5
|
HarkenSpacing,
|
|
6
6
|
HarkenRadii,
|
|
7
|
+
HarkenSizing,
|
|
8
|
+
HarkenOpacity,
|
|
7
9
|
HarkenTheme,
|
|
8
10
|
PartialHarkenTheme,
|
|
9
11
|
TextWeight,
|
|
10
12
|
ThemeMode,
|
|
11
|
-
|
|
13
|
+
// Resolved theme types
|
|
14
|
+
ResolvedHarkenColors,
|
|
15
|
+
ResolvedHarkenSpacing,
|
|
16
|
+
ResolvedHarkenRadii,
|
|
17
|
+
ResolvedHarkenSizing,
|
|
18
|
+
ResolvedHarkenOpacity,
|
|
19
|
+
ResolvedHarkenTheme,
|
|
20
|
+
HarkenComponentTokens,
|
|
21
|
+
} from "./types";
|
|
12
22
|
|
|
13
23
|
// Default theme exports
|
|
14
24
|
export {
|
|
@@ -20,4 +30,7 @@ export {
|
|
|
20
30
|
lightTheme,
|
|
21
31
|
darkTheme,
|
|
22
32
|
createTheme,
|
|
23
|
-
} from
|
|
33
|
+
} from "./defaults";
|
|
34
|
+
|
|
35
|
+
// Theme resolver
|
|
36
|
+
export { resolveTheme } from "./resolver";
|