@uploadista/vue 0.0.20 → 0.1.0

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.
Files changed (75) hide show
  1. package/dist/components/index.d.mts +2 -2
  2. package/dist/components/index.mjs +1 -1
  3. package/dist/{components-CskPs6sR.css → components-B_L33hsM.css} +33 -33
  4. package/dist/{components-CskPs6sR.css.map → components-B_L33hsM.css.map} +1 -1
  5. package/dist/components-MZ9ETx9c.mjs +2 -0
  6. package/dist/components-MZ9ETx9c.mjs.map +1 -0
  7. package/dist/composables/index.d.mts +1 -1
  8. package/dist/composables/index.mjs +1 -1
  9. package/dist/composables-Dny_9Zrg.mjs +2 -0
  10. package/dist/composables-Dny_9Zrg.mjs.map +1 -0
  11. package/dist/{index-CLOy812P.d.mts → index-6Scxoy1b.d.mts} +412 -408
  12. package/dist/index-6Scxoy1b.d.mts.map +1 -0
  13. package/dist/{index-CDJUpsAf.d.mts → index-BpCRFLJ5.d.mts} +8 -8
  14. package/dist/index-BpCRFLJ5.d.mts.map +1 -0
  15. package/dist/{index-BSlqFF1H.d.mts → index-RY4FPqAk.d.mts} +431 -431
  16. package/dist/index-RY4FPqAk.d.mts.map +1 -0
  17. package/dist/index.d.mts +4 -4
  18. package/dist/index.mjs +1 -1
  19. package/dist/providers/index.d.mts +1 -1
  20. package/dist/providers/index.mjs +1 -1
  21. package/dist/{providers-fqmOwF71.mjs → providers-CjhEBaQV.mjs} +2 -2
  22. package/dist/providers-CjhEBaQV.mjs.map +1 -0
  23. package/dist/useUploadistaClient-WVuo8jYH.mjs.map +1 -1
  24. package/dist/utils/index.d.mts +62 -2
  25. package/dist/utils/index.d.mts.map +1 -0
  26. package/package.json +11 -9
  27. package/src/__tests__/setup.ts +154 -0
  28. package/src/components/FlowUploadList.vue +25 -24
  29. package/src/components/UploadList.vue +3 -6
  30. package/src/components/UploadZone.vue +2 -5
  31. package/src/components/flow/Flow.vue +16 -4
  32. package/src/components/flow/FlowDropZone.vue +4 -2
  33. package/src/components/flow/FlowInput.vue +14 -8
  34. package/src/components/flow/FlowInputDropZone.vue +4 -2
  35. package/src/components/flow/FlowInputPreview.vue +3 -1
  36. package/src/components/flow/FlowProgress.vue +1 -1
  37. package/src/components/flow/FlowStatus.vue +1 -1
  38. package/src/components/flow/useFlowContext.ts +7 -5
  39. package/src/components/index.ts +2 -3
  40. package/src/components/upload/Upload.vue +16 -5
  41. package/src/components/upload/UploadCancel.vue +2 -4
  42. package/src/components/upload/UploadClearCompleted.vue +1 -1
  43. package/src/components/upload/UploadDropZone.vue +7 -7
  44. package/src/components/upload/UploadError.vue +2 -4
  45. package/src/components/upload/UploadItem.vue +8 -6
  46. package/src/components/upload/UploadItems.vue +2 -4
  47. package/src/components/upload/UploadProgress.vue +2 -4
  48. package/src/components/upload/UploadReset.vue +2 -4
  49. package/src/components/upload/UploadRetry.vue +2 -4
  50. package/src/components/upload/UploadStartAll.vue +6 -6
  51. package/src/components/upload/UploadStatus.vue +2 -4
  52. package/src/components/upload/index.ts +21 -25
  53. package/src/composables/eventUtils.test.ts +267 -0
  54. package/src/composables/eventUtils.ts +5 -4
  55. package/src/composables/index.ts +1 -1
  56. package/src/composables/useDragDrop.test.ts +304 -0
  57. package/src/composables/useFlow.ts +6 -2
  58. package/src/composables/useFlowManagerContext.ts +5 -1
  59. package/src/composables/useUploadEvents.ts +1 -4
  60. package/src/composables/useUploadistaClient.test.ts +152 -0
  61. package/src/index.ts +43 -45
  62. package/src/providers/FlowManagerProvider.vue +5 -2
  63. package/src/utils/index.test.ts +396 -0
  64. package/src/utils/is-browser-file.test.ts +45 -0
  65. package/vitest.config.ts +25 -0
  66. package/dist/components-DoBB6sqm.mjs +0 -2
  67. package/dist/components-DoBB6sqm.mjs.map +0 -1
  68. package/dist/composables-BZ2c_WgI.mjs +0 -2
  69. package/dist/composables-BZ2c_WgI.mjs.map +0 -1
  70. package/dist/index-BLNNvTVx.d.mts +0 -62
  71. package/dist/index-BLNNvTVx.d.mts.map +0 -1
  72. package/dist/index-BSlqFF1H.d.mts.map +0 -1
  73. package/dist/index-CDJUpsAf.d.mts.map +0 -1
  74. package/dist/index-CLOy812P.d.mts.map +0 -1
  75. package/dist/providers-fqmOwF71.mjs.map +0 -1
