@shipstatic/drop 0.2.0 → 0.2.1
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/index.d.cts +5 -167
- package/dist/index.d.ts +5 -167
- package/dist/testing.cjs +149 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +100 -0
- package/dist/testing.d.ts +100 -0
- package/dist/testing.js +140 -0
- package/dist/testing.js.map +1 -0
- package/dist/useDrop-CRUVkVzW.d.cts +168 -0
- package/dist/useDrop-CRUVkVzW.d.ts +168 -0
- package/package.json +11 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,169 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Core types for @shipstatic/drop
|
|
6
|
-
* Imports types from @shipstatic/types (single source of truth)
|
|
7
|
-
* and defines drop-specific types
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Extended File interface with webkitRelativePath
|
|
12
|
-
* This property is set by browsers for folder uploads and drag-drop
|
|
13
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/File/webkitRelativePath
|
|
14
|
-
*/
|
|
15
|
-
interface FileWithPath extends File {
|
|
16
|
-
readonly webkitRelativePath: string;
|
|
17
|
-
}
|
|
18
|
-
declare const FILE_STATUSES: {
|
|
19
|
-
readonly PROCESSING: "processing";
|
|
20
|
-
readonly UPLOADING: "uploading";
|
|
21
|
-
readonly COMPLETE: "complete";
|
|
22
|
-
readonly ERROR: "error";
|
|
23
|
-
readonly PENDING: "pending";
|
|
24
|
-
readonly PROCESSING_ERROR: "processing_error";
|
|
25
|
-
readonly EMPTY_FILE: "empty_file";
|
|
26
|
-
readonly VALIDATION_FAILED: "validation_failed";
|
|
27
|
-
readonly READY: "ready";
|
|
28
|
-
};
|
|
29
|
-
type FileStatus = (typeof FILE_STATUSES)[keyof typeof FILE_STATUSES];
|
|
30
|
-
/**
|
|
31
|
-
* Client-side error structure
|
|
32
|
-
* Matches ValidationError from @shipstatic/ship for consistency
|
|
33
|
-
*/
|
|
34
|
-
type ClientError = ValidationError;
|
|
35
|
-
/**
|
|
36
|
-
* Processed file entry ready for upload
|
|
37
|
-
* Contains both the File object and UI-specific metadata
|
|
38
|
-
* Use `file` property to access the underlying File for SDK operations
|
|
39
|
-
*/
|
|
40
|
-
interface ProcessedFile {
|
|
41
|
-
/** Unique identifier for React keys and tracking */
|
|
42
|
-
id: string;
|
|
43
|
-
/** The File object - pass this to ship.deployments.create() */
|
|
44
|
-
file: File;
|
|
45
|
-
/** Relative path for deployment (e.g., "images/photo.jpg") */
|
|
46
|
-
path: string;
|
|
47
|
-
/** File size in bytes */
|
|
48
|
-
size: number;
|
|
49
|
-
/** MD5 hash (optional - Ship SDK calculates during deployment if not provided) */
|
|
50
|
-
md5?: string;
|
|
51
|
-
/** Filename without path */
|
|
52
|
-
name: string;
|
|
53
|
-
/** MIME type for UI icons/previews */
|
|
54
|
-
type: string;
|
|
55
|
-
/** Last modified timestamp */
|
|
56
|
-
lastModified: number;
|
|
57
|
-
/** Current processing/upload status */
|
|
58
|
-
status: FileStatus;
|
|
59
|
-
/** Human-readable status message for UI */
|
|
60
|
-
statusMessage?: string;
|
|
61
|
-
/** Upload progress (0-100) - only set during upload */
|
|
62
|
-
progress?: number;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* State machine values for the drop hook
|
|
66
|
-
*/
|
|
67
|
-
type DropStateValue = 'idle' | 'dragging' | 'processing' | 'ready' | 'error';
|
|
68
|
-
/**
|
|
69
|
-
* Status information with title and details
|
|
70
|
-
*/
|
|
71
|
-
interface DropStatus {
|
|
72
|
-
title: string;
|
|
73
|
-
details: string;
|
|
74
|
-
errors?: string[];
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* State machine state for the drop hook
|
|
78
|
-
*/
|
|
79
|
-
interface DropState {
|
|
80
|
-
value: DropStateValue;
|
|
81
|
-
files: ProcessedFile[];
|
|
82
|
-
sourceName: string;
|
|
83
|
-
status: DropStatus | null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
interface DropOptions {
|
|
87
|
-
/** Ship SDK instance (required for validation) */
|
|
88
|
-
ship: Ship;
|
|
89
|
-
/** Callback when files are processed and ready */
|
|
90
|
-
onFilesReady?: (files: ProcessedFile[]) => void;
|
|
91
|
-
/** Callback when validation fails */
|
|
92
|
-
onValidationError?: (error: ClientError) => void;
|
|
93
|
-
/** Whether to strip common directory prefix from paths (default: true) */
|
|
94
|
-
stripPrefix?: boolean;
|
|
95
|
-
}
|
|
96
|
-
/** Options for getDropzoneProps() */
|
|
97
|
-
interface DropzonePropsOptions {
|
|
98
|
-
/** Whether clicking the dropzone opens the file picker (default: true) */
|
|
99
|
-
clickable?: boolean;
|
|
100
|
-
}
|
|
101
|
-
interface DropReturn {
|
|
102
|
-
/** Current phase of the state machine */
|
|
103
|
-
phase: DropStateValue;
|
|
104
|
-
/** Whether currently processing files (ZIP extraction, etc.) */
|
|
105
|
-
isProcessing: boolean;
|
|
106
|
-
/** Whether user is currently dragging over the dropzone */
|
|
107
|
-
isDragging: boolean;
|
|
108
|
-
/** Whether the dropzone is interactive (idle, dragging, or ready - not processing or error) */
|
|
109
|
-
isInteractive: boolean;
|
|
110
|
-
/** Whether an error occurred during processing */
|
|
111
|
-
hasError: boolean;
|
|
112
|
-
/** Flattened access to files */
|
|
113
|
-
files: ProcessedFile[];
|
|
114
|
-
/** Flattened access to source name */
|
|
115
|
-
sourceName: string;
|
|
116
|
-
/** Flattened access to status */
|
|
117
|
-
status: {
|
|
118
|
-
title: string;
|
|
119
|
-
details: string;
|
|
120
|
-
errors?: string[];
|
|
121
|
-
} | null;
|
|
122
|
-
/** Get props to spread on dropzone element (handles drag & drop, optionally click) */
|
|
123
|
-
getDropzoneProps: (options?: DropzonePropsOptions) => {
|
|
124
|
-
onDragOver: (e: React.DragEvent) => void;
|
|
125
|
-
onDragLeave: (e: React.DragEvent) => void;
|
|
126
|
-
onDrop: (e: React.DragEvent) => void;
|
|
127
|
-
onClick?: () => void;
|
|
128
|
-
};
|
|
129
|
-
/** Get props to spread on hidden file input element */
|
|
130
|
-
getInputProps: () => {
|
|
131
|
-
ref: React.RefObject<HTMLInputElement | null>;
|
|
132
|
-
type: 'file';
|
|
133
|
-
style: {
|
|
134
|
-
display: string;
|
|
135
|
-
};
|
|
136
|
-
multiple: boolean;
|
|
137
|
-
webkitdirectory: string;
|
|
138
|
-
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
139
|
-
};
|
|
140
|
-
/** Programmatically trigger file picker */
|
|
141
|
-
open: () => void;
|
|
142
|
-
/** Manually process files (for advanced usage) */
|
|
143
|
-
processFiles: (files: File[]) => Promise<void>;
|
|
144
|
-
/** Reset state and clear all files */
|
|
145
|
-
reset: () => void;
|
|
146
|
-
/** Get only valid files ready for upload */
|
|
147
|
-
validFiles: ProcessedFile[];
|
|
148
|
-
/** Get raw File objects ready for Ship SDK upload */
|
|
149
|
-
getFilesForUpload: () => File[];
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Headless drop hook for file upload workflows
|
|
153
|
-
*
|
|
154
|
-
* @example
|
|
155
|
-
* ```tsx
|
|
156
|
-
* const drop = useDrop({ ship });
|
|
157
|
-
*
|
|
158
|
-
* return (
|
|
159
|
-
* <div {...drop.getDropzoneProps()} style={{...}}>
|
|
160
|
-
* <input {...drop.getInputProps()} />
|
|
161
|
-
* {drop.isDragging ? "📂 Drop" : "📁 Click"}
|
|
162
|
-
* </div>
|
|
163
|
-
* );
|
|
164
|
-
* ```
|
|
165
|
-
*/
|
|
166
|
-
declare function useDrop(options: DropOptions): DropReturn;
|
|
1
|
+
import { P as ProcessedFile } from './useDrop-CRUVkVzW.cjs';
|
|
2
|
+
export { C as ClientError, D as DropOptions, a as DropReturn, b as DropState, c as DropStateValue, d as DropStatus, e as DropzonePropsOptions, F as FILE_STATUSES, f as FileStatus, g as FileWithPath, u as useDrop } from './useDrop-CRUVkVzW.cjs';
|
|
3
|
+
import { formatFileSize as formatFileSize$1 } from '@shipstatic/ship';
|
|
4
|
+
import '@shipstatic/types';
|
|
167
5
|
|
|
168
6
|
/**
|
|
169
7
|
* Unified file processing utilities
|
|
@@ -232,4 +70,4 @@ declare function isZipFile(file: File): boolean;
|
|
|
232
70
|
*/
|
|
233
71
|
declare function getMimeType(path: string): string;
|
|
234
72
|
|
|
235
|
-
export {
|
|
73
|
+
export { ProcessedFile, type ZipExtractionResult, createProcessedFile, extractZipToFiles, formatFileSize, getMimeType, isZipFile, normalizePath, stripCommonPrefix, traverseFileTree };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,169 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Core types for @shipstatic/drop
|
|
6
|
-
* Imports types from @shipstatic/types (single source of truth)
|
|
7
|
-
* and defines drop-specific types
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Extended File interface with webkitRelativePath
|
|
12
|
-
* This property is set by browsers for folder uploads and drag-drop
|
|
13
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/File/webkitRelativePath
|
|
14
|
-
*/
|
|
15
|
-
interface FileWithPath extends File {
|
|
16
|
-
readonly webkitRelativePath: string;
|
|
17
|
-
}
|
|
18
|
-
declare const FILE_STATUSES: {
|
|
19
|
-
readonly PROCESSING: "processing";
|
|
20
|
-
readonly UPLOADING: "uploading";
|
|
21
|
-
readonly COMPLETE: "complete";
|
|
22
|
-
readonly ERROR: "error";
|
|
23
|
-
readonly PENDING: "pending";
|
|
24
|
-
readonly PROCESSING_ERROR: "processing_error";
|
|
25
|
-
readonly EMPTY_FILE: "empty_file";
|
|
26
|
-
readonly VALIDATION_FAILED: "validation_failed";
|
|
27
|
-
readonly READY: "ready";
|
|
28
|
-
};
|
|
29
|
-
type FileStatus = (typeof FILE_STATUSES)[keyof typeof FILE_STATUSES];
|
|
30
|
-
/**
|
|
31
|
-
* Client-side error structure
|
|
32
|
-
* Matches ValidationError from @shipstatic/ship for consistency
|
|
33
|
-
*/
|
|
34
|
-
type ClientError = ValidationError;
|
|
35
|
-
/**
|
|
36
|
-
* Processed file entry ready for upload
|
|
37
|
-
* Contains both the File object and UI-specific metadata
|
|
38
|
-
* Use `file` property to access the underlying File for SDK operations
|
|
39
|
-
*/
|
|
40
|
-
interface ProcessedFile {
|
|
41
|
-
/** Unique identifier for React keys and tracking */
|
|
42
|
-
id: string;
|
|
43
|
-
/** The File object - pass this to ship.deployments.create() */
|
|
44
|
-
file: File;
|
|
45
|
-
/** Relative path for deployment (e.g., "images/photo.jpg") */
|
|
46
|
-
path: string;
|
|
47
|
-
/** File size in bytes */
|
|
48
|
-
size: number;
|
|
49
|
-
/** MD5 hash (optional - Ship SDK calculates during deployment if not provided) */
|
|
50
|
-
md5?: string;
|
|
51
|
-
/** Filename without path */
|
|
52
|
-
name: string;
|
|
53
|
-
/** MIME type for UI icons/previews */
|
|
54
|
-
type: string;
|
|
55
|
-
/** Last modified timestamp */
|
|
56
|
-
lastModified: number;
|
|
57
|
-
/** Current processing/upload status */
|
|
58
|
-
status: FileStatus;
|
|
59
|
-
/** Human-readable status message for UI */
|
|
60
|
-
statusMessage?: string;
|
|
61
|
-
/** Upload progress (0-100) - only set during upload */
|
|
62
|
-
progress?: number;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* State machine values for the drop hook
|
|
66
|
-
*/
|
|
67
|
-
type DropStateValue = 'idle' | 'dragging' | 'processing' | 'ready' | 'error';
|
|
68
|
-
/**
|
|
69
|
-
* Status information with title and details
|
|
70
|
-
*/
|
|
71
|
-
interface DropStatus {
|
|
72
|
-
title: string;
|
|
73
|
-
details: string;
|
|
74
|
-
errors?: string[];
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* State machine state for the drop hook
|
|
78
|
-
*/
|
|
79
|
-
interface DropState {
|
|
80
|
-
value: DropStateValue;
|
|
81
|
-
files: ProcessedFile[];
|
|
82
|
-
sourceName: string;
|
|
83
|
-
status: DropStatus | null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
interface DropOptions {
|
|
87
|
-
/** Ship SDK instance (required for validation) */
|
|
88
|
-
ship: Ship;
|
|
89
|
-
/** Callback when files are processed and ready */
|
|
90
|
-
onFilesReady?: (files: ProcessedFile[]) => void;
|
|
91
|
-
/** Callback when validation fails */
|
|
92
|
-
onValidationError?: (error: ClientError) => void;
|
|
93
|
-
/** Whether to strip common directory prefix from paths (default: true) */
|
|
94
|
-
stripPrefix?: boolean;
|
|
95
|
-
}
|
|
96
|
-
/** Options for getDropzoneProps() */
|
|
97
|
-
interface DropzonePropsOptions {
|
|
98
|
-
/** Whether clicking the dropzone opens the file picker (default: true) */
|
|
99
|
-
clickable?: boolean;
|
|
100
|
-
}
|
|
101
|
-
interface DropReturn {
|
|
102
|
-
/** Current phase of the state machine */
|
|
103
|
-
phase: DropStateValue;
|
|
104
|
-
/** Whether currently processing files (ZIP extraction, etc.) */
|
|
105
|
-
isProcessing: boolean;
|
|
106
|
-
/** Whether user is currently dragging over the dropzone */
|
|
107
|
-
isDragging: boolean;
|
|
108
|
-
/** Whether the dropzone is interactive (idle, dragging, or ready - not processing or error) */
|
|
109
|
-
isInteractive: boolean;
|
|
110
|
-
/** Whether an error occurred during processing */
|
|
111
|
-
hasError: boolean;
|
|
112
|
-
/** Flattened access to files */
|
|
113
|
-
files: ProcessedFile[];
|
|
114
|
-
/** Flattened access to source name */
|
|
115
|
-
sourceName: string;
|
|
116
|
-
/** Flattened access to status */
|
|
117
|
-
status: {
|
|
118
|
-
title: string;
|
|
119
|
-
details: string;
|
|
120
|
-
errors?: string[];
|
|
121
|
-
} | null;
|
|
122
|
-
/** Get props to spread on dropzone element (handles drag & drop, optionally click) */
|
|
123
|
-
getDropzoneProps: (options?: DropzonePropsOptions) => {
|
|
124
|
-
onDragOver: (e: React.DragEvent) => void;
|
|
125
|
-
onDragLeave: (e: React.DragEvent) => void;
|
|
126
|
-
onDrop: (e: React.DragEvent) => void;
|
|
127
|
-
onClick?: () => void;
|
|
128
|
-
};
|
|
129
|
-
/** Get props to spread on hidden file input element */
|
|
130
|
-
getInputProps: () => {
|
|
131
|
-
ref: React.RefObject<HTMLInputElement | null>;
|
|
132
|
-
type: 'file';
|
|
133
|
-
style: {
|
|
134
|
-
display: string;
|
|
135
|
-
};
|
|
136
|
-
multiple: boolean;
|
|
137
|
-
webkitdirectory: string;
|
|
138
|
-
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
139
|
-
};
|
|
140
|
-
/** Programmatically trigger file picker */
|
|
141
|
-
open: () => void;
|
|
142
|
-
/** Manually process files (for advanced usage) */
|
|
143
|
-
processFiles: (files: File[]) => Promise<void>;
|
|
144
|
-
/** Reset state and clear all files */
|
|
145
|
-
reset: () => void;
|
|
146
|
-
/** Get only valid files ready for upload */
|
|
147
|
-
validFiles: ProcessedFile[];
|
|
148
|
-
/** Get raw File objects ready for Ship SDK upload */
|
|
149
|
-
getFilesForUpload: () => File[];
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Headless drop hook for file upload workflows
|
|
153
|
-
*
|
|
154
|
-
* @example
|
|
155
|
-
* ```tsx
|
|
156
|
-
* const drop = useDrop({ ship });
|
|
157
|
-
*
|
|
158
|
-
* return (
|
|
159
|
-
* <div {...drop.getDropzoneProps()} style={{...}}>
|
|
160
|
-
* <input {...drop.getInputProps()} />
|
|
161
|
-
* {drop.isDragging ? "📂 Drop" : "📁 Click"}
|
|
162
|
-
* </div>
|
|
163
|
-
* );
|
|
164
|
-
* ```
|
|
165
|
-
*/
|
|
166
|
-
declare function useDrop(options: DropOptions): DropReturn;
|
|
1
|
+
import { P as ProcessedFile } from './useDrop-CRUVkVzW.js';
|
|
2
|
+
export { C as ClientError, D as DropOptions, a as DropReturn, b as DropState, c as DropStateValue, d as DropStatus, e as DropzonePropsOptions, F as FILE_STATUSES, f as FileStatus, g as FileWithPath, u as useDrop } from './useDrop-CRUVkVzW.js';
|
|
3
|
+
import { formatFileSize as formatFileSize$1 } from '@shipstatic/ship';
|
|
4
|
+
import '@shipstatic/types';
|
|
167
5
|
|
|
168
6
|
/**
|
|
169
7
|
* Unified file processing utilities
|
|
@@ -232,4 +70,4 @@ declare function isZipFile(file: File): boolean;
|
|
|
232
70
|
*/
|
|
233
71
|
declare function getMimeType(path: string): string;
|
|
234
72
|
|
|
235
|
-
export {
|
|
73
|
+
export { ProcessedFile, type ZipExtractionResult, createProcessedFile, extractZipToFiles, formatFileSize, getMimeType, isZipFile, normalizePath, stripCommonPrefix, traverseFileTree };
|
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/testing.ts
|
|
4
|
+
function createMockDrop(options = {}) {
|
|
5
|
+
const {
|
|
6
|
+
phase = "idle",
|
|
7
|
+
files = [],
|
|
8
|
+
sourceName = "",
|
|
9
|
+
status = null
|
|
10
|
+
} = options;
|
|
11
|
+
const validFiles = files.filter((f) => f.status === "ready");
|
|
12
|
+
return {
|
|
13
|
+
// State
|
|
14
|
+
phase,
|
|
15
|
+
isProcessing: phase === "processing",
|
|
16
|
+
isDragging: phase === "dragging",
|
|
17
|
+
isInteractive: phase === "idle" || phase === "dragging" || phase === "ready",
|
|
18
|
+
hasError: phase === "error",
|
|
19
|
+
files,
|
|
20
|
+
validFiles,
|
|
21
|
+
sourceName,
|
|
22
|
+
status,
|
|
23
|
+
// Prop getters - return minimal objects for spreading
|
|
24
|
+
getDropzoneProps: (opts) => ({
|
|
25
|
+
onDragOver: () => {
|
|
26
|
+
},
|
|
27
|
+
onDragLeave: () => {
|
|
28
|
+
},
|
|
29
|
+
onDrop: () => {
|
|
30
|
+
},
|
|
31
|
+
...opts?.clickable !== false && { onClick: () => {
|
|
32
|
+
} }
|
|
33
|
+
}),
|
|
34
|
+
getInputProps: () => ({
|
|
35
|
+
ref: { current: null },
|
|
36
|
+
type: "file",
|
|
37
|
+
style: { display: "none" },
|
|
38
|
+
multiple: true,
|
|
39
|
+
webkitdirectory: "",
|
|
40
|
+
onChange: () => {
|
|
41
|
+
}
|
|
42
|
+
}),
|
|
43
|
+
// Actions - no-op by default, can be spied on
|
|
44
|
+
open: () => {
|
|
45
|
+
},
|
|
46
|
+
processFiles: async () => {
|
|
47
|
+
},
|
|
48
|
+
reset: () => {
|
|
49
|
+
},
|
|
50
|
+
// Helpers
|
|
51
|
+
getFilesForUpload: () => validFiles.map((f) => f.file)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function createMockDropWithSpies(options = {}) {
|
|
55
|
+
const baseDrop = createMockDrop(options);
|
|
56
|
+
let openCalls = 0;
|
|
57
|
+
let processFilesCalls = [];
|
|
58
|
+
let resetCalls = 0;
|
|
59
|
+
const trackedSpies = {
|
|
60
|
+
open: Object.assign(() => {
|
|
61
|
+
openCalls++;
|
|
62
|
+
}, {
|
|
63
|
+
calls: () => openCalls,
|
|
64
|
+
toHaveBeenCalled: () => openCalls > 0
|
|
65
|
+
}),
|
|
66
|
+
processFiles: Object.assign(async (files) => {
|
|
67
|
+
processFilesCalls.push(files);
|
|
68
|
+
}, {
|
|
69
|
+
calls: () => processFilesCalls,
|
|
70
|
+
toHaveBeenCalled: () => processFilesCalls.length > 0,
|
|
71
|
+
toHaveBeenCalledWith: (files) => processFilesCalls.some((c) => c === files)
|
|
72
|
+
}),
|
|
73
|
+
reset: Object.assign(() => {
|
|
74
|
+
resetCalls++;
|
|
75
|
+
}, {
|
|
76
|
+
calls: () => resetCalls,
|
|
77
|
+
toHaveBeenCalled: () => resetCalls > 0
|
|
78
|
+
}),
|
|
79
|
+
getFilesForUpload: baseDrop.getFilesForUpload
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
drop: {
|
|
83
|
+
...baseDrop,
|
|
84
|
+
open: trackedSpies.open,
|
|
85
|
+
processFiles: trackedSpies.processFiles,
|
|
86
|
+
reset: trackedSpies.reset,
|
|
87
|
+
getFilesForUpload: trackedSpies.getFilesForUpload
|
|
88
|
+
},
|
|
89
|
+
spies: trackedSpies
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
var mockFileIdCounter = 0;
|
|
93
|
+
function createMockProcessedFile(name, options = {}) {
|
|
94
|
+
const {
|
|
95
|
+
path = name,
|
|
96
|
+
content = "test content",
|
|
97
|
+
type = "text/plain",
|
|
98
|
+
status = "ready",
|
|
99
|
+
statusMessage
|
|
100
|
+
} = options;
|
|
101
|
+
const file = new File([content], name, { type });
|
|
102
|
+
return {
|
|
103
|
+
id: `mock-file-${++mockFileIdCounter}`,
|
|
104
|
+
file,
|
|
105
|
+
path,
|
|
106
|
+
name,
|
|
107
|
+
size: file.size,
|
|
108
|
+
type,
|
|
109
|
+
lastModified: Date.now(),
|
|
110
|
+
status,
|
|
111
|
+
statusMessage
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function createMockFile(name, content = "test content", type = "text/plain") {
|
|
115
|
+
return new File([content], name, { type, lastModified: Date.now() });
|
|
116
|
+
}
|
|
117
|
+
function createMockFileWithPath(name, webkitRelativePath, content = "test content", type = "text/plain") {
|
|
118
|
+
const file = createMockFile(name, content, type);
|
|
119
|
+
Object.defineProperty(file, "webkitRelativePath", {
|
|
120
|
+
value: webkitRelativePath,
|
|
121
|
+
writable: false,
|
|
122
|
+
enumerable: true,
|
|
123
|
+
configurable: true
|
|
124
|
+
});
|
|
125
|
+
return file;
|
|
126
|
+
}
|
|
127
|
+
function createMockErrorStatus(title = "Validation Failed", details = "One or more files failed validation", errors = []) {
|
|
128
|
+
return { title, details, errors };
|
|
129
|
+
}
|
|
130
|
+
function createMockProcessingStatus(title = "Processing...", details = "Validating and preparing files.") {
|
|
131
|
+
return { title, details };
|
|
132
|
+
}
|
|
133
|
+
function createMockReadyStatus(fileCount) {
|
|
134
|
+
return {
|
|
135
|
+
title: "Ready",
|
|
136
|
+
details: `${fileCount} file(s) are ready.`
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
exports.createMockDrop = createMockDrop;
|
|
141
|
+
exports.createMockDropWithSpies = createMockDropWithSpies;
|
|
142
|
+
exports.createMockErrorStatus = createMockErrorStatus;
|
|
143
|
+
exports.createMockFile = createMockFile;
|
|
144
|
+
exports.createMockFileWithPath = createMockFileWithPath;
|
|
145
|
+
exports.createMockProcessedFile = createMockProcessedFile;
|
|
146
|
+
exports.createMockProcessingStatus = createMockProcessingStatus;
|
|
147
|
+
exports.createMockReadyStatus = createMockReadyStatus;
|
|
148
|
+
//# sourceMappingURL=testing.cjs.map
|
|
149
|
+
//# sourceMappingURL=testing.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts"],"names":[],"mappings":"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","file":"testing.cjs","sourcesContent":["/**\n * Test utilities for @shipstatic/drop\n *\n * Import from '@shipstatic/drop/testing' in your test files:\n *\n * ```typescript\n * import { createMockDrop, createMockFile } from '@shipstatic/drop/testing';\n * ```\n */\n\nimport type { ProcessedFile, DropStatus, DropStateValue } from './types';\nimport type { DropReturn, DropzonePropsOptions } from './hooks/useDrop';\n\n// ============================================================================\n// Mock Drop Hook Return\n// ============================================================================\n\n/**\n * Options for creating a mock drop return value\n */\nexport interface MockDropOptions {\n phase?: DropStateValue;\n files?: ProcessedFile[];\n sourceName?: string;\n status?: DropStatus | null;\n}\n\n/**\n * Creates a mock DropReturn for testing components that receive drop as a prop\n *\n * @example\n * ```tsx\n * import { createMockDrop, createMockProcessedFile } from '@shipstatic/drop/testing';\n *\n * it('renders file count when files are ready', () => {\n * const drop = createMockDrop({\n * phase: 'ready',\n * files: [createMockProcessedFile('index.html')],\n * });\n *\n * render(<DeployDropArea drop={drop} />);\n * expect(screen.getByText('1 files ready')).toBeInTheDocument();\n * });\n * ```\n */\nexport function createMockDrop(options: MockDropOptions = {}): DropReturn {\n const {\n phase = 'idle',\n files = [],\n sourceName = '',\n status = null,\n } = options;\n\n const validFiles = files.filter(f => f.status === 'ready');\n\n return {\n // State\n phase,\n isProcessing: phase === 'processing',\n isDragging: phase === 'dragging',\n isInteractive: phase === 'idle' || phase === 'dragging' || phase === 'ready',\n hasError: phase === 'error',\n files,\n validFiles,\n sourceName,\n status,\n\n // Prop getters - return minimal objects for spreading\n getDropzoneProps: (opts?: DropzonePropsOptions) => ({\n onDragOver: () => {},\n onDragLeave: () => {},\n onDrop: () => {},\n ...(opts?.clickable !== false && { onClick: () => {} }),\n }),\n getInputProps: () => ({\n ref: { current: null },\n type: 'file' as const,\n style: { display: 'none' },\n multiple: true,\n webkitdirectory: '',\n onChange: () => {},\n }),\n\n // Actions - no-op by default, can be spied on\n open: () => {},\n processFiles: async () => {},\n reset: () => {},\n\n // Helpers\n getFilesForUpload: () => validFiles.map(f => f.file),\n };\n}\n\n/**\n * Creates a mock drop with spy functions for testing interactions\n *\n * @example\n * ```tsx\n * import { createMockDropWithSpies } from '@shipstatic/drop/testing';\n *\n * it('calls reset when Clear button is clicked', async () => {\n * const { drop, spies } = createMockDropWithSpies({ phase: 'ready', files: [...] });\n *\n * render(<DeployDropArea drop={drop} />);\n * await userEvent.click(screen.getByText('Clear'));\n *\n * expect(spies.reset).toHaveBeenCalled();\n * });\n * ```\n */\nexport function createMockDropWithSpies(options: MockDropOptions = {}): {\n drop: DropReturn;\n spies: {\n open: () => void;\n processFiles: (files: File[]) => Promise<void>;\n reset: () => void;\n getFilesForUpload: () => File[];\n };\n} {\n const baseDrop = createMockDrop(options);\n\n const spies = {\n open: createNoopSpy(),\n processFiles: createAsyncNoopSpy(),\n reset: createNoopSpy(),\n getFilesForUpload: (() => baseDrop.getFilesForUpload()) as () => File[],\n };\n\n // Track calls manually (works without vitest in runtime)\n let openCalls = 0;\n let processFilesCalls: File[][] = [];\n let resetCalls = 0;\n\n const trackedSpies = {\n open: Object.assign(() => { openCalls++; }, {\n calls: () => openCalls,\n toHaveBeenCalled: () => openCalls > 0,\n }),\n processFiles: Object.assign(async (files: File[]) => { processFilesCalls.push(files); }, {\n calls: () => processFilesCalls,\n toHaveBeenCalled: () => processFilesCalls.length > 0,\n toHaveBeenCalledWith: (files: File[]) => processFilesCalls.some(c => c === files),\n }),\n reset: Object.assign(() => { resetCalls++; }, {\n calls: () => resetCalls,\n toHaveBeenCalled: () => resetCalls > 0,\n }),\n getFilesForUpload: baseDrop.getFilesForUpload,\n };\n\n return {\n drop: {\n ...baseDrop,\n open: trackedSpies.open,\n processFiles: trackedSpies.processFiles,\n reset: trackedSpies.reset,\n getFilesForUpload: trackedSpies.getFilesForUpload,\n },\n spies: trackedSpies,\n };\n}\n\n// ============================================================================\n// Mock File Utilities\n// ============================================================================\n\nlet mockFileIdCounter = 0;\n\n/**\n * Creates a mock ProcessedFile for testing\n */\nexport function createMockProcessedFile(\n name: string,\n options: {\n path?: string;\n content?: string;\n type?: string;\n status?: 'ready' | 'validation_failed' | 'processing_error' | 'empty_file';\n statusMessage?: string;\n } = {}\n): ProcessedFile {\n const {\n path = name,\n content = 'test content',\n type = 'text/plain',\n status = 'ready',\n statusMessage,\n } = options;\n\n const file = new File([content], name, { type });\n\n return {\n id: `mock-file-${++mockFileIdCounter}`,\n file,\n path,\n name,\n size: file.size,\n type,\n lastModified: Date.now(),\n status,\n statusMessage,\n };\n}\n\n/**\n * Creates a mock File object\n */\nexport function createMockFile(\n name: string,\n content: string = 'test content',\n type: string = 'text/plain'\n): File {\n return new File([content], name, { type, lastModified: Date.now() });\n}\n\n/**\n * Creates a mock File object with webkitRelativePath set\n */\nexport function createMockFileWithPath(\n name: string,\n webkitRelativePath: string,\n content: string = 'test content',\n type: string = 'text/plain'\n): File {\n const file = createMockFile(name, content, type);\n Object.defineProperty(file, 'webkitRelativePath', {\n value: webkitRelativePath,\n writable: false,\n enumerable: true,\n configurable: true,\n });\n return file;\n}\n\n// ============================================================================\n// Mock Status Utilities\n// ============================================================================\n\n/**\n * Creates a mock error status\n */\nexport function createMockErrorStatus(\n title: string = 'Validation Failed',\n details: string = 'One or more files failed validation',\n errors: string[] = []\n): DropStatus {\n return { title, details, errors };\n}\n\n/**\n * Creates a mock processing status\n */\nexport function createMockProcessingStatus(\n title: string = 'Processing...',\n details: string = 'Validating and preparing files.'\n): DropStatus {\n return { title, details };\n}\n\n/**\n * Creates a mock ready status\n */\nexport function createMockReadyStatus(fileCount: number): DropStatus {\n return {\n title: 'Ready',\n details: `${fileCount} file(s) are ready.`,\n };\n}\n\n// ============================================================================\n// Internal Helpers\n// ============================================================================\n\nfunction createNoopSpy(): () => void {\n return () => {};\n}\n\nfunction createAsyncNoopSpy(): () => Promise<void> {\n return async () => {};\n}\n"]}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { c as DropStateValue, P as ProcessedFile, d as DropStatus, a as DropReturn } from './useDrop-CRUVkVzW.cjs';
|
|
2
|
+
import '@shipstatic/types';
|
|
3
|
+
import '@shipstatic/ship';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Test utilities for @shipstatic/drop
|
|
7
|
+
*
|
|
8
|
+
* Import from '@shipstatic/drop/testing' in your test files:
|
|
9
|
+
*
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { createMockDrop, createMockFile } from '@shipstatic/drop/testing';
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Options for creating a mock drop return value
|
|
17
|
+
*/
|
|
18
|
+
interface MockDropOptions {
|
|
19
|
+
phase?: DropStateValue;
|
|
20
|
+
files?: ProcessedFile[];
|
|
21
|
+
sourceName?: string;
|
|
22
|
+
status?: DropStatus | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Creates a mock DropReturn for testing components that receive drop as a prop
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* import { createMockDrop, createMockProcessedFile } from '@shipstatic/drop/testing';
|
|
30
|
+
*
|
|
31
|
+
* it('renders file count when files are ready', () => {
|
|
32
|
+
* const drop = createMockDrop({
|
|
33
|
+
* phase: 'ready',
|
|
34
|
+
* files: [createMockProcessedFile('index.html')],
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* render(<DeployDropArea drop={drop} />);
|
|
38
|
+
* expect(screen.getByText('1 files ready')).toBeInTheDocument();
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
declare function createMockDrop(options?: MockDropOptions): DropReturn;
|
|
43
|
+
/**
|
|
44
|
+
* Creates a mock drop with spy functions for testing interactions
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* import { createMockDropWithSpies } from '@shipstatic/drop/testing';
|
|
49
|
+
*
|
|
50
|
+
* it('calls reset when Clear button is clicked', async () => {
|
|
51
|
+
* const { drop, spies } = createMockDropWithSpies({ phase: 'ready', files: [...] });
|
|
52
|
+
*
|
|
53
|
+
* render(<DeployDropArea drop={drop} />);
|
|
54
|
+
* await userEvent.click(screen.getByText('Clear'));
|
|
55
|
+
*
|
|
56
|
+
* expect(spies.reset).toHaveBeenCalled();
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
declare function createMockDropWithSpies(options?: MockDropOptions): {
|
|
61
|
+
drop: DropReturn;
|
|
62
|
+
spies: {
|
|
63
|
+
open: () => void;
|
|
64
|
+
processFiles: (files: File[]) => Promise<void>;
|
|
65
|
+
reset: () => void;
|
|
66
|
+
getFilesForUpload: () => File[];
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Creates a mock ProcessedFile for testing
|
|
71
|
+
*/
|
|
72
|
+
declare function createMockProcessedFile(name: string, options?: {
|
|
73
|
+
path?: string;
|
|
74
|
+
content?: string;
|
|
75
|
+
type?: string;
|
|
76
|
+
status?: 'ready' | 'validation_failed' | 'processing_error' | 'empty_file';
|
|
77
|
+
statusMessage?: string;
|
|
78
|
+
}): ProcessedFile;
|
|
79
|
+
/**
|
|
80
|
+
* Creates a mock File object
|
|
81
|
+
*/
|
|
82
|
+
declare function createMockFile(name: string, content?: string, type?: string): File;
|
|
83
|
+
/**
|
|
84
|
+
* Creates a mock File object with webkitRelativePath set
|
|
85
|
+
*/
|
|
86
|
+
declare function createMockFileWithPath(name: string, webkitRelativePath: string, content?: string, type?: string): File;
|
|
87
|
+
/**
|
|
88
|
+
* Creates a mock error status
|
|
89
|
+
*/
|
|
90
|
+
declare function createMockErrorStatus(title?: string, details?: string, errors?: string[]): DropStatus;
|
|
91
|
+
/**
|
|
92
|
+
* Creates a mock processing status
|
|
93
|
+
*/
|
|
94
|
+
declare function createMockProcessingStatus(title?: string, details?: string): DropStatus;
|
|
95
|
+
/**
|
|
96
|
+
* Creates a mock ready status
|
|
97
|
+
*/
|
|
98
|
+
declare function createMockReadyStatus(fileCount: number): DropStatus;
|
|
99
|
+
|
|
100
|
+
export { type MockDropOptions, createMockDrop, createMockDropWithSpies, createMockErrorStatus, createMockFile, createMockFileWithPath, createMockProcessedFile, createMockProcessingStatus, createMockReadyStatus };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { c as DropStateValue, P as ProcessedFile, d as DropStatus, a as DropReturn } from './useDrop-CRUVkVzW.js';
|
|
2
|
+
import '@shipstatic/types';
|
|
3
|
+
import '@shipstatic/ship';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Test utilities for @shipstatic/drop
|
|
7
|
+
*
|
|
8
|
+
* Import from '@shipstatic/drop/testing' in your test files:
|
|
9
|
+
*
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { createMockDrop, createMockFile } from '@shipstatic/drop/testing';
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Options for creating a mock drop return value
|
|
17
|
+
*/
|
|
18
|
+
interface MockDropOptions {
|
|
19
|
+
phase?: DropStateValue;
|
|
20
|
+
files?: ProcessedFile[];
|
|
21
|
+
sourceName?: string;
|
|
22
|
+
status?: DropStatus | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Creates a mock DropReturn for testing components that receive drop as a prop
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* import { createMockDrop, createMockProcessedFile } from '@shipstatic/drop/testing';
|
|
30
|
+
*
|
|
31
|
+
* it('renders file count when files are ready', () => {
|
|
32
|
+
* const drop = createMockDrop({
|
|
33
|
+
* phase: 'ready',
|
|
34
|
+
* files: [createMockProcessedFile('index.html')],
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* render(<DeployDropArea drop={drop} />);
|
|
38
|
+
* expect(screen.getByText('1 files ready')).toBeInTheDocument();
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
declare function createMockDrop(options?: MockDropOptions): DropReturn;
|
|
43
|
+
/**
|
|
44
|
+
* Creates a mock drop with spy functions for testing interactions
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```tsx
|
|
48
|
+
* import { createMockDropWithSpies } from '@shipstatic/drop/testing';
|
|
49
|
+
*
|
|
50
|
+
* it('calls reset when Clear button is clicked', async () => {
|
|
51
|
+
* const { drop, spies } = createMockDropWithSpies({ phase: 'ready', files: [...] });
|
|
52
|
+
*
|
|
53
|
+
* render(<DeployDropArea drop={drop} />);
|
|
54
|
+
* await userEvent.click(screen.getByText('Clear'));
|
|
55
|
+
*
|
|
56
|
+
* expect(spies.reset).toHaveBeenCalled();
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
declare function createMockDropWithSpies(options?: MockDropOptions): {
|
|
61
|
+
drop: DropReturn;
|
|
62
|
+
spies: {
|
|
63
|
+
open: () => void;
|
|
64
|
+
processFiles: (files: File[]) => Promise<void>;
|
|
65
|
+
reset: () => void;
|
|
66
|
+
getFilesForUpload: () => File[];
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Creates a mock ProcessedFile for testing
|
|
71
|
+
*/
|
|
72
|
+
declare function createMockProcessedFile(name: string, options?: {
|
|
73
|
+
path?: string;
|
|
74
|
+
content?: string;
|
|
75
|
+
type?: string;
|
|
76
|
+
status?: 'ready' | 'validation_failed' | 'processing_error' | 'empty_file';
|
|
77
|
+
statusMessage?: string;
|
|
78
|
+
}): ProcessedFile;
|
|
79
|
+
/**
|
|
80
|
+
* Creates a mock File object
|
|
81
|
+
*/
|
|
82
|
+
declare function createMockFile(name: string, content?: string, type?: string): File;
|
|
83
|
+
/**
|
|
84
|
+
* Creates a mock File object with webkitRelativePath set
|
|
85
|
+
*/
|
|
86
|
+
declare function createMockFileWithPath(name: string, webkitRelativePath: string, content?: string, type?: string): File;
|
|
87
|
+
/**
|
|
88
|
+
* Creates a mock error status
|
|
89
|
+
*/
|
|
90
|
+
declare function createMockErrorStatus(title?: string, details?: string, errors?: string[]): DropStatus;
|
|
91
|
+
/**
|
|
92
|
+
* Creates a mock processing status
|
|
93
|
+
*/
|
|
94
|
+
declare function createMockProcessingStatus(title?: string, details?: string): DropStatus;
|
|
95
|
+
/**
|
|
96
|
+
* Creates a mock ready status
|
|
97
|
+
*/
|
|
98
|
+
declare function createMockReadyStatus(fileCount: number): DropStatus;
|
|
99
|
+
|
|
100
|
+
export { type MockDropOptions, createMockDrop, createMockDropWithSpies, createMockErrorStatus, createMockFile, createMockFileWithPath, createMockProcessedFile, createMockProcessingStatus, createMockReadyStatus };
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// src/testing.ts
|
|
2
|
+
function createMockDrop(options = {}) {
|
|
3
|
+
const {
|
|
4
|
+
phase = "idle",
|
|
5
|
+
files = [],
|
|
6
|
+
sourceName = "",
|
|
7
|
+
status = null
|
|
8
|
+
} = options;
|
|
9
|
+
const validFiles = files.filter((f) => f.status === "ready");
|
|
10
|
+
return {
|
|
11
|
+
// State
|
|
12
|
+
phase,
|
|
13
|
+
isProcessing: phase === "processing",
|
|
14
|
+
isDragging: phase === "dragging",
|
|
15
|
+
isInteractive: phase === "idle" || phase === "dragging" || phase === "ready",
|
|
16
|
+
hasError: phase === "error",
|
|
17
|
+
files,
|
|
18
|
+
validFiles,
|
|
19
|
+
sourceName,
|
|
20
|
+
status,
|
|
21
|
+
// Prop getters - return minimal objects for spreading
|
|
22
|
+
getDropzoneProps: (opts) => ({
|
|
23
|
+
onDragOver: () => {
|
|
24
|
+
},
|
|
25
|
+
onDragLeave: () => {
|
|
26
|
+
},
|
|
27
|
+
onDrop: () => {
|
|
28
|
+
},
|
|
29
|
+
...opts?.clickable !== false && { onClick: () => {
|
|
30
|
+
} }
|
|
31
|
+
}),
|
|
32
|
+
getInputProps: () => ({
|
|
33
|
+
ref: { current: null },
|
|
34
|
+
type: "file",
|
|
35
|
+
style: { display: "none" },
|
|
36
|
+
multiple: true,
|
|
37
|
+
webkitdirectory: "",
|
|
38
|
+
onChange: () => {
|
|
39
|
+
}
|
|
40
|
+
}),
|
|
41
|
+
// Actions - no-op by default, can be spied on
|
|
42
|
+
open: () => {
|
|
43
|
+
},
|
|
44
|
+
processFiles: async () => {
|
|
45
|
+
},
|
|
46
|
+
reset: () => {
|
|
47
|
+
},
|
|
48
|
+
// Helpers
|
|
49
|
+
getFilesForUpload: () => validFiles.map((f) => f.file)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function createMockDropWithSpies(options = {}) {
|
|
53
|
+
const baseDrop = createMockDrop(options);
|
|
54
|
+
let openCalls = 0;
|
|
55
|
+
let processFilesCalls = [];
|
|
56
|
+
let resetCalls = 0;
|
|
57
|
+
const trackedSpies = {
|
|
58
|
+
open: Object.assign(() => {
|
|
59
|
+
openCalls++;
|
|
60
|
+
}, {
|
|
61
|
+
calls: () => openCalls,
|
|
62
|
+
toHaveBeenCalled: () => openCalls > 0
|
|
63
|
+
}),
|
|
64
|
+
processFiles: Object.assign(async (files) => {
|
|
65
|
+
processFilesCalls.push(files);
|
|
66
|
+
}, {
|
|
67
|
+
calls: () => processFilesCalls,
|
|
68
|
+
toHaveBeenCalled: () => processFilesCalls.length > 0,
|
|
69
|
+
toHaveBeenCalledWith: (files) => processFilesCalls.some((c) => c === files)
|
|
70
|
+
}),
|
|
71
|
+
reset: Object.assign(() => {
|
|
72
|
+
resetCalls++;
|
|
73
|
+
}, {
|
|
74
|
+
calls: () => resetCalls,
|
|
75
|
+
toHaveBeenCalled: () => resetCalls > 0
|
|
76
|
+
}),
|
|
77
|
+
getFilesForUpload: baseDrop.getFilesForUpload
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
drop: {
|
|
81
|
+
...baseDrop,
|
|
82
|
+
open: trackedSpies.open,
|
|
83
|
+
processFiles: trackedSpies.processFiles,
|
|
84
|
+
reset: trackedSpies.reset,
|
|
85
|
+
getFilesForUpload: trackedSpies.getFilesForUpload
|
|
86
|
+
},
|
|
87
|
+
spies: trackedSpies
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
var mockFileIdCounter = 0;
|
|
91
|
+
function createMockProcessedFile(name, options = {}) {
|
|
92
|
+
const {
|
|
93
|
+
path = name,
|
|
94
|
+
content = "test content",
|
|
95
|
+
type = "text/plain",
|
|
96
|
+
status = "ready",
|
|
97
|
+
statusMessage
|
|
98
|
+
} = options;
|
|
99
|
+
const file = new File([content], name, { type });
|
|
100
|
+
return {
|
|
101
|
+
id: `mock-file-${++mockFileIdCounter}`,
|
|
102
|
+
file,
|
|
103
|
+
path,
|
|
104
|
+
name,
|
|
105
|
+
size: file.size,
|
|
106
|
+
type,
|
|
107
|
+
lastModified: Date.now(),
|
|
108
|
+
status,
|
|
109
|
+
statusMessage
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function createMockFile(name, content = "test content", type = "text/plain") {
|
|
113
|
+
return new File([content], name, { type, lastModified: Date.now() });
|
|
114
|
+
}
|
|
115
|
+
function createMockFileWithPath(name, webkitRelativePath, content = "test content", type = "text/plain") {
|
|
116
|
+
const file = createMockFile(name, content, type);
|
|
117
|
+
Object.defineProperty(file, "webkitRelativePath", {
|
|
118
|
+
value: webkitRelativePath,
|
|
119
|
+
writable: false,
|
|
120
|
+
enumerable: true,
|
|
121
|
+
configurable: true
|
|
122
|
+
});
|
|
123
|
+
return file;
|
|
124
|
+
}
|
|
125
|
+
function createMockErrorStatus(title = "Validation Failed", details = "One or more files failed validation", errors = []) {
|
|
126
|
+
return { title, details, errors };
|
|
127
|
+
}
|
|
128
|
+
function createMockProcessingStatus(title = "Processing...", details = "Validating and preparing files.") {
|
|
129
|
+
return { title, details };
|
|
130
|
+
}
|
|
131
|
+
function createMockReadyStatus(fileCount) {
|
|
132
|
+
return {
|
|
133
|
+
title: "Ready",
|
|
134
|
+
details: `${fileCount} file(s) are ready.`
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export { createMockDrop, createMockDropWithSpies, createMockErrorStatus, createMockFile, createMockFileWithPath, createMockProcessedFile, createMockProcessingStatus, createMockReadyStatus };
|
|
139
|
+
//# sourceMappingURL=testing.js.map
|
|
140
|
+
//# sourceMappingURL=testing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts"],"names":[],"mappings":"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","file":"testing.js","sourcesContent":["/**\n * Test utilities for @shipstatic/drop\n *\n * Import from '@shipstatic/drop/testing' in your test files:\n *\n * ```typescript\n * import { createMockDrop, createMockFile } from '@shipstatic/drop/testing';\n * ```\n */\n\nimport type { ProcessedFile, DropStatus, DropStateValue } from './types';\nimport type { DropReturn, DropzonePropsOptions } from './hooks/useDrop';\n\n// ============================================================================\n// Mock Drop Hook Return\n// ============================================================================\n\n/**\n * Options for creating a mock drop return value\n */\nexport interface MockDropOptions {\n phase?: DropStateValue;\n files?: ProcessedFile[];\n sourceName?: string;\n status?: DropStatus | null;\n}\n\n/**\n * Creates a mock DropReturn for testing components that receive drop as a prop\n *\n * @example\n * ```tsx\n * import { createMockDrop, createMockProcessedFile } from '@shipstatic/drop/testing';\n *\n * it('renders file count when files are ready', () => {\n * const drop = createMockDrop({\n * phase: 'ready',\n * files: [createMockProcessedFile('index.html')],\n * });\n *\n * render(<DeployDropArea drop={drop} />);\n * expect(screen.getByText('1 files ready')).toBeInTheDocument();\n * });\n * ```\n */\nexport function createMockDrop(options: MockDropOptions = {}): DropReturn {\n const {\n phase = 'idle',\n files = [],\n sourceName = '',\n status = null,\n } = options;\n\n const validFiles = files.filter(f => f.status === 'ready');\n\n return {\n // State\n phase,\n isProcessing: phase === 'processing',\n isDragging: phase === 'dragging',\n isInteractive: phase === 'idle' || phase === 'dragging' || phase === 'ready',\n hasError: phase === 'error',\n files,\n validFiles,\n sourceName,\n status,\n\n // Prop getters - return minimal objects for spreading\n getDropzoneProps: (opts?: DropzonePropsOptions) => ({\n onDragOver: () => {},\n onDragLeave: () => {},\n onDrop: () => {},\n ...(opts?.clickable !== false && { onClick: () => {} }),\n }),\n getInputProps: () => ({\n ref: { current: null },\n type: 'file' as const,\n style: { display: 'none' },\n multiple: true,\n webkitdirectory: '',\n onChange: () => {},\n }),\n\n // Actions - no-op by default, can be spied on\n open: () => {},\n processFiles: async () => {},\n reset: () => {},\n\n // Helpers\n getFilesForUpload: () => validFiles.map(f => f.file),\n };\n}\n\n/**\n * Creates a mock drop with spy functions for testing interactions\n *\n * @example\n * ```tsx\n * import { createMockDropWithSpies } from '@shipstatic/drop/testing';\n *\n * it('calls reset when Clear button is clicked', async () => {\n * const { drop, spies } = createMockDropWithSpies({ phase: 'ready', files: [...] });\n *\n * render(<DeployDropArea drop={drop} />);\n * await userEvent.click(screen.getByText('Clear'));\n *\n * expect(spies.reset).toHaveBeenCalled();\n * });\n * ```\n */\nexport function createMockDropWithSpies(options: MockDropOptions = {}): {\n drop: DropReturn;\n spies: {\n open: () => void;\n processFiles: (files: File[]) => Promise<void>;\n reset: () => void;\n getFilesForUpload: () => File[];\n };\n} {\n const baseDrop = createMockDrop(options);\n\n const spies = {\n open: createNoopSpy(),\n processFiles: createAsyncNoopSpy(),\n reset: createNoopSpy(),\n getFilesForUpload: (() => baseDrop.getFilesForUpload()) as () => File[],\n };\n\n // Track calls manually (works without vitest in runtime)\n let openCalls = 0;\n let processFilesCalls: File[][] = [];\n let resetCalls = 0;\n\n const trackedSpies = {\n open: Object.assign(() => { openCalls++; }, {\n calls: () => openCalls,\n toHaveBeenCalled: () => openCalls > 0,\n }),\n processFiles: Object.assign(async (files: File[]) => { processFilesCalls.push(files); }, {\n calls: () => processFilesCalls,\n toHaveBeenCalled: () => processFilesCalls.length > 0,\n toHaveBeenCalledWith: (files: File[]) => processFilesCalls.some(c => c === files),\n }),\n reset: Object.assign(() => { resetCalls++; }, {\n calls: () => resetCalls,\n toHaveBeenCalled: () => resetCalls > 0,\n }),\n getFilesForUpload: baseDrop.getFilesForUpload,\n };\n\n return {\n drop: {\n ...baseDrop,\n open: trackedSpies.open,\n processFiles: trackedSpies.processFiles,\n reset: trackedSpies.reset,\n getFilesForUpload: trackedSpies.getFilesForUpload,\n },\n spies: trackedSpies,\n };\n}\n\n// ============================================================================\n// Mock File Utilities\n// ============================================================================\n\nlet mockFileIdCounter = 0;\n\n/**\n * Creates a mock ProcessedFile for testing\n */\nexport function createMockProcessedFile(\n name: string,\n options: {\n path?: string;\n content?: string;\n type?: string;\n status?: 'ready' | 'validation_failed' | 'processing_error' | 'empty_file';\n statusMessage?: string;\n } = {}\n): ProcessedFile {\n const {\n path = name,\n content = 'test content',\n type = 'text/plain',\n status = 'ready',\n statusMessage,\n } = options;\n\n const file = new File([content], name, { type });\n\n return {\n id: `mock-file-${++mockFileIdCounter}`,\n file,\n path,\n name,\n size: file.size,\n type,\n lastModified: Date.now(),\n status,\n statusMessage,\n };\n}\n\n/**\n * Creates a mock File object\n */\nexport function createMockFile(\n name: string,\n content: string = 'test content',\n type: string = 'text/plain'\n): File {\n return new File([content], name, { type, lastModified: Date.now() });\n}\n\n/**\n * Creates a mock File object with webkitRelativePath set\n */\nexport function createMockFileWithPath(\n name: string,\n webkitRelativePath: string,\n content: string = 'test content',\n type: string = 'text/plain'\n): File {\n const file = createMockFile(name, content, type);\n Object.defineProperty(file, 'webkitRelativePath', {\n value: webkitRelativePath,\n writable: false,\n enumerable: true,\n configurable: true,\n });\n return file;\n}\n\n// ============================================================================\n// Mock Status Utilities\n// ============================================================================\n\n/**\n * Creates a mock error status\n */\nexport function createMockErrorStatus(\n title: string = 'Validation Failed',\n details: string = 'One or more files failed validation',\n errors: string[] = []\n): DropStatus {\n return { title, details, errors };\n}\n\n/**\n * Creates a mock processing status\n */\nexport function createMockProcessingStatus(\n title: string = 'Processing...',\n details: string = 'Validating and preparing files.'\n): DropStatus {\n return { title, details };\n}\n\n/**\n * Creates a mock ready status\n */\nexport function createMockReadyStatus(fileCount: number): DropStatus {\n return {\n title: 'Ready',\n details: `${fileCount} file(s) are ready.`,\n };\n}\n\n// ============================================================================\n// Internal Helpers\n// ============================================================================\n\nfunction createNoopSpy(): () => void {\n return () => {};\n}\n\nfunction createAsyncNoopSpy(): () => Promise<void> {\n return async () => {};\n}\n"]}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { ValidationError } from '@shipstatic/types';
|
|
2
|
+
import { Ship } from '@shipstatic/ship';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Core types for @shipstatic/drop
|
|
6
|
+
* Imports types from @shipstatic/types (single source of truth)
|
|
7
|
+
* and defines drop-specific types
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extended File interface with webkitRelativePath
|
|
12
|
+
* This property is set by browsers for folder uploads and drag-drop
|
|
13
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/File/webkitRelativePath
|
|
14
|
+
*/
|
|
15
|
+
interface FileWithPath extends File {
|
|
16
|
+
readonly webkitRelativePath: string;
|
|
17
|
+
}
|
|
18
|
+
declare const FILE_STATUSES: {
|
|
19
|
+
readonly PROCESSING: "processing";
|
|
20
|
+
readonly UPLOADING: "uploading";
|
|
21
|
+
readonly COMPLETE: "complete";
|
|
22
|
+
readonly ERROR: "error";
|
|
23
|
+
readonly PENDING: "pending";
|
|
24
|
+
readonly PROCESSING_ERROR: "processing_error";
|
|
25
|
+
readonly EMPTY_FILE: "empty_file";
|
|
26
|
+
readonly VALIDATION_FAILED: "validation_failed";
|
|
27
|
+
readonly READY: "ready";
|
|
28
|
+
};
|
|
29
|
+
type FileStatus = (typeof FILE_STATUSES)[keyof typeof FILE_STATUSES];
|
|
30
|
+
/**
|
|
31
|
+
* Client-side error structure
|
|
32
|
+
* Matches ValidationError from @shipstatic/ship for consistency
|
|
33
|
+
*/
|
|
34
|
+
type ClientError = ValidationError;
|
|
35
|
+
/**
|
|
36
|
+
* Processed file entry ready for upload
|
|
37
|
+
* Contains both the File object and UI-specific metadata
|
|
38
|
+
* Use `file` property to access the underlying File for SDK operations
|
|
39
|
+
*/
|
|
40
|
+
interface ProcessedFile {
|
|
41
|
+
/** Unique identifier for React keys and tracking */
|
|
42
|
+
id: string;
|
|
43
|
+
/** The File object - pass this to ship.deployments.create() */
|
|
44
|
+
file: File;
|
|
45
|
+
/** Relative path for deployment (e.g., "images/photo.jpg") */
|
|
46
|
+
path: string;
|
|
47
|
+
/** File size in bytes */
|
|
48
|
+
size: number;
|
|
49
|
+
/** MD5 hash (optional - Ship SDK calculates during deployment if not provided) */
|
|
50
|
+
md5?: string;
|
|
51
|
+
/** Filename without path */
|
|
52
|
+
name: string;
|
|
53
|
+
/** MIME type for UI icons/previews */
|
|
54
|
+
type: string;
|
|
55
|
+
/** Last modified timestamp */
|
|
56
|
+
lastModified: number;
|
|
57
|
+
/** Current processing/upload status */
|
|
58
|
+
status: FileStatus;
|
|
59
|
+
/** Human-readable status message for UI */
|
|
60
|
+
statusMessage?: string;
|
|
61
|
+
/** Upload progress (0-100) - only set during upload */
|
|
62
|
+
progress?: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* State machine values for the drop hook
|
|
66
|
+
*/
|
|
67
|
+
type DropStateValue = 'idle' | 'dragging' | 'processing' | 'ready' | 'error';
|
|
68
|
+
/**
|
|
69
|
+
* Status information with title and details
|
|
70
|
+
*/
|
|
71
|
+
interface DropStatus {
|
|
72
|
+
title: string;
|
|
73
|
+
details: string;
|
|
74
|
+
errors?: string[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* State machine state for the drop hook
|
|
78
|
+
*/
|
|
79
|
+
interface DropState {
|
|
80
|
+
value: DropStateValue;
|
|
81
|
+
files: ProcessedFile[];
|
|
82
|
+
sourceName: string;
|
|
83
|
+
status: DropStatus | null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface DropOptions {
|
|
87
|
+
/** Ship SDK instance (required for validation) */
|
|
88
|
+
ship: Ship;
|
|
89
|
+
/** Callback when files are processed and ready */
|
|
90
|
+
onFilesReady?: (files: ProcessedFile[]) => void;
|
|
91
|
+
/** Callback when validation fails */
|
|
92
|
+
onValidationError?: (error: ClientError) => void;
|
|
93
|
+
/** Whether to strip common directory prefix from paths (default: true) */
|
|
94
|
+
stripPrefix?: boolean;
|
|
95
|
+
}
|
|
96
|
+
/** Options for getDropzoneProps() */
|
|
97
|
+
interface DropzonePropsOptions {
|
|
98
|
+
/** Whether clicking the dropzone opens the file picker (default: true) */
|
|
99
|
+
clickable?: boolean;
|
|
100
|
+
}
|
|
101
|
+
interface DropReturn {
|
|
102
|
+
/** Current phase of the state machine */
|
|
103
|
+
phase: DropStateValue;
|
|
104
|
+
/** Whether currently processing files (ZIP extraction, etc.) */
|
|
105
|
+
isProcessing: boolean;
|
|
106
|
+
/** Whether user is currently dragging over the dropzone */
|
|
107
|
+
isDragging: boolean;
|
|
108
|
+
/** Whether the dropzone is interactive (idle, dragging, or ready - not processing or error) */
|
|
109
|
+
isInteractive: boolean;
|
|
110
|
+
/** Whether an error occurred during processing */
|
|
111
|
+
hasError: boolean;
|
|
112
|
+
/** Flattened access to files */
|
|
113
|
+
files: ProcessedFile[];
|
|
114
|
+
/** Flattened access to source name */
|
|
115
|
+
sourceName: string;
|
|
116
|
+
/** Flattened access to status */
|
|
117
|
+
status: {
|
|
118
|
+
title: string;
|
|
119
|
+
details: string;
|
|
120
|
+
errors?: string[];
|
|
121
|
+
} | null;
|
|
122
|
+
/** Get props to spread on dropzone element (handles drag & drop, optionally click) */
|
|
123
|
+
getDropzoneProps: (options?: DropzonePropsOptions) => {
|
|
124
|
+
onDragOver: (e: React.DragEvent) => void;
|
|
125
|
+
onDragLeave: (e: React.DragEvent) => void;
|
|
126
|
+
onDrop: (e: React.DragEvent) => void;
|
|
127
|
+
onClick?: () => void;
|
|
128
|
+
};
|
|
129
|
+
/** Get props to spread on hidden file input element */
|
|
130
|
+
getInputProps: () => {
|
|
131
|
+
ref: React.RefObject<HTMLInputElement | null>;
|
|
132
|
+
type: 'file';
|
|
133
|
+
style: {
|
|
134
|
+
display: string;
|
|
135
|
+
};
|
|
136
|
+
multiple: boolean;
|
|
137
|
+
webkitdirectory: string;
|
|
138
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
139
|
+
};
|
|
140
|
+
/** Programmatically trigger file picker */
|
|
141
|
+
open: () => void;
|
|
142
|
+
/** Manually process files (for advanced usage) */
|
|
143
|
+
processFiles: (files: File[]) => Promise<void>;
|
|
144
|
+
/** Reset state and clear all files */
|
|
145
|
+
reset: () => void;
|
|
146
|
+
/** Get only valid files ready for upload */
|
|
147
|
+
validFiles: ProcessedFile[];
|
|
148
|
+
/** Get raw File objects ready for Ship SDK upload */
|
|
149
|
+
getFilesForUpload: () => File[];
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Headless drop hook for file upload workflows
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```tsx
|
|
156
|
+
* const drop = useDrop({ ship });
|
|
157
|
+
*
|
|
158
|
+
* return (
|
|
159
|
+
* <div {...drop.getDropzoneProps()} style={{...}}>
|
|
160
|
+
* <input {...drop.getInputProps()} />
|
|
161
|
+
* {drop.isDragging ? "📂 Drop" : "📁 Click"}
|
|
162
|
+
* </div>
|
|
163
|
+
* );
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
declare function useDrop(options: DropOptions): DropReturn;
|
|
167
|
+
|
|
168
|
+
export { type ClientError as C, type DropOptions as D, FILE_STATUSES as F, type ProcessedFile as P, type DropReturn as a, type DropState as b, type DropStateValue as c, type DropStatus as d, type DropzonePropsOptions as e, type FileStatus as f, type FileWithPath as g, useDrop as u };
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { ValidationError } from '@shipstatic/types';
|
|
2
|
+
import { Ship } from '@shipstatic/ship';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Core types for @shipstatic/drop
|
|
6
|
+
* Imports types from @shipstatic/types (single source of truth)
|
|
7
|
+
* and defines drop-specific types
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extended File interface with webkitRelativePath
|
|
12
|
+
* This property is set by browsers for folder uploads and drag-drop
|
|
13
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/File/webkitRelativePath
|
|
14
|
+
*/
|
|
15
|
+
interface FileWithPath extends File {
|
|
16
|
+
readonly webkitRelativePath: string;
|
|
17
|
+
}
|
|
18
|
+
declare const FILE_STATUSES: {
|
|
19
|
+
readonly PROCESSING: "processing";
|
|
20
|
+
readonly UPLOADING: "uploading";
|
|
21
|
+
readonly COMPLETE: "complete";
|
|
22
|
+
readonly ERROR: "error";
|
|
23
|
+
readonly PENDING: "pending";
|
|
24
|
+
readonly PROCESSING_ERROR: "processing_error";
|
|
25
|
+
readonly EMPTY_FILE: "empty_file";
|
|
26
|
+
readonly VALIDATION_FAILED: "validation_failed";
|
|
27
|
+
readonly READY: "ready";
|
|
28
|
+
};
|
|
29
|
+
type FileStatus = (typeof FILE_STATUSES)[keyof typeof FILE_STATUSES];
|
|
30
|
+
/**
|
|
31
|
+
* Client-side error structure
|
|
32
|
+
* Matches ValidationError from @shipstatic/ship for consistency
|
|
33
|
+
*/
|
|
34
|
+
type ClientError = ValidationError;
|
|
35
|
+
/**
|
|
36
|
+
* Processed file entry ready for upload
|
|
37
|
+
* Contains both the File object and UI-specific metadata
|
|
38
|
+
* Use `file` property to access the underlying File for SDK operations
|
|
39
|
+
*/
|
|
40
|
+
interface ProcessedFile {
|
|
41
|
+
/** Unique identifier for React keys and tracking */
|
|
42
|
+
id: string;
|
|
43
|
+
/** The File object - pass this to ship.deployments.create() */
|
|
44
|
+
file: File;
|
|
45
|
+
/** Relative path for deployment (e.g., "images/photo.jpg") */
|
|
46
|
+
path: string;
|
|
47
|
+
/** File size in bytes */
|
|
48
|
+
size: number;
|
|
49
|
+
/** MD5 hash (optional - Ship SDK calculates during deployment if not provided) */
|
|
50
|
+
md5?: string;
|
|
51
|
+
/** Filename without path */
|
|
52
|
+
name: string;
|
|
53
|
+
/** MIME type for UI icons/previews */
|
|
54
|
+
type: string;
|
|
55
|
+
/** Last modified timestamp */
|
|
56
|
+
lastModified: number;
|
|
57
|
+
/** Current processing/upload status */
|
|
58
|
+
status: FileStatus;
|
|
59
|
+
/** Human-readable status message for UI */
|
|
60
|
+
statusMessage?: string;
|
|
61
|
+
/** Upload progress (0-100) - only set during upload */
|
|
62
|
+
progress?: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* State machine values for the drop hook
|
|
66
|
+
*/
|
|
67
|
+
type DropStateValue = 'idle' | 'dragging' | 'processing' | 'ready' | 'error';
|
|
68
|
+
/**
|
|
69
|
+
* Status information with title and details
|
|
70
|
+
*/
|
|
71
|
+
interface DropStatus {
|
|
72
|
+
title: string;
|
|
73
|
+
details: string;
|
|
74
|
+
errors?: string[];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* State machine state for the drop hook
|
|
78
|
+
*/
|
|
79
|
+
interface DropState {
|
|
80
|
+
value: DropStateValue;
|
|
81
|
+
files: ProcessedFile[];
|
|
82
|
+
sourceName: string;
|
|
83
|
+
status: DropStatus | null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface DropOptions {
|
|
87
|
+
/** Ship SDK instance (required for validation) */
|
|
88
|
+
ship: Ship;
|
|
89
|
+
/** Callback when files are processed and ready */
|
|
90
|
+
onFilesReady?: (files: ProcessedFile[]) => void;
|
|
91
|
+
/** Callback when validation fails */
|
|
92
|
+
onValidationError?: (error: ClientError) => void;
|
|
93
|
+
/** Whether to strip common directory prefix from paths (default: true) */
|
|
94
|
+
stripPrefix?: boolean;
|
|
95
|
+
}
|
|
96
|
+
/** Options for getDropzoneProps() */
|
|
97
|
+
interface DropzonePropsOptions {
|
|
98
|
+
/** Whether clicking the dropzone opens the file picker (default: true) */
|
|
99
|
+
clickable?: boolean;
|
|
100
|
+
}
|
|
101
|
+
interface DropReturn {
|
|
102
|
+
/** Current phase of the state machine */
|
|
103
|
+
phase: DropStateValue;
|
|
104
|
+
/** Whether currently processing files (ZIP extraction, etc.) */
|
|
105
|
+
isProcessing: boolean;
|
|
106
|
+
/** Whether user is currently dragging over the dropzone */
|
|
107
|
+
isDragging: boolean;
|
|
108
|
+
/** Whether the dropzone is interactive (idle, dragging, or ready - not processing or error) */
|
|
109
|
+
isInteractive: boolean;
|
|
110
|
+
/** Whether an error occurred during processing */
|
|
111
|
+
hasError: boolean;
|
|
112
|
+
/** Flattened access to files */
|
|
113
|
+
files: ProcessedFile[];
|
|
114
|
+
/** Flattened access to source name */
|
|
115
|
+
sourceName: string;
|
|
116
|
+
/** Flattened access to status */
|
|
117
|
+
status: {
|
|
118
|
+
title: string;
|
|
119
|
+
details: string;
|
|
120
|
+
errors?: string[];
|
|
121
|
+
} | null;
|
|
122
|
+
/** Get props to spread on dropzone element (handles drag & drop, optionally click) */
|
|
123
|
+
getDropzoneProps: (options?: DropzonePropsOptions) => {
|
|
124
|
+
onDragOver: (e: React.DragEvent) => void;
|
|
125
|
+
onDragLeave: (e: React.DragEvent) => void;
|
|
126
|
+
onDrop: (e: React.DragEvent) => void;
|
|
127
|
+
onClick?: () => void;
|
|
128
|
+
};
|
|
129
|
+
/** Get props to spread on hidden file input element */
|
|
130
|
+
getInputProps: () => {
|
|
131
|
+
ref: React.RefObject<HTMLInputElement | null>;
|
|
132
|
+
type: 'file';
|
|
133
|
+
style: {
|
|
134
|
+
display: string;
|
|
135
|
+
};
|
|
136
|
+
multiple: boolean;
|
|
137
|
+
webkitdirectory: string;
|
|
138
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
139
|
+
};
|
|
140
|
+
/** Programmatically trigger file picker */
|
|
141
|
+
open: () => void;
|
|
142
|
+
/** Manually process files (for advanced usage) */
|
|
143
|
+
processFiles: (files: File[]) => Promise<void>;
|
|
144
|
+
/** Reset state and clear all files */
|
|
145
|
+
reset: () => void;
|
|
146
|
+
/** Get only valid files ready for upload */
|
|
147
|
+
validFiles: ProcessedFile[];
|
|
148
|
+
/** Get raw File objects ready for Ship SDK upload */
|
|
149
|
+
getFilesForUpload: () => File[];
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Headless drop hook for file upload workflows
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```tsx
|
|
156
|
+
* const drop = useDrop({ ship });
|
|
157
|
+
*
|
|
158
|
+
* return (
|
|
159
|
+
* <div {...drop.getDropzoneProps()} style={{...}}>
|
|
160
|
+
* <input {...drop.getInputProps()} />
|
|
161
|
+
* {drop.isDragging ? "📂 Drop" : "📁 Click"}
|
|
162
|
+
* </div>
|
|
163
|
+
* );
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
declare function useDrop(options: DropOptions): DropReturn;
|
|
167
|
+
|
|
168
|
+
export { type ClientError as C, type DropOptions as D, FILE_STATUSES as F, type ProcessedFile as P, type DropReturn as a, type DropState as b, type DropStateValue as c, type DropStatus as d, type DropzonePropsOptions as e, type FileStatus as f, type FileWithPath as g, useDrop as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipstatic/drop",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Headless React hook for file dropping, processing, ZIP extraction, and validation - purpose-built for Ship SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -16,6 +16,16 @@
|
|
|
16
16
|
"types": "./dist/index.d.cts",
|
|
17
17
|
"default": "./dist/index.cjs"
|
|
18
18
|
}
|
|
19
|
+
},
|
|
20
|
+
"./testing": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/testing.d.ts",
|
|
23
|
+
"default": "./dist/testing.js"
|
|
24
|
+
},
|
|
25
|
+
"require": {
|
|
26
|
+
"types": "./dist/testing.d.cts",
|
|
27
|
+
"default": "./dist/testing.cjs"
|
|
28
|
+
}
|
|
19
29
|
}
|
|
20
30
|
},
|
|
21
31
|
"files": [
|