@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
package/src/index.ts
CHANGED
|
@@ -20,33 +20,25 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
// Provider and context
|
|
23
|
-
export { HarkenProvider } from
|
|
24
|
-
export type { HarkenContextValue } from
|
|
23
|
+
export { HarkenProvider } from "./context";
|
|
24
|
+
export type { HarkenContextValue } from "./context";
|
|
25
25
|
|
|
26
26
|
// Hooks (core)
|
|
27
|
-
export {
|
|
28
|
-
|
|
29
|
-
useHarkenContext,
|
|
30
|
-
useAnonymousId,
|
|
31
|
-
useFeedback,
|
|
32
|
-
} from './hooks';
|
|
33
|
-
export type { SubmitFeedbackParams, UseFeedbackResult } from './hooks';
|
|
27
|
+
export { useHarkenTheme, useHarkenContext, useAnonymousId, useFeedback } from "./hooks";
|
|
28
|
+
export type { SubmitFeedbackParams, UseFeedbackResult } from "./hooks";
|
|
34
29
|
|
|
35
30
|
// Hooks (attachments)
|
|
36
|
-
export { useAttachmentUpload } from
|
|
37
|
-
export type {
|
|
38
|
-
AttachmentState,
|
|
39
|
-
UseAttachmentUploadResult,
|
|
40
|
-
} from './hooks/useAttachmentUpload';
|
|
31
|
+
export { useAttachmentUpload } from "./hooks/useAttachmentUpload";
|
|
32
|
+
export type { AttachmentState, UseAttachmentUploadResult } from "./hooks/useAttachmentUpload";
|
|
41
33
|
|
|
42
|
-
export { useAttachmentPicker } from
|
|
34
|
+
export { useAttachmentPicker } from "./hooks/useAttachmentPicker";
|
|
43
35
|
export type {
|
|
44
36
|
AttachmentSourceConfig,
|
|
45
37
|
UseAttachmentPickerResult,
|
|
46
|
-
} from
|
|
38
|
+
} from "./hooks/useAttachmentPicker";
|
|
47
39
|
|
|
48
|
-
export { useAttachmentStatus } from
|
|
49
|
-
export type { AttachmentStatus } from
|
|
40
|
+
export { useAttachmentStatus } from "./hooks/useAttachmentStatus";
|
|
41
|
+
export type { AttachmentStatus } from "./hooks/useAttachmentStatus";
|
|
50
42
|
|
|
51
43
|
// Theme system
|
|
52
44
|
export type {
|
|
@@ -58,7 +50,7 @@ export type {
|
|
|
58
50
|
PartialHarkenTheme,
|
|
59
51
|
TextWeight,
|
|
60
52
|
ThemeMode,
|
|
61
|
-
} from
|
|
53
|
+
} from "./theme";
|
|
62
54
|
|
|
63
55
|
export {
|
|
64
56
|
lightColors,
|
|
@@ -69,18 +61,14 @@ export {
|
|
|
69
61
|
lightTheme,
|
|
70
62
|
darkTheme,
|
|
71
63
|
createTheme,
|
|
72
|
-
} from
|
|
64
|
+
} from "./theme";
|
|
73
65
|
|
|
74
66
|
// Storage and identity
|
|
75
|
-
export type { SecureStorage } from
|
|
76
|
-
export {
|
|
77
|
-
createSecureStoreAdapter,
|
|
78
|
-
createMemoryStorage,
|
|
79
|
-
IdentityStore,
|
|
80
|
-
} from './storage';
|
|
67
|
+
export type { SecureStorage } from "./storage";
|
|
68
|
+
export { createSecureStoreAdapter, createMemoryStorage, IdentityStore } from "./storage";
|
|
81
69
|
|
|
82
70
|
// Utilities
|
|
83
|
-
export { generateUUID } from
|
|
71
|
+
export { generateUUID } from "./utils";
|
|
84
72
|
|
|
85
73
|
// Components (core)
|
|
86
74
|
export {
|
|
@@ -90,7 +78,7 @@ export {
|
|
|
90
78
|
CategorySelector,
|
|
91
79
|
FeedbackForm,
|
|
92
80
|
DEFAULT_CATEGORIES,
|
|
93
|
-
} from
|
|
81
|
+
} from "./components";
|
|
94
82
|
|
|
95
83
|
export type {
|
|
96
84
|
ThemedTextProps,
|
|
@@ -102,7 +90,7 @@ export type {
|
|
|
102
90
|
CategoryOption,
|
|
103
91
|
FeedbackFormProps,
|
|
104
92
|
FeedbackFormData,
|
|
105
|
-
} from
|
|
93
|
+
} from "./components";
|
|
106
94
|
|
|
107
95
|
// Components (attachments)
|
|
108
96
|
export {
|
|
@@ -110,7 +98,7 @@ export {
|
|
|
110
98
|
UploadStatusOverlay,
|
|
111
99
|
AttachmentPreview,
|
|
112
100
|
AttachmentGrid,
|
|
113
|
-
} from
|
|
101
|
+
} from "./components";
|
|
114
102
|
|
|
115
103
|
export type {
|
|
116
104
|
AttachmentPickerProps,
|
|
@@ -120,11 +108,11 @@ export type {
|
|
|
120
108
|
UploadStatusLabels,
|
|
121
109
|
AttachmentPreviewProps,
|
|
122
110
|
AttachmentGridProps,
|
|
123
|
-
} from
|
|
111
|
+
} from "./components";
|
|
124
112
|
|
|
125
113
|
// FeedbackSheet (with full attachment support)
|
|
126
|
-
export { FeedbackSheet } from
|
|
127
|
-
export type { FeedbackSheetProps } from
|
|
114
|
+
export { FeedbackSheet } from "./attachments/FeedbackSheet";
|
|
115
|
+
export type { FeedbackSheetProps } from "./attachments/FeedbackSheet";
|
|
128
116
|
|
|
129
117
|
// API client
|
|
130
118
|
export {
|
|
@@ -135,8 +123,8 @@ export {
|
|
|
135
123
|
HarkenNetworkError,
|
|
136
124
|
withRetry,
|
|
137
125
|
DEFAULT_RETRY_CONFIG,
|
|
138
|
-
} from
|
|
139
|
-
export type { HarkenClientConfig, RetryConfig } from
|
|
126
|
+
} from "./api";
|
|
127
|
+
export type { HarkenClientConfig, RetryConfig } from "./api";
|
|
140
128
|
|
|
141
129
|
// Configuration types
|
|
142
130
|
export type {
|
|
@@ -145,24 +133,12 @@ export type {
|
|
|
145
133
|
FeedbackCategory,
|
|
146
134
|
Platform,
|
|
147
135
|
DeviceMetadata,
|
|
148
|
-
} from
|
|
136
|
+
} from "./types";
|
|
149
137
|
|
|
150
138
|
// Domain types
|
|
151
|
-
export { UploadPhase, DEFAULT_UPLOAD_RETRY_CONFIG } from
|
|
152
|
-
export type {
|
|
153
|
-
QueueItem,
|
|
154
|
-
QueueStatus,
|
|
155
|
-
UploadProgress,
|
|
156
|
-
UploadRetryConfig,
|
|
157
|
-
} from './domain';
|
|
139
|
+
export { UploadPhase, DEFAULT_UPLOAD_RETRY_CONFIG } from "./domain";
|
|
140
|
+
export type { QueueItem, QueueStatus, UploadProgress, UploadRetryConfig } from "./domain";
|
|
158
141
|
|
|
159
142
|
// Services (for advanced usage)
|
|
160
|
-
export {
|
|
161
|
-
|
|
162
|
-
uploadQueueService,
|
|
163
|
-
UploadQueueStorage,
|
|
164
|
-
} from './services';
|
|
165
|
-
export type {
|
|
166
|
-
UploadQueueServiceConfig,
|
|
167
|
-
EnqueueParams,
|
|
168
|
-
} from './services';
|
|
143
|
+
export { UploadQueueService, uploadQueueService, UploadQueueStorage } from "./services";
|
|
144
|
+
export type { UploadQueueServiceConfig, EnqueueParams } from "./services";
|
package/src/services/index.ts
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
export {
|
|
2
|
-
UploadQueueService,
|
|
3
|
-
uploadQueueService,
|
|
4
|
-
} from './uploadQueueService';
|
|
1
|
+
export { UploadQueueService, uploadQueueService } from "./uploadQueueService";
|
|
5
2
|
|
|
6
|
-
export type {
|
|
7
|
-
UploadQueueServiceConfig,
|
|
8
|
-
EnqueueParams,
|
|
9
|
-
} from './uploadQueueService';
|
|
3
|
+
export type { UploadQueueServiceConfig, EnqueueParams } from "./uploadQueueService";
|
|
10
4
|
|
|
11
|
-
export { UploadQueueStorage } from
|
|
5
|
+
export { UploadQueueStorage } from "./uploadQueueStorage";
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { UploadPhase } from "../domain";
|
|
3
|
+
import type { QueueItem, UploadProgress } from "../domain";
|
|
4
|
+
|
|
5
|
+
// Mock storage - use vi.hoisted to ensure variables are available for mocks
|
|
6
|
+
const { mockLoadQueue, mockSaveQueue, mockClearQueue } = vi.hoisted(() => ({
|
|
7
|
+
mockLoadQueue: vi.fn(),
|
|
8
|
+
mockSaveQueue: vi.fn(),
|
|
9
|
+
mockClearQueue: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
vi.mock("./uploadQueueStorage", () => ({
|
|
13
|
+
UploadQueueStorage: class MockStorage {
|
|
14
|
+
loadQueue = mockLoadQueue;
|
|
15
|
+
saveQueue = mockSaveQueue;
|
|
16
|
+
clearQueue = mockClearQueue;
|
|
17
|
+
},
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
// Mock FileSystem
|
|
21
|
+
const { mockUploadTask } = vi.hoisted(() => ({
|
|
22
|
+
mockUploadTask: {
|
|
23
|
+
uploadAsync: vi.fn(),
|
|
24
|
+
cancelAsync: vi.fn(),
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
vi.mock("expo-file-system/legacy", () => ({
|
|
29
|
+
createUploadTask: vi.fn(() => mockUploadTask),
|
|
30
|
+
FileSystemUploadType: { BINARY_CONTENT: "binary" },
|
|
31
|
+
FileSystemSessionType: { BACKGROUND: "background" },
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
// Mock NetInfo
|
|
35
|
+
vi.mock("@react-native-community/netinfo", () => ({
|
|
36
|
+
default: {
|
|
37
|
+
addEventListener: vi.fn(() => vi.fn()),
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
// Mock generateUUID
|
|
42
|
+
const uuidState = vi.hoisted(() => ({ counter: 0 }));
|
|
43
|
+
vi.mock("../utils", () => ({
|
|
44
|
+
generateUUID: () => `uuid-${++uuidState.counter}`,
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
// Import after mocks are set up
|
|
48
|
+
import { UploadQueueService } from "./uploadQueueService";
|
|
49
|
+
|
|
50
|
+
// Mock client
|
|
51
|
+
function createMockClient() {
|
|
52
|
+
return {
|
|
53
|
+
createAttachmentUpload: vi.fn().mockResolvedValue({
|
|
54
|
+
attachment_id: "att_123",
|
|
55
|
+
upload_url: "https://storage.example.com/upload",
|
|
56
|
+
upload_expires_at: new Date(Date.now() + 3600000).toISOString(),
|
|
57
|
+
}),
|
|
58
|
+
confirmAttachment: vi.fn().mockResolvedValue({}),
|
|
59
|
+
reportAttachmentFailure: vi.fn().mockResolvedValue({}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
describe("UploadQueueService", () => {
|
|
64
|
+
let service: UploadQueueService;
|
|
65
|
+
let mockClient: ReturnType<typeof createMockClient>;
|
|
66
|
+
|
|
67
|
+
beforeEach(() => {
|
|
68
|
+
vi.clearAllMocks();
|
|
69
|
+
uuidState.counter = 0;
|
|
70
|
+
mockLoadQueue.mockResolvedValue([]);
|
|
71
|
+
mockSaveQueue.mockResolvedValue(undefined);
|
|
72
|
+
mockUploadTask.uploadAsync.mockResolvedValue({ status: 200 });
|
|
73
|
+
|
|
74
|
+
// Get fresh instance by destroying any existing one
|
|
75
|
+
service = UploadQueueService.getInstance();
|
|
76
|
+
service.destroy();
|
|
77
|
+
service = UploadQueueService.getInstance();
|
|
78
|
+
|
|
79
|
+
mockClient = createMockClient();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
afterEach(() => {
|
|
83
|
+
service.destroy();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("initialization", () => {
|
|
87
|
+
it("loads persisted queue on initialize", async () => {
|
|
88
|
+
// Use an item that's already COMPLETED so it doesn't get processed
|
|
89
|
+
const persistedItem: QueueItem = {
|
|
90
|
+
id: "queue_1",
|
|
91
|
+
attachmentId: "att_persisted",
|
|
92
|
+
localUri: "file:///photo.jpg",
|
|
93
|
+
uploadUrl: "https://storage.example.com/upload",
|
|
94
|
+
uploadExpiresAt: new Date(Date.now() + 3600000).toISOString(),
|
|
95
|
+
mimeType: "image/jpeg",
|
|
96
|
+
fileName: "photo.jpg",
|
|
97
|
+
fileSize: 1000,
|
|
98
|
+
phase: UploadPhase.COMPLETED, // Already done, won't be processed
|
|
99
|
+
progress: 1,
|
|
100
|
+
attemptNumber: 1,
|
|
101
|
+
maxAttempts: 3,
|
|
102
|
+
createdAt: new Date().toISOString(),
|
|
103
|
+
completedAt: new Date().toISOString(),
|
|
104
|
+
};
|
|
105
|
+
mockLoadQueue.mockResolvedValue([persistedItem]);
|
|
106
|
+
|
|
107
|
+
await service.initialize({ client: mockClient as never });
|
|
108
|
+
|
|
109
|
+
const status = service.getQueueStatus();
|
|
110
|
+
expect(status.total).toBe(1);
|
|
111
|
+
expect(status.completed).toBe(1);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("resets uploading items to queued on initialize", async () => {
|
|
115
|
+
const uploadingItem: QueueItem = {
|
|
116
|
+
id: "queue_1",
|
|
117
|
+
attachmentId: "att_interrupted",
|
|
118
|
+
localUri: "file:///photo.jpg",
|
|
119
|
+
uploadUrl: "https://storage.example.com/upload",
|
|
120
|
+
uploadExpiresAt: new Date(Date.now() + 3600000).toISOString(),
|
|
121
|
+
mimeType: "image/jpeg",
|
|
122
|
+
fileName: "photo.jpg",
|
|
123
|
+
fileSize: 1000,
|
|
124
|
+
phase: UploadPhase.UPLOADING, // Was mid-upload when app was killed
|
|
125
|
+
progress: 0.5,
|
|
126
|
+
attemptNumber: 1,
|
|
127
|
+
maxAttempts: 3,
|
|
128
|
+
createdAt: new Date().toISOString(),
|
|
129
|
+
};
|
|
130
|
+
mockLoadQueue.mockResolvedValue([uploadingItem]);
|
|
131
|
+
|
|
132
|
+
// processQueue() only processes items in QUEUED phase, so receiving
|
|
133
|
+
// any progress event for this item proves it was reset from UPLOADING to QUEUED
|
|
134
|
+
const progressReceived = new Promise<UploadProgress>((resolve) => {
|
|
135
|
+
service.onProgress((p) => {
|
|
136
|
+
if (p.attachmentId === "att_interrupted") resolve(p);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await service.initialize({ client: mockClient as never });
|
|
141
|
+
|
|
142
|
+
// Wait for a progress event on the interrupted item
|
|
143
|
+
const progress = await progressReceived;
|
|
144
|
+
|
|
145
|
+
// The item was processed (received progress), which proves it was reset
|
|
146
|
+
// to QUEUED since processQueue() skips non-QUEUED items
|
|
147
|
+
expect(progress.attachmentId).toBe("att_interrupted");
|
|
148
|
+
// Should have progressed beyond initial state
|
|
149
|
+
expect([UploadPhase.UPLOADING, UploadPhase.CONFIRMING, UploadPhase.COMPLETED]).toContain(
|
|
150
|
+
progress.phase
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe("enqueue", () => {
|
|
156
|
+
it("creates queue item and calls presign API", async () => {
|
|
157
|
+
await service.initialize({ client: mockClient as never });
|
|
158
|
+
|
|
159
|
+
const result = await service.enqueue({
|
|
160
|
+
localUri: "file:///new-photo.jpg",
|
|
161
|
+
mimeType: "image/jpeg",
|
|
162
|
+
fileName: "new-photo.jpg",
|
|
163
|
+
fileSize: 2000,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(result.attachmentId).toBe("att_123");
|
|
167
|
+
expect(mockClient.createAttachmentUpload).toHaveBeenCalledWith({
|
|
168
|
+
filename: "new-photo.jpg",
|
|
169
|
+
content_type: "image/jpeg",
|
|
170
|
+
size: 2000,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Item exists (may have started processing already)
|
|
174
|
+
const item = service.getItemByAttachmentId("att_123");
|
|
175
|
+
expect(item).toBeDefined();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("persists queue after enqueue", async () => {
|
|
179
|
+
await service.initialize({ client: mockClient as never });
|
|
180
|
+
|
|
181
|
+
await service.enqueue({
|
|
182
|
+
localUri: "file:///photo.jpg",
|
|
183
|
+
mimeType: "image/jpeg",
|
|
184
|
+
fileName: "photo.jpg",
|
|
185
|
+
fileSize: 1000,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(mockSaveQueue).toHaveBeenCalled();
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe("progress events", () => {
|
|
193
|
+
it("fires progress callback on state changes", async () => {
|
|
194
|
+
await service.initialize({ client: mockClient as never });
|
|
195
|
+
|
|
196
|
+
const progressEvents: { phase: UploadPhase; progress: number }[] = [];
|
|
197
|
+
service.onProgress((progress) => {
|
|
198
|
+
progressEvents.push({ phase: progress.phase, progress: progress.progress });
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
await service.enqueue({
|
|
202
|
+
localUri: "file:///photo.jpg",
|
|
203
|
+
mimeType: "image/jpeg",
|
|
204
|
+
fileName: "photo.jpg",
|
|
205
|
+
fileSize: 1000,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Should have emitted initial QUEUED progress
|
|
209
|
+
expect(progressEvents.length).toBeGreaterThan(0);
|
|
210
|
+
expect(progressEvents[0]).toMatchObject({
|
|
211
|
+
phase: UploadPhase.QUEUED,
|
|
212
|
+
progress: 0,
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("unsubscribe stops receiving events", async () => {
|
|
217
|
+
await service.initialize({ client: mockClient as never });
|
|
218
|
+
|
|
219
|
+
let callCount = 0;
|
|
220
|
+
const unsubscribe = service.onProgress(() => {
|
|
221
|
+
callCount++;
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
await service.enqueue({
|
|
225
|
+
localUri: "file:///photo1.jpg",
|
|
226
|
+
mimeType: "image/jpeg",
|
|
227
|
+
fileName: "photo1.jpg",
|
|
228
|
+
fileSize: 1000,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const countAfterFirst = callCount;
|
|
232
|
+
|
|
233
|
+
unsubscribe();
|
|
234
|
+
|
|
235
|
+
await service.enqueue({
|
|
236
|
+
localUri: "file:///photo2.jpg",
|
|
237
|
+
mimeType: "image/jpeg",
|
|
238
|
+
fileName: "photo2.jpg",
|
|
239
|
+
fileSize: 1000,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Count should not increase after unsubscribe
|
|
243
|
+
expect(callCount).toBe(countAfterFirst);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe("retry scheduling", () => {
|
|
248
|
+
it("resets attempt count and clears error on retry", async () => {
|
|
249
|
+
const failedItem: QueueItem = {
|
|
250
|
+
id: "queue_1",
|
|
251
|
+
attachmentId: "att_failed",
|
|
252
|
+
localUri: "file:///photo.jpg",
|
|
253
|
+
uploadUrl: "https://storage.example.com/upload",
|
|
254
|
+
uploadExpiresAt: new Date(Date.now() + 3600000).toISOString(),
|
|
255
|
+
mimeType: "image/jpeg",
|
|
256
|
+
fileName: "photo.jpg",
|
|
257
|
+
fileSize: 1000,
|
|
258
|
+
phase: UploadPhase.FAILED,
|
|
259
|
+
progress: 0,
|
|
260
|
+
attemptNumber: 2,
|
|
261
|
+
maxAttempts: 3,
|
|
262
|
+
createdAt: new Date().toISOString(),
|
|
263
|
+
lastError: "Previous error",
|
|
264
|
+
};
|
|
265
|
+
mockLoadQueue.mockResolvedValue([failedItem]);
|
|
266
|
+
|
|
267
|
+
await service.initialize({ client: mockClient as never });
|
|
268
|
+
|
|
269
|
+
// Verify item is in failed state
|
|
270
|
+
expect(service.getItemByAttachmentId("att_failed")?.phase).toBe(UploadPhase.FAILED);
|
|
271
|
+
expect(service.getItemByAttachmentId("att_failed")?.attemptNumber).toBe(2);
|
|
272
|
+
|
|
273
|
+
// Retry should reset state and trigger processing
|
|
274
|
+
await service.retryItem("att_failed");
|
|
275
|
+
|
|
276
|
+
// After retry, item should exist and lastError should be cleared
|
|
277
|
+
// Note: attemptNumber may be 0 or 1 depending on whether processing started
|
|
278
|
+
const item = service.getItemByAttachmentId("att_failed");
|
|
279
|
+
expect(item).toBeDefined();
|
|
280
|
+
expect(item?.lastError).toBeUndefined(); // Error cleared
|
|
281
|
+
// Phase should no longer be FAILED
|
|
282
|
+
expect(item?.phase).not.toBe(UploadPhase.FAILED);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("throws when retrying non-failed item", async () => {
|
|
286
|
+
// Use COMPLETED item since QUEUED will start processing
|
|
287
|
+
const completedItem: QueueItem = {
|
|
288
|
+
id: "queue_1",
|
|
289
|
+
attachmentId: "att_completed",
|
|
290
|
+
localUri: "file:///photo.jpg",
|
|
291
|
+
uploadUrl: "https://storage.example.com/upload",
|
|
292
|
+
uploadExpiresAt: new Date(Date.now() + 3600000).toISOString(),
|
|
293
|
+
mimeType: "image/jpeg",
|
|
294
|
+
fileName: "photo.jpg",
|
|
295
|
+
fileSize: 1000,
|
|
296
|
+
phase: UploadPhase.COMPLETED,
|
|
297
|
+
progress: 1,
|
|
298
|
+
attemptNumber: 1,
|
|
299
|
+
maxAttempts: 3,
|
|
300
|
+
createdAt: new Date().toISOString(),
|
|
301
|
+
};
|
|
302
|
+
mockLoadQueue.mockResolvedValue([completedItem]);
|
|
303
|
+
|
|
304
|
+
await service.initialize({ client: mockClient as never });
|
|
305
|
+
|
|
306
|
+
await expect(service.retryItem("att_completed")).rejects.toThrow("not in failed state");
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe("failure handling", () => {
|
|
311
|
+
it("transitions to FAILED after max attempts exceeded", async () => {
|
|
312
|
+
// Create item that has already used all attempts
|
|
313
|
+
const maxedOutItem: QueueItem = {
|
|
314
|
+
id: "queue_1",
|
|
315
|
+
attachmentId: "att_maxed",
|
|
316
|
+
localUri: "file:///photo.jpg",
|
|
317
|
+
uploadUrl: "https://storage.example.com/upload",
|
|
318
|
+
uploadExpiresAt: new Date(Date.now() + 3600000).toISOString(),
|
|
319
|
+
mimeType: "image/jpeg",
|
|
320
|
+
fileName: "photo.jpg",
|
|
321
|
+
fileSize: 1000,
|
|
322
|
+
phase: UploadPhase.QUEUED,
|
|
323
|
+
progress: 0,
|
|
324
|
+
attemptNumber: 2, // Will be 3 after next attempt
|
|
325
|
+
maxAttempts: 3,
|
|
326
|
+
createdAt: new Date().toISOString(),
|
|
327
|
+
};
|
|
328
|
+
mockLoadQueue.mockResolvedValue([maxedOutItem]);
|
|
329
|
+
|
|
330
|
+
// Make upload fail
|
|
331
|
+
mockUploadTask.uploadAsync.mockResolvedValue({ status: 500 });
|
|
332
|
+
|
|
333
|
+
// Wait for FAILED phase progress event (deterministic signal)
|
|
334
|
+
const failedReceived = new Promise<void>((resolve) => {
|
|
335
|
+
service.onProgress((p) => {
|
|
336
|
+
if (p.phase === UploadPhase.FAILED) resolve();
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
await service.initialize({ client: mockClient as never });
|
|
341
|
+
|
|
342
|
+
// Wait for the failure to be processed
|
|
343
|
+
await failedReceived;
|
|
344
|
+
|
|
345
|
+
const item = service.getItemByAttachmentId("att_maxed");
|
|
346
|
+
expect(item?.phase).toBe(UploadPhase.FAILED);
|
|
347
|
+
expect(item?.attemptNumber).toBe(3);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
describe("queue status", () => {
|
|
352
|
+
it("returns correct counts by phase", async () => {
|
|
353
|
+
// Use only terminal states (COMPLETED, FAILED) to avoid async processing
|
|
354
|
+
const items: QueueItem[] = [
|
|
355
|
+
{
|
|
356
|
+
id: "queue_1",
|
|
357
|
+
attachmentId: "att_1",
|
|
358
|
+
localUri: "file:///1.jpg",
|
|
359
|
+
uploadUrl: "https://storage.example.com/1",
|
|
360
|
+
uploadExpiresAt: new Date(Date.now() + 3600000).toISOString(),
|
|
361
|
+
mimeType: "image/jpeg",
|
|
362
|
+
fileName: "1.jpg",
|
|
363
|
+
fileSize: 1000,
|
|
364
|
+
phase: UploadPhase.COMPLETED,
|
|
365
|
+
progress: 1,
|
|
366
|
+
attemptNumber: 1,
|
|
367
|
+
maxAttempts: 3,
|
|
368
|
+
createdAt: new Date().toISOString(),
|
|
369
|
+
completedAt: new Date().toISOString(),
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
id: "queue_2",
|
|
373
|
+
attachmentId: "att_2",
|
|
374
|
+
localUri: "file:///2.jpg",
|
|
375
|
+
uploadUrl: "https://storage.example.com/2",
|
|
376
|
+
uploadExpiresAt: new Date(Date.now() + 3600000).toISOString(),
|
|
377
|
+
mimeType: "image/jpeg",
|
|
378
|
+
fileName: "2.jpg",
|
|
379
|
+
fileSize: 1000,
|
|
380
|
+
phase: UploadPhase.COMPLETED,
|
|
381
|
+
progress: 1,
|
|
382
|
+
attemptNumber: 1,
|
|
383
|
+
maxAttempts: 3,
|
|
384
|
+
createdAt: new Date().toISOString(),
|
|
385
|
+
completedAt: new Date().toISOString(),
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
id: "queue_3",
|
|
389
|
+
attachmentId: "att_3",
|
|
390
|
+
localUri: "file:///3.jpg",
|
|
391
|
+
uploadUrl: "https://storage.example.com/3",
|
|
392
|
+
uploadExpiresAt: new Date(Date.now() + 3600000).toISOString(),
|
|
393
|
+
mimeType: "image/jpeg",
|
|
394
|
+
fileName: "3.jpg",
|
|
395
|
+
fileSize: 1000,
|
|
396
|
+
phase: UploadPhase.FAILED,
|
|
397
|
+
progress: 0,
|
|
398
|
+
attemptNumber: 3,
|
|
399
|
+
maxAttempts: 3,
|
|
400
|
+
createdAt: new Date().toISOString(),
|
|
401
|
+
lastError: "Upload failed",
|
|
402
|
+
},
|
|
403
|
+
];
|
|
404
|
+
mockLoadQueue.mockResolvedValue(items);
|
|
405
|
+
|
|
406
|
+
await service.initialize({ client: mockClient as never });
|
|
407
|
+
|
|
408
|
+
const status = service.getQueueStatus();
|
|
409
|
+
expect(status.total).toBe(3);
|
|
410
|
+
expect(status.completed).toBe(2);
|
|
411
|
+
expect(status.failed).toBe(1);
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe("cancelItem", () => {
|
|
416
|
+
it("removes item from queue", async () => {
|
|
417
|
+
const item: QueueItem = {
|
|
418
|
+
id: "queue_1",
|
|
419
|
+
attachmentId: "att_cancel",
|
|
420
|
+
localUri: "file:///photo.jpg",
|
|
421
|
+
uploadUrl: "https://storage.example.com/upload",
|
|
422
|
+
uploadExpiresAt: new Date(Date.now() + 3600000).toISOString(),
|
|
423
|
+
mimeType: "image/jpeg",
|
|
424
|
+
fileName: "photo.jpg",
|
|
425
|
+
fileSize: 1000,
|
|
426
|
+
phase: UploadPhase.QUEUED,
|
|
427
|
+
progress: 0,
|
|
428
|
+
attemptNumber: 0,
|
|
429
|
+
maxAttempts: 3,
|
|
430
|
+
createdAt: new Date().toISOString(),
|
|
431
|
+
};
|
|
432
|
+
mockLoadQueue.mockResolvedValue([item]);
|
|
433
|
+
|
|
434
|
+
await service.initialize({ client: mockClient as never });
|
|
435
|
+
|
|
436
|
+
expect(service.getQueueStatus().total).toBe(1);
|
|
437
|
+
|
|
438
|
+
await service.cancelItem("att_cancel");
|
|
439
|
+
|
|
440
|
+
expect(service.getQueueStatus().total).toBe(0);
|
|
441
|
+
expect(mockSaveQueue).toHaveBeenCalled();
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
describe("clearCompleted", () => {
|
|
446
|
+
it("removes only completed items", async () => {
|
|
447
|
+
const items: QueueItem[] = [
|
|
448
|
+
{
|
|
449
|
+
id: "queue_1",
|
|
450
|
+
attachmentId: "att_completed",
|
|
451
|
+
localUri: "file:///1.jpg",
|
|
452
|
+
uploadUrl: "https://storage.example.com/1",
|
|
453
|
+
uploadExpiresAt: new Date(Date.now() + 3600000).toISOString(),
|
|
454
|
+
mimeType: "image/jpeg",
|
|
455
|
+
fileName: "1.jpg",
|
|
456
|
+
fileSize: 1000,
|
|
457
|
+
phase: UploadPhase.COMPLETED,
|
|
458
|
+
progress: 1,
|
|
459
|
+
attemptNumber: 1,
|
|
460
|
+
maxAttempts: 3,
|
|
461
|
+
createdAt: new Date().toISOString(),
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
id: "queue_2",
|
|
465
|
+
attachmentId: "att_queued",
|
|
466
|
+
localUri: "file:///2.jpg",
|
|
467
|
+
uploadUrl: "https://storage.example.com/2",
|
|
468
|
+
uploadExpiresAt: new Date(Date.now() + 3600000).toISOString(),
|
|
469
|
+
mimeType: "image/jpeg",
|
|
470
|
+
fileName: "2.jpg",
|
|
471
|
+
fileSize: 1000,
|
|
472
|
+
phase: UploadPhase.QUEUED,
|
|
473
|
+
progress: 0,
|
|
474
|
+
attemptNumber: 0,
|
|
475
|
+
maxAttempts: 3,
|
|
476
|
+
createdAt: new Date().toISOString(),
|
|
477
|
+
},
|
|
478
|
+
];
|
|
479
|
+
mockLoadQueue.mockResolvedValue(items);
|
|
480
|
+
|
|
481
|
+
await service.initialize({ client: mockClient as never });
|
|
482
|
+
|
|
483
|
+
await service.clearCompleted();
|
|
484
|
+
|
|
485
|
+
expect(service.getItemByAttachmentId("att_completed")).toBeUndefined();
|
|
486
|
+
expect(service.getItemByAttachmentId("att_queued")).toBeDefined();
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
});
|