@jskit-ai/uploads-image-web 0.1.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/package.descriptor.mjs +80 -0
- package/package.json +22 -0
- package/src/client/composables/createImageUploadRuntime.js +245 -0
- package/src/client/index.js +5 -0
- package/src/client/styles/index.js +5 -0
- package/src/client/support/parseUploadResponse.js +13 -0
- package/src/client/support/stopImageEditor.js +8 -0
- package/src/shared/imageUploadDefaults.js +29 -0
- package/src/shared/index.js +7 -0
- package/test/createImageUploadRuntime.test.js +171 -0
- package/test/exportsContract.test.js +23 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export default Object.freeze({
|
|
2
|
+
packageVersion: 1,
|
|
3
|
+
packageId: "@jskit-ai/uploads-image-web",
|
|
4
|
+
version: "0.1.1",
|
|
5
|
+
kind: "runtime",
|
|
6
|
+
description: "Reusable client-side image upload runtime with pre-upload image editing.",
|
|
7
|
+
dependsOn: [
|
|
8
|
+
"@jskit-ai/uploads-runtime"
|
|
9
|
+
],
|
|
10
|
+
capabilities: {
|
|
11
|
+
provides: [
|
|
12
|
+
"runtime.uploads.image-web"
|
|
13
|
+
],
|
|
14
|
+
requires: []
|
|
15
|
+
},
|
|
16
|
+
runtime: {
|
|
17
|
+
server: {
|
|
18
|
+
providers: []
|
|
19
|
+
},
|
|
20
|
+
client: {
|
|
21
|
+
providers: []
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
metadata: {
|
|
25
|
+
client: {
|
|
26
|
+
optimizeDeps: {
|
|
27
|
+
include: [
|
|
28
|
+
"@uppy/core",
|
|
29
|
+
"@uppy/dashboard",
|
|
30
|
+
"@uppy/image-editor",
|
|
31
|
+
"@uppy/compressor",
|
|
32
|
+
"@uppy/xhr-upload"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
apiSummary: {
|
|
37
|
+
surfaces: [
|
|
38
|
+
{
|
|
39
|
+
subpath: "./client",
|
|
40
|
+
summary: "Exports reusable image upload client runtime helpers."
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
subpath: "./client/composables/createImageUploadRuntime",
|
|
44
|
+
summary: "Exports the reusable image upload runtime factory."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
subpath: "./client/styles",
|
|
48
|
+
summary: "Exports Uppy CSS side effects for image upload UIs."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
subpath: "./shared",
|
|
52
|
+
summary: "Exports shared image upload defaults."
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
containerTokens: {
|
|
56
|
+
server: [],
|
|
57
|
+
client: []
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
mutations: {
|
|
62
|
+
dependencies: {
|
|
63
|
+
runtime: {
|
|
64
|
+
"@jskit-ai/uploads-runtime": "0.1.1",
|
|
65
|
+
"@uppy/compressor": "^3.1.0",
|
|
66
|
+
"@uppy/core": "^5.2.0",
|
|
67
|
+
"@uppy/dashboard": "^5.1.1",
|
|
68
|
+
"@uppy/image-editor": "^4.2.0",
|
|
69
|
+
"@uppy/xhr-upload": "^5.1.1"
|
|
70
|
+
},
|
|
71
|
+
dev: {}
|
|
72
|
+
},
|
|
73
|
+
packageJson: {
|
|
74
|
+
scripts: {}
|
|
75
|
+
},
|
|
76
|
+
procfile: {},
|
|
77
|
+
files: [],
|
|
78
|
+
text: []
|
|
79
|
+
}
|
|
80
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jskit-ai/uploads-image-web",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "node --test"
|
|
7
|
+
},
|
|
8
|
+
"exports": {
|
|
9
|
+
"./client": "./src/client/index.js",
|
|
10
|
+
"./client/composables/createImageUploadRuntime": "./src/client/composables/createImageUploadRuntime.js",
|
|
11
|
+
"./client/styles": "./src/client/styles/index.js",
|
|
12
|
+
"./shared": "./src/shared/index.js"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@jskit-ai/uploads-runtime": "0.1.1",
|
|
16
|
+
"@uppy/compressor": "^3.1.0",
|
|
17
|
+
"@uppy/core": "^5.2.0",
|
|
18
|
+
"@uppy/dashboard": "^5.1.1",
|
|
19
|
+
"@uppy/image-editor": "^4.2.0",
|
|
20
|
+
"@uppy/xhr-upload": "^5.1.1"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import Uppy from "@uppy/core";
|
|
2
|
+
import Dashboard from "@uppy/dashboard";
|
|
3
|
+
import ImageEditor from "@uppy/image-editor";
|
|
4
|
+
import Compressor from "@uppy/compressor";
|
|
5
|
+
import XHRUpload from "@uppy/xhr-upload";
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_IMAGE_COMPRESSOR_OPTIONS,
|
|
8
|
+
DEFAULT_IMAGE_DASHBOARD_OPTIONS,
|
|
9
|
+
DEFAULT_IMAGE_EDITOR_OPTIONS,
|
|
10
|
+
DEFAULT_IMAGE_UPLOAD_ALLOWED_MIME_TYPES,
|
|
11
|
+
DEFAULT_IMAGE_UPLOAD_MAX_BYTES
|
|
12
|
+
} from "../../shared/imageUploadDefaults.js";
|
|
13
|
+
import { parseUploadResponse } from "../support/parseUploadResponse.js";
|
|
14
|
+
import { stopImageEditor } from "../support/stopImageEditor.js";
|
|
15
|
+
|
|
16
|
+
function normalizeObject(value) {
|
|
17
|
+
return value && typeof value === "object" ? value : {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createImageUploadRuntime({
|
|
21
|
+
endpoint = "",
|
|
22
|
+
method = "POST",
|
|
23
|
+
fieldName = "file",
|
|
24
|
+
withCredentials = true,
|
|
25
|
+
maxNumberOfFiles = 1,
|
|
26
|
+
allowedMimeTypes = DEFAULT_IMAGE_UPLOAD_ALLOWED_MIME_TYPES,
|
|
27
|
+
maxUploadBytes = DEFAULT_IMAGE_UPLOAD_MAX_BYTES,
|
|
28
|
+
note = "",
|
|
29
|
+
dashboardOptions = {},
|
|
30
|
+
imageEditorOptions = {},
|
|
31
|
+
compressorOptions = {},
|
|
32
|
+
parseResponse = parseUploadResponse,
|
|
33
|
+
resolveRequestHeaders = null,
|
|
34
|
+
onSelectedFileNameChanged = null,
|
|
35
|
+
onUploadSuccess = null,
|
|
36
|
+
onInvalidResponse = null,
|
|
37
|
+
onUploadError = null,
|
|
38
|
+
onRestrictionFailed = null,
|
|
39
|
+
onUnavailable = null,
|
|
40
|
+
dependencies = {}
|
|
41
|
+
} = {}) {
|
|
42
|
+
const UppyClass = dependencies.UppyClass || Uppy;
|
|
43
|
+
const DashboardPlugin = dependencies.DashboardPlugin || Dashboard;
|
|
44
|
+
const ImageEditorPlugin = dependencies.ImageEditorPlugin || ImageEditor;
|
|
45
|
+
const CompressorPlugin = dependencies.CompressorPlugin || Compressor;
|
|
46
|
+
const XHRUploadPlugin = dependencies.XHRUploadPlugin || XHRUpload;
|
|
47
|
+
let uploadUppy = null;
|
|
48
|
+
|
|
49
|
+
const normalizedAllowedMimeTypes =
|
|
50
|
+
Array.isArray(allowedMimeTypes) && allowedMimeTypes.length > 0
|
|
51
|
+
? allowedMimeTypes.map((value) => String(value || "").trim()).filter(Boolean)
|
|
52
|
+
: [...DEFAULT_IMAGE_UPLOAD_ALLOWED_MIME_TYPES];
|
|
53
|
+
const normalizedMaxUploadBytes =
|
|
54
|
+
Number.isInteger(maxUploadBytes) && maxUploadBytes > 0 ? maxUploadBytes : DEFAULT_IMAGE_UPLOAD_MAX_BYTES;
|
|
55
|
+
const normalizedEndpoint = String(endpoint || "").trim();
|
|
56
|
+
const normalizedFieldName = String(fieldName || "file").trim() || "file";
|
|
57
|
+
const normalizedMethod = String(method || "POST").trim().toUpperCase() || "POST";
|
|
58
|
+
const dashboardNote =
|
|
59
|
+
String(note || "").trim() ||
|
|
60
|
+
`Accepted: ${normalizedAllowedMimeTypes.join(", ")}, max ${Math.floor(normalizedMaxUploadBytes / (1024 * 1024))}MB`;
|
|
61
|
+
|
|
62
|
+
function emitSelectedFileName(fileName) {
|
|
63
|
+
if (typeof onSelectedFileNameChanged === "function") {
|
|
64
|
+
onSelectedFileNameChanged(String(fileName || ""));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function setup() {
|
|
69
|
+
if (typeof window === "undefined") {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (uploadUppy) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const uppy = new UppyClass({
|
|
78
|
+
autoProceed: false,
|
|
79
|
+
restrictions: {
|
|
80
|
+
maxNumberOfFiles: Number.isInteger(maxNumberOfFiles) && maxNumberOfFiles > 0 ? maxNumberOfFiles : 1,
|
|
81
|
+
allowedFileTypes: [...normalizedAllowedMimeTypes],
|
|
82
|
+
maxFileSize: normalizedMaxUploadBytes
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
uppy.use(DashboardPlugin, {
|
|
87
|
+
...DEFAULT_IMAGE_DASHBOARD_OPTIONS,
|
|
88
|
+
...normalizeObject(dashboardOptions),
|
|
89
|
+
note: dashboardNote,
|
|
90
|
+
doneButtonHandler: () => {
|
|
91
|
+
const dashboard = uppy.getPlugin("Dashboard");
|
|
92
|
+
if (dashboard && typeof dashboard.closeModal === "function") {
|
|
93
|
+
dashboard.closeModal();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
uppy.use(ImageEditorPlugin, {
|
|
99
|
+
...DEFAULT_IMAGE_EDITOR_OPTIONS,
|
|
100
|
+
...normalizeObject(imageEditorOptions)
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
uppy.use(CompressorPlugin, {
|
|
104
|
+
...DEFAULT_IMAGE_COMPRESSOR_OPTIONS,
|
|
105
|
+
...normalizeObject(compressorOptions)
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
uppy.use(XHRUploadPlugin, {
|
|
109
|
+
endpoint: normalizedEndpoint,
|
|
110
|
+
method: normalizedMethod,
|
|
111
|
+
formData: true,
|
|
112
|
+
fieldName: normalizedFieldName,
|
|
113
|
+
withCredentials: withCredentials !== false,
|
|
114
|
+
onBeforeRequest: async (xhr) => {
|
|
115
|
+
const headers =
|
|
116
|
+
typeof resolveRequestHeaders === "function"
|
|
117
|
+
? normalizeObject(await resolveRequestHeaders({ xhr, uppy }))
|
|
118
|
+
: {};
|
|
119
|
+
for (const [headerName, headerValue] of Object.entries(headers)) {
|
|
120
|
+
if (headerValue == null) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
xhr.setRequestHeader(headerName, String(headerValue));
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
getResponseData: parseResponse
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
uppy.on("file-added", (file) => {
|
|
130
|
+
emitSelectedFileName(file?.name || "");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
uppy.on("file-removed", () => {
|
|
134
|
+
emitSelectedFileName("");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
uppy.on("file-editor:complete", (file) => {
|
|
138
|
+
emitSelectedFileName(file?.name || "");
|
|
139
|
+
stopImageEditor(uppy);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
uppy.on("file-editor:cancel", () => {
|
|
143
|
+
stopImageEditor(uppy);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
uppy.on("dashboard:modal-closed", () => {
|
|
147
|
+
stopImageEditor(uppy);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
uppy.on("upload-success", (file, response) => {
|
|
151
|
+
const data = response?.body;
|
|
152
|
+
if (!data || typeof data !== "object") {
|
|
153
|
+
if (typeof onInvalidResponse === "function") {
|
|
154
|
+
onInvalidResponse({
|
|
155
|
+
file,
|
|
156
|
+
response,
|
|
157
|
+
uppy
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (typeof onUploadSuccess === "function") {
|
|
164
|
+
onUploadSuccess({
|
|
165
|
+
data,
|
|
166
|
+
file,
|
|
167
|
+
response,
|
|
168
|
+
uppy
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
emitSelectedFileName("");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
uppy.on("upload-error", (file, error, response) => {
|
|
176
|
+
if (typeof onUploadError === "function") {
|
|
177
|
+
onUploadError({
|
|
178
|
+
file,
|
|
179
|
+
error,
|
|
180
|
+
response,
|
|
181
|
+
uppy
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
uppy.on("restriction-failed", (file, error) => {
|
|
187
|
+
if (typeof onRestrictionFailed === "function") {
|
|
188
|
+
onRestrictionFailed({
|
|
189
|
+
file,
|
|
190
|
+
error,
|
|
191
|
+
uppy
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
uppy.on("complete", (result) => {
|
|
197
|
+
const successfulCount = Array.isArray(result?.successful) ? result.successful.length : 0;
|
|
198
|
+
if (successfulCount <= 0) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
uppy.clear();
|
|
204
|
+
} catch {
|
|
205
|
+
// Upload succeeded; ignore clear timing issues.
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
uploadUppy = uppy;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function openEditor() {
|
|
213
|
+
setup();
|
|
214
|
+
|
|
215
|
+
const uppy = uploadUppy;
|
|
216
|
+
if (!uppy) {
|
|
217
|
+
if (typeof onUnavailable === "function") {
|
|
218
|
+
onUnavailable();
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const dashboard = uppy.getPlugin("Dashboard");
|
|
224
|
+
if (dashboard && typeof dashboard.openModal === "function") {
|
|
225
|
+
dashboard.openModal();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function destroy() {
|
|
230
|
+
if (!uploadUppy) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
uploadUppy.destroy();
|
|
235
|
+
uploadUppy = null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return Object.freeze({
|
|
239
|
+
destroy,
|
|
240
|
+
openEditor,
|
|
241
|
+
setup
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export { createImageUploadRuntime };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_IMAGE_UPLOAD_ALLOWED_MIME_TYPES,
|
|
3
|
+
DEFAULT_IMAGE_UPLOAD_MAX_BYTES
|
|
4
|
+
} from "@jskit-ai/uploads-runtime/shared";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_IMAGE_EDITOR_OPTIONS = Object.freeze({
|
|
7
|
+
quality: 0.9
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const DEFAULT_IMAGE_COMPRESSOR_OPTIONS = Object.freeze({
|
|
11
|
+
quality: 0.84,
|
|
12
|
+
limit: 1
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const DEFAULT_IMAGE_DASHBOARD_OPTIONS = Object.freeze({
|
|
16
|
+
inline: false,
|
|
17
|
+
closeAfterFinish: false,
|
|
18
|
+
showProgressDetails: true,
|
|
19
|
+
proudlyDisplayPoweredByUppy: false,
|
|
20
|
+
hideUploadButton: false
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
DEFAULT_IMAGE_COMPRESSOR_OPTIONS,
|
|
25
|
+
DEFAULT_IMAGE_DASHBOARD_OPTIONS,
|
|
26
|
+
DEFAULT_IMAGE_EDITOR_OPTIONS,
|
|
27
|
+
DEFAULT_IMAGE_UPLOAD_ALLOWED_MIME_TYPES,
|
|
28
|
+
DEFAULT_IMAGE_UPLOAD_MAX_BYTES
|
|
29
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { createImageUploadRuntime } from "../src/client/composables/createImageUploadRuntime.js";
|
|
4
|
+
|
|
5
|
+
class FakeUppy {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
this.plugins = new Map();
|
|
9
|
+
this.pluginOptions = new Map();
|
|
10
|
+
this.eventHandlers = new Map();
|
|
11
|
+
this.clearCount = 0;
|
|
12
|
+
this.destroyCount = 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
use(plugin, options) {
|
|
16
|
+
const pluginName = String(plugin?.name || plugin?.pluginName || "");
|
|
17
|
+
this.pluginOptions.set(pluginName, options);
|
|
18
|
+
|
|
19
|
+
if (pluginName === "Dashboard") {
|
|
20
|
+
this.plugins.set("Dashboard", {
|
|
21
|
+
openCount: 0,
|
|
22
|
+
closeCount: 0,
|
|
23
|
+
openModal() {
|
|
24
|
+
this.openCount += 1;
|
|
25
|
+
},
|
|
26
|
+
closeModal() {
|
|
27
|
+
this.closeCount += 1;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
} else if (pluginName === "ImageEditor") {
|
|
31
|
+
this.plugins.set("ImageEditor", {
|
|
32
|
+
stopCount: 0,
|
|
33
|
+
stop() {
|
|
34
|
+
this.stopCount += 1;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
on(eventName, handler) {
|
|
43
|
+
this.eventHandlers.set(eventName, handler);
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getPlugin(name) {
|
|
48
|
+
return this.plugins.get(name) || null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
clear() {
|
|
52
|
+
this.clearCount += 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
destroy() {
|
|
56
|
+
this.destroyCount += 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function setWindowStub() {
|
|
61
|
+
const previousWindow = globalThis.window;
|
|
62
|
+
globalThis.window = {};
|
|
63
|
+
return () => {
|
|
64
|
+
if (previousWindow === undefined) {
|
|
65
|
+
delete globalThis.window;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
globalThis.window = previousWindow;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
test("createImageUploadRuntime wires headers and lifecycle callbacks", async () => {
|
|
73
|
+
const restoreWindow = setWindowStub();
|
|
74
|
+
let fakeUppy = null;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const fileNames = [];
|
|
78
|
+
const successPayloads = [];
|
|
79
|
+
const invalidResponses = [];
|
|
80
|
+
const unavailableCalls = [];
|
|
81
|
+
const restrictionFailures = [];
|
|
82
|
+
const uploadErrors = [];
|
|
83
|
+
|
|
84
|
+
const runtime = createImageUploadRuntime({
|
|
85
|
+
endpoint: "/api/upload",
|
|
86
|
+
fieldName: "avatar",
|
|
87
|
+
resolveRequestHeaders: async () => ({
|
|
88
|
+
"csrf-token": "csrf-1"
|
|
89
|
+
}),
|
|
90
|
+
onSelectedFileNameChanged: (name) => {
|
|
91
|
+
fileNames.push(name);
|
|
92
|
+
},
|
|
93
|
+
onUploadSuccess: (payload) => {
|
|
94
|
+
successPayloads.push(payload.data);
|
|
95
|
+
},
|
|
96
|
+
onInvalidResponse: (payload) => {
|
|
97
|
+
invalidResponses.push(payload.response?.body || null);
|
|
98
|
+
},
|
|
99
|
+
onUploadError: (payload) => {
|
|
100
|
+
uploadErrors.push(payload.error?.message || "");
|
|
101
|
+
},
|
|
102
|
+
onRestrictionFailed: (payload) => {
|
|
103
|
+
restrictionFailures.push(payload.error?.message || "");
|
|
104
|
+
},
|
|
105
|
+
onUnavailable: () => {
|
|
106
|
+
unavailableCalls.push(true);
|
|
107
|
+
},
|
|
108
|
+
dependencies: {
|
|
109
|
+
UppyClass: class extends FakeUppy {
|
|
110
|
+
constructor(options) {
|
|
111
|
+
super(options);
|
|
112
|
+
fakeUppy = this;
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
DashboardPlugin: { name: "Dashboard" },
|
|
116
|
+
ImageEditorPlugin: { name: "ImageEditor" },
|
|
117
|
+
CompressorPlugin: { name: "Compressor" },
|
|
118
|
+
XHRUploadPlugin: { name: "XHRUpload" }
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
runtime.setup();
|
|
123
|
+
runtime.openEditor();
|
|
124
|
+
|
|
125
|
+
assert.ok(fakeUppy);
|
|
126
|
+
const pluginOptions = fakeUppy.pluginOptions || new Map();
|
|
127
|
+
const xhrOptions = pluginOptions.get("XHRUpload");
|
|
128
|
+
|
|
129
|
+
const headers = [];
|
|
130
|
+
await xhrOptions.onBeforeRequest({
|
|
131
|
+
setRequestHeader(name, value) {
|
|
132
|
+
headers.push([name, value]);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
assert.deepEqual(headers, [["csrf-token", "csrf-1"]]);
|
|
136
|
+
|
|
137
|
+
fakeUppy.eventHandlers.get("file-added")({ name: "face.png" });
|
|
138
|
+
fakeUppy.eventHandlers.get("file-editor:complete")({ name: "face-edited.png" });
|
|
139
|
+
fakeUppy.eventHandlers.get("upload-success")({}, { body: { ok: true } });
|
|
140
|
+
fakeUppy.eventHandlers.get("upload-success")({}, { body: "" });
|
|
141
|
+
fakeUppy.eventHandlers.get("upload-error")({}, new Error("boom"), {});
|
|
142
|
+
fakeUppy.eventHandlers.get("restriction-failed")({}, new Error("nope"));
|
|
143
|
+
fakeUppy.eventHandlers.get("complete")({ successful: [{}] });
|
|
144
|
+
|
|
145
|
+
assert.deepEqual(fileNames, ["face.png", "face-edited.png", ""]);
|
|
146
|
+
assert.deepEqual(successPayloads, [{ ok: true }]);
|
|
147
|
+
assert.deepEqual(invalidResponses, [null]);
|
|
148
|
+
assert.deepEqual(uploadErrors, ["boom"]);
|
|
149
|
+
assert.deepEqual(restrictionFailures, ["nope"]);
|
|
150
|
+
assert.equal(fakeUppy.clearCount, 1);
|
|
151
|
+
|
|
152
|
+
runtime.destroy();
|
|
153
|
+
assert.equal(fakeUppy.destroyCount, 1);
|
|
154
|
+
assert.deepEqual(unavailableCalls, []);
|
|
155
|
+
} finally {
|
|
156
|
+
restoreWindow();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("createImageUploadRuntime reports unavailable editor outside browser environments", () => {
|
|
161
|
+
const unavailableCalls = [];
|
|
162
|
+
|
|
163
|
+
const runtime = createImageUploadRuntime({
|
|
164
|
+
onUnavailable: () => {
|
|
165
|
+
unavailableCalls.push(true);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
runtime.openEditor();
|
|
170
|
+
assert.deepEqual(unavailableCalls, [true]);
|
|
171
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { evaluatePackageExportsContract } from "../../../tooling/test-support/exportsContract.mjs";
|
|
6
|
+
|
|
7
|
+
const TEST_DIRECTORY = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const REPO_ROOT = path.resolve(TEST_DIRECTORY, "..", "..", "..");
|
|
9
|
+
const PACKAGE_DIR = path.join(REPO_ROOT, "packages", "uploads-image-web");
|
|
10
|
+
|
|
11
|
+
test("uploads-image-web exports are explicit and aligned with usage", () => {
|
|
12
|
+
const result = evaluatePackageExportsContract({
|
|
13
|
+
repoRoot: REPO_ROOT,
|
|
14
|
+
packageDir: PACKAGE_DIR,
|
|
15
|
+
packageId: "@jskit-ai/uploads-image-web",
|
|
16
|
+
requiredExports: ["./client", "./shared"]
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
assert.deepEqual(result.wildcardExports, []);
|
|
20
|
+
assert.deepEqual(result.missingRequiredExports, []);
|
|
21
|
+
assert.deepEqual(result.missingExports, []);
|
|
22
|
+
assert.deepEqual(result.staleExports, []);
|
|
23
|
+
});
|