@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/hooks/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Core hooks (no native module dependencies)
|
|
2
|
-
export { useHarkenTheme } from
|
|
3
|
-
export { useHarkenContext } from
|
|
4
|
-
export { useAnonymousId } from
|
|
5
|
-
export { useFeedback } from
|
|
6
|
-
export type { SubmitFeedbackParams, UseFeedbackResult } from
|
|
2
|
+
export { useHarkenTheme } from "./useHarkenTheme";
|
|
3
|
+
export { useHarkenContext } from "./useHarkenContext";
|
|
4
|
+
export { useAnonymousId } from "./useAnonymousId";
|
|
5
|
+
export { useFeedback } from "./useFeedback";
|
|
6
|
+
export type { SubmitFeedbackParams, UseFeedbackResult } from "./useFeedback";
|
|
7
7
|
|
|
8
8
|
// Note: Attachment hooks (useAttachmentUpload, useAttachmentStatus) are
|
|
9
9
|
// exported from '@harkenapp/sdk-react-native/attachments' to avoid eager
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vitest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
5
|
+
import { renderHook, waitFor } from "@testing-library/react";
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { useAnonymousId } from "./useAnonymousId";
|
|
8
|
+
import { HarkenContext } from "../context";
|
|
9
|
+
|
|
10
|
+
// Mock identityStore
|
|
11
|
+
function createMockIdentityStore() {
|
|
12
|
+
return {
|
|
13
|
+
getAnonymousId: vi.fn(),
|
|
14
|
+
getUserId: vi.fn(),
|
|
15
|
+
setUserId: vi.fn(),
|
|
16
|
+
clearUserId: vi.fn(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Helper to create a wrapper with HarkenContext
|
|
21
|
+
function createWrapper(contextValue: unknown) {
|
|
22
|
+
return function Wrapper({ children }: { children: React.ReactNode }) {
|
|
23
|
+
return React.createElement(HarkenContext.Provider, { value: contextValue as never }, children);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("useAnonymousId", () => {
|
|
28
|
+
let mockIdentityStore: ReturnType<typeof createMockIdentityStore>;
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
vi.clearAllMocks();
|
|
32
|
+
mockIdentityStore = createMockIdentityStore();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("context requirement", () => {
|
|
36
|
+
it("throws when used outside HarkenProvider", () => {
|
|
37
|
+
// Suppress console.error for expected error
|
|
38
|
+
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
39
|
+
|
|
40
|
+
expect(() => {
|
|
41
|
+
renderHook(() => useAnonymousId());
|
|
42
|
+
}).toThrow("useAnonymousId must be used within a HarkenProvider");
|
|
43
|
+
|
|
44
|
+
consoleSpy.mockRestore();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("loading state", () => {
|
|
49
|
+
it("starts with isLoading true and anonymousId null", () => {
|
|
50
|
+
mockIdentityStore.getAnonymousId.mockImplementation(
|
|
51
|
+
() => new Promise(() => {}) // Never resolves
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const wrapper = createWrapper({
|
|
55
|
+
identityStore: mockIdentityStore,
|
|
56
|
+
client: {},
|
|
57
|
+
config: {},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const { result } = renderHook(() => useAnonymousId(), { wrapper });
|
|
61
|
+
|
|
62
|
+
expect(result.current.isLoading).toBe(true);
|
|
63
|
+
expect(result.current.anonymousId).toBeNull();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("sets isLoading false after loading completes", async () => {
|
|
67
|
+
mockIdentityStore.getAnonymousId.mockResolvedValue("anon-123");
|
|
68
|
+
|
|
69
|
+
const wrapper = createWrapper({
|
|
70
|
+
identityStore: mockIdentityStore,
|
|
71
|
+
client: {},
|
|
72
|
+
config: {},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const { result } = renderHook(() => useAnonymousId(), { wrapper });
|
|
76
|
+
|
|
77
|
+
await waitFor(() => {
|
|
78
|
+
expect(result.current.isLoading).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("anonymousId retrieval", () => {
|
|
84
|
+
it("returns anonymousId from identityStore", async () => {
|
|
85
|
+
mockIdentityStore.getAnonymousId.mockResolvedValue("stable-uuid-abc");
|
|
86
|
+
|
|
87
|
+
const wrapper = createWrapper({
|
|
88
|
+
identityStore: mockIdentityStore,
|
|
89
|
+
client: {},
|
|
90
|
+
config: {},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const { result } = renderHook(() => useAnonymousId(), { wrapper });
|
|
94
|
+
|
|
95
|
+
await waitFor(() => {
|
|
96
|
+
expect(result.current.anonymousId).toBe("stable-uuid-abc");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(mockIdentityStore.getAnonymousId).toHaveBeenCalledTimes(1);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("returns different IDs for different stores", async () => {
|
|
103
|
+
const store1 = createMockIdentityStore();
|
|
104
|
+
const store2 = createMockIdentityStore();
|
|
105
|
+
store1.getAnonymousId.mockResolvedValue("id-from-store-1");
|
|
106
|
+
store2.getAnonymousId.mockResolvedValue("id-from-store-2");
|
|
107
|
+
|
|
108
|
+
const wrapper1 = createWrapper({
|
|
109
|
+
identityStore: store1,
|
|
110
|
+
client: {},
|
|
111
|
+
config: {},
|
|
112
|
+
});
|
|
113
|
+
const wrapper2 = createWrapper({
|
|
114
|
+
identityStore: store2,
|
|
115
|
+
client: {},
|
|
116
|
+
config: {},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const { result: result1 } = renderHook(() => useAnonymousId(), { wrapper: wrapper1 });
|
|
120
|
+
const { result: result2 } = renderHook(() => useAnonymousId(), { wrapper: wrapper2 });
|
|
121
|
+
|
|
122
|
+
await waitFor(() => {
|
|
123
|
+
expect(result1.current.anonymousId).toBe("id-from-store-1");
|
|
124
|
+
});
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
expect(result2.current.anonymousId).toBe("id-from-store-2");
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("error handling", () => {
|
|
132
|
+
it("handles storage errors gracefully", async () => {
|
|
133
|
+
mockIdentityStore.getAnonymousId.mockRejectedValue(new Error("Storage failed"));
|
|
134
|
+
|
|
135
|
+
const wrapper = createWrapper({
|
|
136
|
+
identityStore: mockIdentityStore,
|
|
137
|
+
client: {},
|
|
138
|
+
config: {},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const { result } = renderHook(() => useAnonymousId(), { wrapper });
|
|
142
|
+
|
|
143
|
+
await waitFor(() => {
|
|
144
|
+
expect(result.current.isLoading).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Should have null ID but not crash
|
|
148
|
+
expect(result.current.anonymousId).toBeNull();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("cleanup", () => {
|
|
153
|
+
it("does not update state after unmount", async () => {
|
|
154
|
+
// Spy on console.error to detect React's unmounted component warning
|
|
155
|
+
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
156
|
+
|
|
157
|
+
let resolvePromise: (value: string) => void;
|
|
158
|
+
const promise = new Promise<string>((resolve) => {
|
|
159
|
+
resolvePromise = resolve;
|
|
160
|
+
});
|
|
161
|
+
mockIdentityStore.getAnonymousId.mockReturnValue(promise);
|
|
162
|
+
|
|
163
|
+
const wrapper = createWrapper({
|
|
164
|
+
identityStore: mockIdentityStore,
|
|
165
|
+
client: {},
|
|
166
|
+
config: {},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const { unmount } = renderHook(() => useAnonymousId(), { wrapper });
|
|
170
|
+
|
|
171
|
+
// Unmount before the promise resolves
|
|
172
|
+
unmount();
|
|
173
|
+
|
|
174
|
+
// Resolve after unmount - should not trigger setState on unmounted component
|
|
175
|
+
resolvePromise!("late-id");
|
|
176
|
+
|
|
177
|
+
// Wait for the async handler to run
|
|
178
|
+
await new Promise((resolve) => setTimeout(() => resolve(undefined), 10));
|
|
179
|
+
|
|
180
|
+
// Verify no React warning about updating unmounted component was logged
|
|
181
|
+
const reactWarnings = errorSpy.mock.calls.filter(
|
|
182
|
+
(call) => typeof call[0] === "string" && call[0].includes("unmounted component")
|
|
183
|
+
);
|
|
184
|
+
expect(reactWarnings).toHaveLength(0);
|
|
185
|
+
|
|
186
|
+
errorSpy.mockRestore();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useState, useEffect, useContext } from
|
|
2
|
-
import { HarkenContext } from
|
|
1
|
+
import { useState, useEffect, useContext } from "react";
|
|
2
|
+
import { HarkenContext } from "../context";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Hook to access the anonymous ID for the current installation.
|
|
@@ -31,7 +31,7 @@ export function useAnonymousId(): {
|
|
|
31
31
|
const context = useContext(HarkenContext);
|
|
32
32
|
|
|
33
33
|
if (!context) {
|
|
34
|
-
throw new Error(
|
|
34
|
+
throw new Error("useAnonymousId must be used within a HarkenProvider");
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// Capture identityStore to satisfy TypeScript narrowing in useEffect
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
* - Warning when no sources are enabled
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { useState, useCallback, useMemo, useEffect } from
|
|
10
|
-
import { useAttachmentUpload } from
|
|
11
|
-
import type { UseAttachmentUploadResult } from
|
|
12
|
-
import type { AttachmentPickerProps } from
|
|
9
|
+
import { useState, useCallback, useMemo, useEffect } from "react";
|
|
10
|
+
import { useAttachmentUpload } from "./useAttachmentUpload";
|
|
11
|
+
import type { UseAttachmentUploadResult } from "./useAttachmentUpload";
|
|
12
|
+
import type { AttachmentPickerProps } from "../components/AttachmentPicker";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Configuration for which attachment sources are enabled.
|
|
@@ -39,7 +39,7 @@ export interface UseAttachmentPickerResult extends UseAttachmentUploadResult {
|
|
|
39
39
|
/** Props to spread onto AttachmentPicker component */
|
|
40
40
|
pickerProps: Pick<
|
|
41
41
|
AttachmentPickerProps,
|
|
42
|
-
|
|
42
|
+
"visible" | "onClose" | "onTakePhoto" | "onPickFromLibrary" | "onPickDocument" | "options"
|
|
43
43
|
>;
|
|
44
44
|
|
|
45
45
|
/** Which sources are enabled */
|
|
@@ -138,18 +138,18 @@ export function useAttachmentPicker(
|
|
|
138
138
|
const handleTakePhoto = useCallback(async () => {
|
|
139
139
|
setIsPickerVisible(false);
|
|
140
140
|
try {
|
|
141
|
-
await pickImage(
|
|
141
|
+
await pickImage("camera");
|
|
142
142
|
} catch (e) {
|
|
143
|
-
console.error(
|
|
143
|
+
console.error("[Harken] Failed to take photo:", e);
|
|
144
144
|
}
|
|
145
145
|
}, [pickImage]);
|
|
146
146
|
|
|
147
147
|
const handlePickFromLibrary = useCallback(async () => {
|
|
148
148
|
setIsPickerVisible(false);
|
|
149
149
|
try {
|
|
150
|
-
await pickImage(
|
|
150
|
+
await pickImage("library");
|
|
151
151
|
} catch (e) {
|
|
152
|
-
console.error(
|
|
152
|
+
console.error("[Harken] Failed to pick from library:", e);
|
|
153
153
|
}
|
|
154
154
|
}, [pickImage]);
|
|
155
155
|
|
|
@@ -158,7 +158,7 @@ export function useAttachmentPicker(
|
|
|
158
158
|
try {
|
|
159
159
|
await pickDocument();
|
|
160
160
|
} catch (e) {
|
|
161
|
-
console.error(
|
|
161
|
+
console.error("[Harken] Failed to pick document:", e);
|
|
162
162
|
}
|
|
163
163
|
}, [pickDocument]);
|
|
164
164
|
|
|
@@ -175,8 +175,8 @@ export function useAttachmentPicker(
|
|
|
175
175
|
const openPicker = useCallback(() => {
|
|
176
176
|
if (enabledSourceCount === 0) {
|
|
177
177
|
console.warn(
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
"[Harken] useAttachmentPicker: No attachment sources are enabled. " +
|
|
179
|
+
"Enable at least one of: camera, library, or files."
|
|
180
180
|
);
|
|
181
181
|
return;
|
|
182
182
|
}
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Useful for components that display a single attachment with progress indicator.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { useState, useEffect } from
|
|
8
|
-
import { uploadQueueService } from
|
|
9
|
-
import { UploadPhase, UploadProgress } from
|
|
7
|
+
import { useState, useEffect } from "react";
|
|
8
|
+
import { uploadQueueService } from "../services";
|
|
9
|
+
import type { UploadPhase, UploadProgress } from "../domain";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Status information for a single attachment.
|
|
@@ -52,9 +52,7 @@ export interface AttachmentStatus {
|
|
|
52
52
|
* }
|
|
53
53
|
* ```
|
|
54
54
|
*/
|
|
55
|
-
export function useAttachmentStatus(
|
|
56
|
-
attachmentId: string
|
|
57
|
-
): AttachmentStatus | null {
|
|
55
|
+
export function useAttachmentStatus(attachmentId: string): AttachmentStatus | null {
|
|
58
56
|
const [status, setStatus] = useState<AttachmentStatus | null>(() => {
|
|
59
57
|
// Initialize with current state from queue
|
|
60
58
|
const item = uploadQueueService.getItemByAttachmentId(attachmentId);
|
|
@@ -67,17 +65,15 @@ export function useAttachmentStatus(
|
|
|
67
65
|
});
|
|
68
66
|
|
|
69
67
|
useEffect(() => {
|
|
70
|
-
const unsubscribe = uploadQueueService.onProgress(
|
|
71
|
-
(progress
|
|
72
|
-
if (progress.attachmentId !== attachmentId) return;
|
|
68
|
+
const unsubscribe = uploadQueueService.onProgress((progress: UploadProgress) => {
|
|
69
|
+
if (progress.attachmentId !== attachmentId) return;
|
|
73
70
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
);
|
|
71
|
+
setStatus({
|
|
72
|
+
phase: progress.phase,
|
|
73
|
+
progress: progress.progress,
|
|
74
|
+
error: progress.error,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
81
77
|
|
|
82
78
|
return unsubscribe;
|
|
83
79
|
}, [attachmentId]);
|