@shipstatic/drop 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 };
@@ -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":";AA6CO,SAAS,cAAA,CAAe,OAAA,GAA2B,EAAC,EAAe;AACxE,EAAA,MAAM;AAAA,IACJ,KAAA,GAAQ,MAAA;AAAA,IACR,QAAQ,EAAC;AAAA,IACT,UAAA,GAAa,EAAA;AAAA,IACb,MAAA,GAAS;AAAA,GACX,GAAI,OAAA;AAEJ,EAAA,MAAM,aAAa,KAAA,CAAM,MAAA,CAAO,CAAA,CAAA,KAAK,CAAA,CAAE,WAAW,OAAO,CAAA;AAEzD,EAAA,OAAO;AAAA;AAAA,IAEL,KAAA;AAAA,IACA,cAAc,KAAA,KAAU,YAAA;AAAA,IACxB,YAAY,KAAA,KAAU,UAAA;AAAA,IACtB,aAAA,EAAe,KAAA,KAAU,MAAA,IAAU,KAAA,KAAU,cAAc,KAAA,KAAU,OAAA;AAAA,IACrE,UAAU,KAAA,KAAU,OAAA;AAAA,IACpB,KAAA;AAAA,IACA,UAAA;AAAA,IACA,UAAA;AAAA,IACA,MAAA;AAAA;AAAA,IAGA,gBAAA,EAAkB,CAAC,IAAA,MAAiC;AAAA,MAClD,YAAY,MAAM;AAAA,MAAC,CAAA;AAAA,MACnB,aAAa,MAAM;AAAA,MAAC,CAAA;AAAA,MACpB,QAAQ,MAAM;AAAA,MAAC,CAAA;AAAA,MACf,GAAI,IAAA,EAAM,SAAA,KAAc,KAAA,IAAS,EAAE,SAAS,MAAM;AAAA,MAAC,CAAA;AAAE,KACvD,CAAA;AAAA,IACA,eAAe,OAAO;AAAA,MACpB,GAAA,EAAK,EAAE,OAAA,EAAS,IAAA,EAAK;AAAA,MACrB,IAAA,EAAM,MAAA;AAAA,MACN,KAAA,EAAO,EAAE,OAAA,EAAS,MAAA,EAAO;AAAA,MACzB,QAAA,EAAU,IAAA;AAAA,MACV,eAAA,EAAiB,EAAA;AAAA,MACjB,UAAU,MAAM;AAAA,MAAC;AAAA,KACnB,CAAA;AAAA;AAAA,IAGA,MAAM,MAAM;AAAA,IAAC,CAAA;AAAA,IACb,cAAc,YAAY;AAAA,IAAC,CAAA;AAAA,IAC3B,OAAO,MAAM;AAAA,IAAC,CAAA;AAAA;AAAA,IAGd,mBAAmB,MAAM,UAAA,CAAW,GAAA,CAAI,CAAA,CAAA,KAAK,EAAE,IAAI;AAAA,GACrD;AACF;AAmBO,SAAS,uBAAA,CAAwB,OAAA,GAA2B,EAAC,EAQlE;AACA,EAAA,MAAM,QAAA,GAAW,eAAe,OAAO,CAAA;AAUvC,EAAA,IAAI,SAAA,GAAY,CAAA;AAChB,EAAA,IAAI,oBAA8B,EAAC;AACnC,EAAA,IAAI,UAAA,GAAa,CAAA;AAEjB,EAAA,MAAM,YAAA,GAAe;AAAA,IACnB,IAAA,EAAM,MAAA,CAAO,MAAA,CAAO,MAAM;AAAE,MAAA,SAAA,EAAA;AAAA,IAAa,CAAA,EAAG;AAAA,MAC1C,OAAO,MAAM,SAAA;AAAA,MACb,gBAAA,EAAkB,MAAM,SAAA,GAAY;AAAA,KACrC,CAAA;AAAA,IACD,YAAA,EAAc,MAAA,CAAO,MAAA,CAAO,OAAO,KAAA,KAAkB;AAAE,MAAA,iBAAA,CAAkB,KAAK,KAAK,CAAA;AAAA,IAAG,CAAA,EAAG;AAAA,MACvF,OAAO,MAAM,iBAAA;AAAA,MACb,gBAAA,EAAkB,MAAM,iBAAA,CAAkB,MAAA,GAAS,CAAA;AAAA,MACnD,sBAAsB,CAAC,KAAA,KAAkB,kBAAkB,IAAA,CAAK,CAAA,CAAA,KAAK,MAAM,KAAK;AAAA,KACjF,CAAA;AAAA,IACD,KAAA,EAAO,MAAA,CAAO,MAAA,CAAO,MAAM;AAAE,MAAA,UAAA,EAAA;AAAA,IAAc,CAAA,EAAG;AAAA,MAC5C,OAAO,MAAM,UAAA;AAAA,MACb,gBAAA,EAAkB,MAAM,UAAA,GAAa;AAAA,KACtC,CAAA;AAAA,IACD,mBAAmB,QAAA,CAAS;AAAA,GAC9B;AAEA,EAAA,OAAO;AAAA,IACL,IAAA,EAAM;AAAA,MACJ,GAAG,QAAA;AAAA,MACH,MAAM,YAAA,CAAa,IAAA;AAAA,MACnB,cAAc,YAAA,CAAa,YAAA;AAAA,MAC3B,OAAO,YAAA,CAAa,KAAA;AAAA,MACpB,mBAAmB,YAAA,CAAa;AAAA,KAClC;AAAA,IACA,KAAA,EAAO;AAAA,GACT;AACF;AAMA,IAAI,iBAAA,GAAoB,CAAA;AAKjB,SAAS,uBAAA,CACd,IAAA,EACA,OAAA,GAMI,EAAC,EACU;AACf,EAAA,MAAM;AAAA,IACJ,IAAA,GAAO,IAAA;AAAA,IACP,OAAA,GAAU,cAAA;AAAA,IACV,IAAA,GAAO,YAAA;AAAA,IACP,MAAA,GAAS,OAAA;AAAA,IACT;AAAA,GACF,GAAI,OAAA;AAEJ,EAAA,MAAM,IAAA,GAAO,IAAI,IAAA,CAAK,CAAC,OAAO,CAAA,EAAG,IAAA,EAAM,EAAE,IAAA,EAAM,CAAA;AAE/C,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,CAAA,UAAA,EAAa,EAAE,iBAAiB,CAAA,CAAA;AAAA,IACpC,IAAA;AAAA,IACA,IAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAM,IAAA,CAAK,IAAA;AAAA,IACX,IAAA;AAAA,IACA,YAAA,EAAc,KAAK,GAAA,EAAI;AAAA,IACvB,MAAA;AAAA,IACA;AAAA,GACF;AACF;AAKO,SAAS,cAAA,CACd,IAAA,EACA,OAAA,GAAkB,cAAA,EAClB,OAAe,YAAA,EACT;AACN,EAAA,OAAO,IAAI,IAAA,CAAK,CAAC,OAAO,CAAA,EAAG,IAAA,EAAM,EAAE,IAAA,EAAM,YAAA,EAAc,IAAA,CAAK,GAAA,EAAI,EAAG,CAAA;AACrE;AAKO,SAAS,uBACd,IAAA,EACA,kBAAA,EACA,OAAA,GAAkB,cAAA,EAClB,OAAe,YAAA,EACT;AACN,EAAA,MAAM,IAAA,GAAO,cAAA,CAAe,IAAA,EAAM,OAAA,EAAS,IAAI,CAAA;AAC/C,EAAA,MAAA,CAAO,cAAA,CAAe,MAAM,oBAAA,EAAsB;AAAA,IAChD,KAAA,EAAO,kBAAA;AAAA,IACP,QAAA,EAAU,KAAA;AAAA,IACV,UAAA,EAAY,IAAA;AAAA,IACZ,YAAA,EAAc;AAAA,GACf,CAAA;AACD,EAAA,OAAO,IAAA;AACT;AASO,SAAS,sBACd,KAAA,GAAgB,mBAAA,EAChB,UAAkB,qCAAA,EAClB,MAAA,GAAmB,EAAC,EACR;AACZ,EAAA,OAAO,EAAE,KAAA,EAAO,OAAA,EAAS,MAAA,EAAO;AAClC;AAKO,SAAS,0BAAA,CACd,KAAA,GAAgB,eAAA,EAChB,OAAA,GAAkB,iCAAA,EACN;AACZ,EAAA,OAAO,EAAE,OAAO,OAAA,EAAQ;AAC1B;AAKO,SAAS,sBAAsB,SAAA,EAA+B;AACnE,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,OAAA;AAAA,IACP,OAAA,EAAS,GAAG,SAAS,CAAA,mBAAA;AAAA,GACvB;AACF","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"]}