@@ -0,0 +1,152 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { defineComponent, h, ref } from "vue";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import {
5
+ UPLOADISTA_CLIENT_KEY,
6
+ UPLOADISTA_EVENT_SUBSCRIBERS_KEY,
7
+ } from "./plugin";
8
+ import { useUploadistaClient } from "./useUploadistaClient";
9
+
10
+ describe("useUploadistaClient", () => {
11
+ const createMockClient = () => ({
12
+ upload: vi.fn(),
13
+ abort: vi.fn(),
14
+ getProgress: vi.fn(),
15
+ });
16
+
17
+ const createTestComponent = (setupFn: () => unknown) => {
18
+ return defineComponent({
19
+ setup() {
20
+ const result = setupFn();
21
+ return { result };
22
+ },
23
+ render() {
24
+ return h("div");
25
+ },
26
+ });
27
+ };
28
+
29
+ describe("client injection", () => {
30
+ it("should return injected client", () => {
31
+ const mockClient = createMockClient();
32
+ let result: ReturnType<typeof useUploadistaClient> | null = null;
33
+
34
+ const TestComponent = createTestComponent(() => {
35
+ result = useUploadistaClient();
36
+ return result;
37
+ });
38
+
39
+ mount(TestComponent, {
40
+ global: {
41
+ provide: {
42
+ [UPLOADISTA_CLIENT_KEY as symbol]: mockClient,
43
+ [UPLOADISTA_EVENT_SUBSCRIBERS_KEY as symbol]: ref(new Set()),
44
+ },
45
+ },
46
+ });
47
+
48
+ expect(result).not.toBeNull();
49
+ expect(result!.client).toBe(mockClient);
50
+ });
51
+
52
+ it("should throw error when used outside provider context", () => {
53
+ const TestComponent = createTestComponent(() => {
54
+ useUploadistaClient();
55
+ });
56
+
57
+ expect(() => {
58
+ mount(TestComponent);
59
+ }).toThrow(
60
+ "useUploadistaClient must be used within a component tree that has the Uploadista plugin or provider installed",
61
+ );
62
+ });
63
+ });
64
+
65
+ describe("event subscription", () => {
66
+ it("should add handler to event subscribers", () => {
67
+ const mockClient = createMockClient();
68
+ const eventSubscribers = ref(new Set<(event: unknown) => void>());
69
+ let result: ReturnType<typeof useUploadistaClient> | null = null;
70
+
71
+ const TestComponent = createTestComponent(() => {
72
+ result = useUploadistaClient();
73
+ return result;
74
+ });
75
+
76
+ mount(TestComponent, {
77
+ global: {
78
+ provide: {
79
+ [UPLOADISTA_CLIENT_KEY as symbol]: mockClient,
80
+ [UPLOADISTA_EVENT_SUBSCRIBERS_KEY as symbol]: eventSubscribers,
81
+ },
82
+ },
83
+ });
84
+
85
+ const handler = vi.fn();
86
+ result!.subscribeToEvents(handler);
87
+
88
+ expect(eventSubscribers.value.has(handler)).toBe(true);
89
+ });
90
+
91
+ it("should remove handler on unsubscribe", () => {
92
+ const mockClient = createMockClient();
93
+ const eventSubscribers = ref(new Set<(event: unknown) => void>());
94
+ let result: ReturnType<typeof useUploadistaClient> | null = null;
95
+
96
+ const TestComponent = createTestComponent(() => {
97
+ result = useUploadistaClient();
98
+ return result;
99
+ });
100
+
101
+ mount(TestComponent, {
102
+ global: {
103
+ provide: {
104
+ [UPLOADISTA_CLIENT_KEY as symbol]: mockClient,
105
+ [UPLOADISTA_EVENT_SUBSCRIBERS_KEY as symbol]: eventSubscribers,
106
+ },
107
+ },
108
+ });
109
+
110
+ const handler = vi.fn();
111
+ const unsubscribe = result!.subscribeToEvents(handler);
112
+
113
+ expect(eventSubscribers.value.has(handler)).toBe(true);
114
+
115
+ unsubscribe();
116
+
117
+ expect(eventSubscribers.value.has(handler)).toBe(false);
118
+ });
119
+
120
+ it("should warn and return no-op when subscribers not available", () => {
121
+ const mockClient = createMockClient();
122
+ let result: ReturnType<typeof useUploadistaClient> | null = null;
123
+ const consoleWarn = vi.spyOn(console, "warn").mockImplementation(() => {});
124
+
125
+ const TestComponent = createTestComponent(() => {
126
+ result = useUploadistaClient();
127
+ return result;
128
+ });
129
+
130
+ mount(TestComponent, {
131
+ global: {
132
+ provide: {
133
+ [UPLOADISTA_CLIENT_KEY as symbol]: mockClient,
134
+ // Not providing UPLOADISTA_EVENT_SUBSCRIBERS_KEY
135
+ },
136
+ },
137
+ });
138
+
139
+ const handler = vi.fn();
140
+ const unsubscribe = result!.subscribeToEvents(handler);
141
+
142
+ expect(consoleWarn).toHaveBeenCalledWith(
143
+ expect.stringContaining("subscribeToEvents called but no event subscribers provided"),
144
+ );
145
+
146
+ // Unsubscribe should be a no-op and not throw
147
+ expect(() => unsubscribe()).not.toThrow();
148
+
149
+ consoleWarn.mockRestore();
150
+ });
151
+ });
152
+ });
package/src/index.ts CHANGED
@@ -18,73 +18,71 @@
18
18
 
