@uploadista/vue 0.0.20 → 0.1.0-beta.5
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/dist/components/index.d.mts +2 -2
- package/dist/components/index.mjs +1 -1
- package/dist/{components-CskPs6sR.css → components-B_L33hsM.css} +33 -33
- package/dist/{components-CskPs6sR.css.map → components-B_L33hsM.css.map} +1 -1
- package/dist/components-MZ9ETx9c.mjs +2 -0
- package/dist/components-MZ9ETx9c.mjs.map +1 -0
- package/dist/composables/index.d.mts +1 -1
- package/dist/composables/index.mjs +1 -1
- package/dist/composables-Dny_9Zrg.mjs +2 -0
- package/dist/composables-Dny_9Zrg.mjs.map +1 -0
- package/dist/{index-CLOy812P.d.mts → index-6Scxoy1b.d.mts} +412 -408
- package/dist/index-6Scxoy1b.d.mts.map +1 -0
- package/dist/{index-CDJUpsAf.d.mts → index-BpCRFLJ5.d.mts} +8 -8
- package/dist/index-BpCRFLJ5.d.mts.map +1 -0
- package/dist/{index-BSlqFF1H.d.mts → index-RY4FPqAk.d.mts} +431 -431
- package/dist/index-RY4FPqAk.d.mts.map +1 -0
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +1 -1
- package/dist/providers/index.d.mts +1 -1
- package/dist/providers/index.mjs +1 -1
- package/dist/{providers-fqmOwF71.mjs → providers-CjhEBaQV.mjs} +2 -2
- package/dist/providers-CjhEBaQV.mjs.map +1 -0
- package/dist/useUploadistaClient-WVuo8jYH.mjs.map +1 -1
- package/dist/utils/index.d.mts +62 -2
- package/dist/utils/index.d.mts.map +1 -0
- package/package.json +11 -9
- package/src/__tests__/setup.ts +154 -0
- package/src/components/FlowUploadList.vue +25 -24
- package/src/components/UploadList.vue +3 -6
- package/src/components/UploadZone.vue +2 -5
- package/src/components/flow/Flow.vue +16 -4
- package/src/components/flow/FlowDropZone.vue +4 -2
- package/src/components/flow/FlowInput.vue +14 -8
- package/src/components/flow/FlowInputDropZone.vue +4 -2
- package/src/components/flow/FlowInputPreview.vue +3 -1
- package/src/components/flow/FlowProgress.vue +1 -1
- package/src/components/flow/FlowStatus.vue +1 -1
- package/src/components/flow/useFlowContext.ts +7 -5
- package/src/components/index.ts +2 -3
- package/src/components/upload/Upload.vue +16 -5
- package/src/components/upload/UploadCancel.vue +2 -4
- package/src/components/upload/UploadClearCompleted.vue +1 -1
- package/src/components/upload/UploadDropZone.vue +7 -7
- package/src/components/upload/UploadError.vue +2 -4
- package/src/components/upload/UploadItem.vue +8 -6
- package/src/components/upload/UploadItems.vue +2 -4
- package/src/components/upload/UploadProgress.vue +2 -4
- package/src/components/upload/UploadReset.vue +2 -4
- package/src/components/upload/UploadRetry.vue +2 -4
- package/src/components/upload/UploadStartAll.vue +6 -6
- package/src/components/upload/UploadStatus.vue +2 -4
- package/src/components/upload/index.ts +21 -25
- package/src/composables/eventUtils.test.ts +267 -0
- package/src/composables/eventUtils.ts +5 -4
- package/src/composables/index.ts +1 -1
- package/src/composables/useDragDrop.test.ts +304 -0
- package/src/composables/useFlow.ts +6 -2
- package/src/composables/useFlowManagerContext.ts +5 -1
- package/src/composables/useUploadEvents.ts +1 -4
- package/src/composables/useUploadistaClient.test.ts +152 -0
- package/src/index.ts +43 -45
- package/src/providers/FlowManagerProvider.vue +5 -2
- package/src/utils/index.test.ts +396 -0
- package/src/utils/is-browser-file.test.ts +45 -0
- package/vitest.config.ts +25 -0
- package/dist/components-DoBB6sqm.mjs +0 -2
- package/dist/components-DoBB6sqm.mjs.map +0 -1
- package/dist/composables-BZ2c_WgI.mjs +0 -2
- package/dist/composables-BZ2c_WgI.mjs.map +0 -1
- package/dist/index-BLNNvTVx.d.mts +0 -62
- package/dist/index-BLNNvTVx.d.mts.map +0 -1
- package/dist/index-BSlqFF1H.d.mts.map +0 -1
- package/dist/index-CDJUpsAf.d.mts.map +0 -1
- package/dist/index-CLOy812P.d.mts.map +0 -1
- 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
|
-
//
|
|
52
|
-
|
|
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 {
|
|
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 {
|
|
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
|
+
});
|