19
19
  // Re-export all components
20
20
  export * from "./components";
21
-
22
- // Re-export composables with explicit types to avoid conflicts
23
- // Types with potential conflicts are renamed for clarity
24
- export {
25
- // Event composables
26
- isFlowEvent,
27
- isUploadEvent,
28
- useUploadistaEvents,
29
- useFlowEvents,
30
- useUploadEvents,
31
- // Plugin
32
- createUploadistaPlugin,
33
- UPLOADISTA_CLIENT_KEY,
34
- // Drag and drop
35
- useDragDrop,
36
- // Flow composables
37
- useFlow,
38
- // Multi-upload
39
- useMultiFlowUpload,
40
- useMultiUpload,
41
- // Upload composables
42
- useUpload,
43
- // Client
44
- useUploadistaClient,
45
- // Metrics
46
- useUploadMetrics,
47
- } from "./composables";
48
-
49
21
  // Re-export types from composables
50
22
  export type {
51
- // Event types
52
- UseFlowEventsOptions,
53
- UploadFailedEventData,
54
- UploadFileEventData,
55
- UploadProgressEventData,
56
- UploadValidationFailedEventData,
57
- UploadValidationSuccessEventData,
58
- UploadValidationWarningEventData,
59
- UseUploadEventsOptions,
60
- // Plugin types
61
- UploadistaPluginOptions,
23
+ // Upload types - rename to avoid conflict
24
+ ChunkMetrics,
62
25
  // Drag and drop types
63
26
  DragDropOptions,
64
27
  DragDropState,
28
+ // Metrics types
29
+ FileUploadMetrics,
65
30
  // Flow types
66
31
  FlowInputMetadata,
67
32
  FlowUploadState,
68
33
  FlowUploadStatus,
69
34
  InputExecutionState,
70
- UseFlowReturn,
71
35
  // Multi-upload types - rename to avoid conflict
72
36
  MultiUploadOptions,
73
37
  MultiUploadState,
74
- UploadItem as MultiUploadItem,
75
- // Upload types - rename to avoid conflict
76
- ChunkMetrics,
77
38
  PerformanceInsights,
39
+ UploadFailedEventData,
40
+ UploadFileEventData,
78
41
  UploadInput,
42
+ UploadItem as MultiUploadItem,
43
+ // Plugin types
44
+ UploadistaPluginOptions,
45
+ UploadProgressEventData,
79
46
  UploadSessionMetrics,
80
47
  UploadState,
81
48
  UploadStatus as UploadStatusType,
49
+ UploadValidationFailedEventData,
50
+ UploadValidationSuccessEventData,
51
+ UploadValidationWarningEventData,
52
+ // Event types
53
+ UseFlowEventsOptions,
54
+ UseFlowReturn,
55
+ UseUploadEventsOptions,
82
56
  // Client types
83
57
  UseUploadistaClientReturn,
84
- // Metrics types
85
- FileUploadMetrics,
86
58
  UseUploadMetricsOptions,
87
59
  } from "./composables";
60
+ // Re-export composables with explicit types to avoid conflicts
61
+ // Types with potential conflicts are renamed for clarity
62
+ export {
63
+ // Plugin
64
+ createUploadistaPlugin,
65
+ // Event composables
66
+ isFlowEvent,
67
+ isUploadEvent,
68
+ UPLOADISTA_CLIENT_KEY,
69
+ // Drag and drop
70
+ useDragDrop,
71
+ // Flow composables
72
+ useFlow,
73
+ useFlowEvents,
74
+ // Multi-upload
75
+ useMultiFlowUpload,
76
+ useMultiUpload,
77
+ // Upload composables
78
+ useUpload,
79
+ useUploadEvents,
80
+ // Client
81
+ useUploadistaClient,
82
+ useUploadistaEvents,
83
+ // Metrics
84
+ useUploadMetrics,
85
+ } from "./composables";
88
86
 
89
87
  export * from "./providers";
90
88
  // Re-export utilities
@@ -3,7 +3,10 @@
3
3
  </template>
4
4
 
5
5
  <script setup lang="ts">
6
- import type { BrowserUploadInput, UploadistaEvent } from "@uploadista/client-browser";
6
+ import type {
7
+ BrowserUploadInput,
8
+ UploadistaEvent,
9
+ } from "@uploadista/client-browser";
7
10
  import {
8
11
  FlowManager,
9
12
  type FlowManagerCallbacks,
@@ -11,7 +14,7 @@ import {
11
14
  } from "@uploadista/client-core";
12
15
  import { EventType, type FlowEvent } from "@uploadista/core/flow";
13
16
  import { UploadEventType } from "@uploadista/core/types";
14
- import { onMounted, onBeforeUnmount, provide } from "vue";
17
+ import { onBeforeUnmount, onMounted, provide } from "vue";
15
18
  import { useUploadistaClient } from "../composables/useUploadistaClient";
16
19
 
17
20
  /**
@@ -0,0 +1,396 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ createMockFile,
4
+ mockCreateObjectURL,
5
+ mockRevokeObjectURL,
6
+ } from "../__tests__/setup";
7
+ import {
8
+ calculateProgress,
9
+ createFilePreview,
10
+ formatDuration,
11
+ formatFileSize,
12
+ formatSpeed,
13
+ generateUploadId,
14
+ getFileExtension,
15
+ isAudioFile,
16
+ isDocumentFile,
17
+ isImageFile,
18
+ isVideoFile,
19
+ revokeFilePreview,
20
+ validateFileType,
21
+ } from "./index";
22
+
23
+ describe("formatFileSize", () => {
24
+ it("should format 0 bytes", () => {
25
+ expect(formatFileSize(0)).toBe("0 Bytes");
26
+ });
27
+
28
+ it("should format bytes", () => {
29
+ expect(formatFileSize(500)).toBe("500 Bytes");
30
+ });
31
+
32
+ it("should format kilobytes", () => {
33
+ expect(formatFileSize(1024)).toBe("1 KB");
34
+ expect(formatFileSize(1536)).toBe("1.5 KB");
35
+ });
36
+
37
+ it("should format megabytes", () => {
38
+ expect(formatFileSize(1024 * 1024)).toBe("1 MB");
39
+ expect(formatFileSize(5.5 * 1024 * 1024)).toBe("5.5 MB");
40
+ });
41
+
42
+ it("should format gigabytes", () => {
43
+ expect(formatFileSize(1024 * 1024 * 1024)).toBe("1 GB");
44
+ });
45
+
46
+ it("should format terabytes", () => {
47
+ expect(formatFileSize(1024 * 1024 * 1024 * 1024)).toBe("1 TB");
48
+ });
49
+ });
50
+
51
+ describe("formatSpeed", () => {
52
+ it("should format 0 B/s", () => {
53
+ expect(formatSpeed(0)).toBe("0 B/s");
54
+ });
55
+
56
+ it("should format bytes per second", () => {
57
+ expect(formatSpeed(500)).toBe("500 B/s");
58
+ });
59
+
60
+ it("should format kilobytes per second", () => {
61
+ expect(formatSpeed(1024)).toBe("1 KB/s");
62
+ });
63
+
64
+ it("should format megabytes per second", () => {
65
+ expect(formatSpeed(1024 * 1024)).toBe("1 MB/s");
66
+ });
67
+
68
+ it("should format gigabytes per second", () => {
69
+ expect(formatSpeed(1024 * 1024 * 1024)).toBe("1 GB/s");
70
+ });
71
+ });
72
+
73
+ describe("formatDuration", () => {
74
+ it("should format milliseconds", () => {
75
+ expect(formatDuration(500)).toBe("500ms");
76
+ expect(formatDuration(999)).toBe("999ms");
77
+ });
78
+
79
+ it("should format seconds", () => {
80
+ expect(formatDuration(1000)).toBe("1s");
81
+ expect(formatDuration(30000)).toBe("30s");
82
+ expect(formatDuration(59999)).toBe("60s");
83
+ });
84
+
85
+ it("should format minutes", () => {
86
+ expect(formatDuration(60000)).toBe("1m");
87
+ expect(formatDuration(90000)).toBe("1m 30s");
88
+ expect(formatDuration(120000)).toBe("2m");
89
+ });
90
+
91
+ it("should format hours", () => {
92
+ expect(formatDuration(3600000)).toBe("1h");
93
+ expect(formatDuration(3600000 + 1800000)).toBe("1h 30m");
94
+ expect(formatDuration(7200000)).toBe("2h");
95
+ });
96
+ });
97
+
98
+ describe("validateFileType", () => {
99
+ it("should return true when accept is empty", () => {
100
+ const file = createMockFile("test.txt", 100, "text/plain");
101
+ expect(validateFileType(file, [])).toBe(true);
102
+ });
103
+
104
+ it("should validate by file extension", () => {
105
+ const txtFile = createMockFile("test.txt", 100, "text/plain");
106
+ const pdfFile = createMockFile("doc.pdf", 100, "application/pdf");
107
+
108
+ expect(validateFileType(txtFile, [".txt"])).toBe(true);
109
+ expect(validateFileType(txtFile, [".pdf"])).toBe(false);
110
+ expect(validateFileType(pdfFile, [".pdf", ".doc"])).toBe(true);
111
+ });
112
+
113
+ it("should validate by exact MIME type", () => {
114
+ const file = createMockFile("test.txt", 100, "text/plain");
115
+
116
+ expect(validateFileType(file, ["text/plain"])).toBe(true);
117
+ expect(validateFileType(file, ["application/json"])).toBe(false);
118
+ });
119
+
120
+ it("should validate by wildcard MIME type", () => {
121
+ const imageFile = createMockFile("photo.jpg", 100, "image/jpeg");
122
+ const videoFile = createMockFile("video.mp4", 100, "video/mp4");
123
+
124
+ expect(validateFileType(imageFile, ["image/*"])).toBe(true);
125
+ expect(validateFileType(imageFile, ["video/*"])).toBe(false);
126
+ expect(validateFileType(videoFile, ["video/*"])).toBe(true);
127
+ });
128
+
129
+ it("should validate with mixed accept types", () => {
130
+ const imageFile = createMockFile("photo.jpg", 100, "image/jpeg");
131
+ const pdfFile = createMockFile("doc.pdf", 100, "application/pdf");
132
+
133
+ expect(validateFileType(imageFile, ["image/*", ".pdf"])).toBe(true);
134
+ expect(validateFileType(pdfFile, ["image/*", ".pdf"])).toBe(true);
135
+ });
136
+
137
+ it("should be case-insensitive for file extensions", () => {
138
+ const file = createMockFile("TEST.TXT", 100, "text/plain");
139
+ expect(validateFileType(file, [".txt"])).toBe(true);
140
+ });
141
+ });
142
+
143
+ describe("generateUploadId", () => {
144
+ it("should generate unique IDs", () => {
145
+ const id1 = generateUploadId();
146
+ const id2 = generateUploadId();
147
+
148
+ expect(id1).not.toBe(id2);
149
+ });
150
+
151
+ it("should start with 'upload-'", () => {
152
+ const id = generateUploadId();
153
+ expect(id).toMatch(/^upload-/);
154
+ });
155
+
156
+ it("should contain timestamp and random string", () => {
157
+ const id = generateUploadId();
158
+ expect(id).toMatch(/^upload-\d+-[a-z0-9]+$/);
159
+ });
160
+ });
161
+
162
+ describe("getFileExtension", () => {
163
+ it("should extract file extension", () => {
164
+ expect(getFileExtension("file.txt")).toBe("txt");
165
+ expect(getFileExtension("image.jpeg")).toBe("jpeg");
166
+ expect(getFileExtension("document.pdf")).toBe("pdf");
167
+ });
168
+
169
+ it("should return lowercase extension", () => {
170
+ expect(getFileExtension("FILE.TXT")).toBe("txt");
171
+ expect(getFileExtension("Image.JPEG")).toBe("jpeg");
172
+ });
173
+
174
+ it("should handle files with multiple dots", () => {
175
+ expect(getFileExtension("archive.tar.gz")).toBe("gz");
176
+ expect(getFileExtension("my.file.name.txt")).toBe("txt");
177
+ });
178
+
179
+ it("should return empty string for files without extension", () => {
180
+ expect(getFileExtension("noextension")).toBe("");
181
+ expect(getFileExtension("Makefile")).toBe("");
182
+ });
183
+ });
184
+
185
+ describe("isImageFile", () => {
186
+ it("should identify image files", () => {
187
+ expect(isImageFile(createMockFile("photo.jpg", 100, "image/jpeg"))).toBe(
188
+ true,
189
+ );
190
+ expect(isImageFile(createMockFile("photo.png", 100, "image/png"))).toBe(
191
+ true,
192
+ );
193
+ expect(isImageFile(createMockFile("photo.gif", 100, "image/gif"))).toBe(
194
+ true,
195
+ );
196
+ expect(isImageFile(createMockFile("photo.webp", 100, "image/webp"))).toBe(
197
+ true,
198
+ );
199
+ });
200
+
201
+ it("should reject non-image files", () => {
202
+ expect(isImageFile(createMockFile("video.mp4", 100, "video/mp4"))).toBe(
203
+ false,
204
+ );
205
+ expect(isImageFile(createMockFile("doc.pdf", 100, "application/pdf"))).toBe(
206
+ false,
207
+ );
208
+ });
209
+ });
210
+
211
+ describe("isVideoFile", () => {
212
+ it("should identify video files", () => {
213
+ expect(isVideoFile(createMockFile("video.mp4", 100, "video/mp4"))).toBe(
214
+ true,
215
+ );
216
+ expect(isVideoFile(createMockFile("video.webm", 100, "video/webm"))).toBe(
217
+ true,
218
+ );
219
+ expect(isVideoFile(createMockFile("video.avi", 100, "video/avi"))).toBe(
220
+ true,
221
+ );
222
+ });
223
+
224
+ it("should reject non-video files", () => {
225
+ expect(isVideoFile(createMockFile("photo.jpg", 100, "image/jpeg"))).toBe(
226
+ false,
227
+ );
228
+ expect(isVideoFile(createMockFile("audio.mp3", 100, "audio/mpeg"))).toBe(
229
+ false,
230
+ );
231
+ });
232
+ });
233
+
234
+ describe("isAudioFile", () => {
235
+ it("should identify audio files", () => {
236
+ expect(isAudioFile(createMockFile("song.mp3", 100, "audio/mpeg"))).toBe(
237
+ true,
238
+ );
239
+ expect(isAudioFile(createMockFile("song.wav", 100, "audio/wav"))).toBe(
240
+ true,
241
+ );
242
+ expect(isAudioFile(createMockFile("song.ogg", 100, "audio/ogg"))).toBe(
243
+ true,
244
+ );
245
+ });
246
+
247
+ it("should reject non-audio files", () => {
248
+ expect(isAudioFile(createMockFile("video.mp4", 100, "video/mp4"))).toBe(
249
+ false,
250
+ );
251
+ expect(isAudioFile(createMockFile("photo.jpg", 100, "image/jpeg"))).toBe(
252
+ false,
253
+ );
254
+ });
255
+ });
256
+
257
+ describe("isDocumentFile", () => {
258
+ it("should identify PDF files", () => {
259
+ expect(
260
+ isDocumentFile(createMockFile("doc.pdf", 100, "application/pdf")),
261
+ ).toBe(true);
262
+ });
263
+
264
+ it("should identify Word documents", () => {
265
+ expect(
266
+ isDocumentFile(createMockFile("doc.doc", 100, "application/msword")),
267
+ ).toBe(true);
268
+ expect(
269
+ isDocumentFile(
270
+ createMockFile(
271
+ "doc.docx",
272
+ 100,
273
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
274
+ ),
275
+ ),
276
+ ).toBe(true);
277
+ });
278
+
279
+ it("should identify Excel files", () => {
280
+ expect(
281
+ isDocumentFile(createMockFile("sheet.xls", 100, "application/vnd.ms-excel")),
282
+ ).toBe(true);
283
+ expect(
284
+ isDocumentFile(
285
+ createMockFile(
286
+ "sheet.xlsx",
287
+ 100,
288
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
289
+ ),
290
+ ),
291
+ ).toBe(true);
292
+ });
293
+
294
+ it("should identify PowerPoint files", () => {
295
+ expect(
296
+ isDocumentFile(
297
+ createMockFile("pres.ppt", 100, "application/vnd.ms-powerpoint"),
298
+ ),
299
+ ).toBe(true);
300
+ expect(
301
+ isDocumentFile(
302
+ createMockFile(
303
+ "pres.pptx",
304
+ 100,
305
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
306
+ ),
307
+ ),
308
+ ).toBe(true);
309
+ });
310
+
311
+ it("should identify text files", () => {
312
+ expect(
313
+ isDocumentFile(createMockFile("readme.txt", 100, "text/plain")),
314
+ ).toBe(true);
315
+ expect(isDocumentFile(createMockFile("data.csv", 100, "text/csv"))).toBe(
316
+ true,
317
+ );
318
+ });
319
+
320
+ it("should identify RTF files", () => {
321
+ expect(
322
+ isDocumentFile(createMockFile("doc.rtf", 100, "application/rtf")),
323
+ ).toBe(true);
324
+ });
325
+
326
+ it("should reject non-document files", () => {
327
+ expect(
328
+ isDocumentFile(createMockFile("photo.jpg", 100, "image/jpeg")),
329
+ ).toBe(false);
330
+ expect(
331
+ isDocumentFile(createMockFile("video.mp4", 100, "video/mp4")),
332
+ ).toBe(false);
333
+ });
334
+ });
335
+
336
+ describe("createFilePreview", () => {
337
+ it("should create preview URL for image files", () => {
338
+ const file = createMockFile("photo.jpg", 100, "image/jpeg");
339
+ const preview = createFilePreview(file);
340
+ expect(preview).toBe("blob:mock-url");
341
+ expect(mockCreateObjectURL).toHaveBeenCalledWith(file);
342
+ });
343
+
344
+ it("should create preview URL for video files", () => {
345
+ const file = createMockFile("video.mp4", 100, "video/mp4");
346
+ const preview = createFilePreview(file);
347
+ expect(preview).toBe("blob:mock-url");
348
+ expect(mockCreateObjectURL).toHaveBeenCalledWith(file);
349
+ });
350
+
351
+ it("should create preview URL for audio files", () => {
352
+ const file = createMockFile("audio.mp3", 100, "audio/mpeg");
353
+ const preview = createFilePreview(file);
354
+ expect(preview).toBe("blob:mock-url");
355
+ expect(mockCreateObjectURL).toHaveBeenCalledWith(file);
356
+ });
357
+
358
+ it("should return null for unsupported file types", () => {
359
+ const file = createMockFile("doc.pdf", 100, "application/pdf");
360
+ const preview = createFilePreview(file);
361
+ expect(preview).toBeNull();
362
+ });
363
+ });
364
+
365
+ describe("revokeFilePreview", () => {
366
+ it("should call URL.revokeObjectURL", () => {
367
+ const mockUrl = "blob:test-url";
368
+ revokeFilePreview(mockUrl);
369
+ expect(mockRevokeObjectURL).toHaveBeenCalledWith(mockUrl);
370
+ });
371
+ });
372
+
373
+ describe("calculateProgress", () => {
374
+ it("should return 0 when total is 0", () => {
375
+ expect(calculateProgress(50, 0)).toBe(0);
376
+ });
377
+
378
+ it("should calculate correct percentage", () => {
379
+ expect(calculateProgress(50, 100)).toBe(50);
380
+ expect(calculateProgress(25, 100)).toBe(25);
381
+ expect(calculateProgress(75, 100)).toBe(75);
382
+ });
383
+
384
+ it("should round to nearest integer", () => {
385
+ expect(calculateProgress(33, 100)).toBe(33);
386
+ expect(calculateProgress(1, 3)).toBe(33);
387
+ });
388
+
389
+ it("should cap at 100%", () => {
390
+ expect(calculateProgress(150, 100)).toBe(100);
391
+ });
392
+
393
+ it("should not go below 0%", () => {
394
+ expect(calculateProgress(-10, 100)).toBe(0);
395
+ });
396
+ });