@transloadit/convex 0.0.3 → 0.0.4
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/README.md +114 -134
- package/dist/client/index.d.ts +24 -13
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +14 -3
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +2 -2
- package/dist/component/_generated/dataModel.d.ts +1 -1
- package/dist/component/_generated/server.d.ts +1 -1
- package/dist/component/apiUtils.d.ts +26 -6
- package/dist/component/apiUtils.d.ts.map +1 -1
- package/dist/component/apiUtils.js +48 -38
- package/dist/component/apiUtils.js.map +1 -1
- package/dist/component/lib.d.ts +7 -9
- package/dist/component/lib.d.ts.map +1 -1
- package/dist/component/lib.js +74 -18
- package/dist/component/lib.js.map +1 -1
- package/dist/component/schema.d.ts +4 -6
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +0 -7
- package/dist/component/schema.js.map +1 -1
- package/dist/debug/index.d.ts +19 -0
- package/dist/debug/index.d.ts.map +1 -0
- package/dist/debug/index.js +49 -0
- package/dist/debug/index.js.map +1 -0
- package/dist/react/index.d.ts +201 -3
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +674 -94
- package/dist/react/index.js.map +1 -1
- package/dist/shared/assemblyUrls.d.ts +10 -0
- package/dist/shared/assemblyUrls.d.ts.map +1 -0
- package/dist/shared/assemblyUrls.js +26 -0
- package/dist/shared/assemblyUrls.js.map +1 -0
- package/dist/shared/errors.d.ts +7 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/errors.js +10 -0
- package/dist/shared/errors.js.map +1 -0
- package/dist/shared/pollAssembly.d.ts +12 -0
- package/dist/shared/pollAssembly.d.ts.map +1 -0
- package/dist/shared/pollAssembly.js +50 -0
- package/dist/shared/pollAssembly.js.map +1 -0
- package/dist/shared/resultTypes.d.ts +37 -0
- package/dist/shared/resultTypes.d.ts.map +1 -0
- package/dist/shared/resultTypes.js +2 -0
- package/dist/shared/resultTypes.js.map +1 -0
- package/dist/shared/resultUtils.d.ts +4 -0
- package/dist/shared/resultUtils.d.ts.map +1 -0
- package/dist/shared/resultUtils.js +69 -0
- package/dist/shared/resultUtils.js.map +1 -0
- package/dist/shared/tusUpload.d.ts +13 -0
- package/dist/shared/tusUpload.d.ts.map +1 -0
- package/dist/shared/tusUpload.js +32 -0
- package/dist/shared/tusUpload.js.map +1 -0
- package/dist/test/index.d.ts +4 -4
- package/dist/test/nodeModules.d.ts +2 -0
- package/dist/test/nodeModules.d.ts.map +1 -0
- package/dist/test/nodeModules.js +19 -0
- package/dist/test/nodeModules.js.map +1 -0
- package/package.json +36 -6
- package/src/client/index.ts +73 -7
- package/src/component/_generated/api.ts +2 -2
- package/src/component/_generated/dataModel.ts +1 -1
- package/src/component/_generated/server.ts +1 -1
- package/src/component/apiUtils.test.ts +166 -2
- package/src/component/apiUtils.ts +96 -64
- package/src/component/lib.test.ts +170 -4
- package/src/component/lib.ts +113 -25
- package/src/component/schema.ts +0 -10
- package/src/debug/index.ts +84 -0
- package/src/react/index.test.tsx +340 -0
- package/src/react/index.tsx +1089 -179
- package/src/react/uploadWithTus.test.tsx +192 -0
- package/src/shared/assemblyUrls.test.ts +71 -0
- package/src/shared/assemblyUrls.ts +59 -0
- package/src/shared/errors.ts +23 -0
- package/src/shared/pollAssembly.ts +65 -0
- package/src/shared/resultTypes.ts +44 -0
- package/src/shared/resultUtils.test.ts +29 -0
- package/src/shared/resultUtils.ts +71 -0
- package/src/shared/tusUpload.ts +59 -0
- package/src/test/index.ts +1 -1
- package/src/test/nodeModules.ts +19 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
// @vitest-environment jsdom
|
|
3
|
+
|
|
4
|
+
import { renderHook } from "@testing-library/react";
|
|
5
|
+
import type { FunctionReference } from "convex/server";
|
|
6
|
+
import { act } from "react";
|
|
7
|
+
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
8
|
+
import type {
|
|
9
|
+
CreateAssemblyFn,
|
|
10
|
+
GetAssemblyStatusFn,
|
|
11
|
+
ListResultsFn,
|
|
12
|
+
RefreshAssemblyFn,
|
|
13
|
+
UppyLike,
|
|
14
|
+
} from "./index.tsx";
|
|
15
|
+
import {
|
|
16
|
+
useAssemblyStatusWithPolling,
|
|
17
|
+
useTransloaditUpload,
|
|
18
|
+
useTransloaditUppy,
|
|
19
|
+
} from "./index.tsx";
|
|
20
|
+
|
|
21
|
+
(
|
|
22
|
+
globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
|
|
23
|
+
).IS_REACT_ACT_ENVIRONMENT = true;
|
|
24
|
+
|
|
25
|
+
let currentStatus: unknown = null;
|
|
26
|
+
let currentResults: unknown = null;
|
|
27
|
+
let queryHandler: (fn: unknown, args: unknown) => unknown = () => currentStatus;
|
|
28
|
+
const refreshMock = vi.hoisted(() => vi.fn(() => Promise.resolve()));
|
|
29
|
+
const actionMock = vi.hoisted(() => vi.fn((fn: unknown) => fn));
|
|
30
|
+
const queryMock = vi.hoisted(() => vi.fn());
|
|
31
|
+
|
|
32
|
+
vi.mock("convex/react", () => ({
|
|
33
|
+
useQuery: queryMock,
|
|
34
|
+
useAction: actionMock,
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
vi.mock("tus-js-client", () => {
|
|
38
|
+
type UploadOptions = {
|
|
39
|
+
endpoint?: string;
|
|
40
|
+
onUploadUrlAvailable?: () => void;
|
|
41
|
+
onProgress?: (bytesUploaded: number, bytesTotal: number) => void;
|
|
42
|
+
onSuccess?: () => void;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
Upload: class MockUpload {
|
|
47
|
+
url?: string;
|
|
48
|
+
private options: UploadOptions;
|
|
49
|
+
constructor(_file: File, options: UploadOptions) {
|
|
50
|
+
this.options = options;
|
|
51
|
+
this.url = options?.endpoint
|
|
52
|
+
? `${options.endpoint}/upload`
|
|
53
|
+
: "https://tus.mock/upload";
|
|
54
|
+
}
|
|
55
|
+
start() {
|
|
56
|
+
this.options?.onUploadUrlAvailable?.();
|
|
57
|
+
this.options?.onProgress?.(1, 1);
|
|
58
|
+
this.options?.onSuccess?.();
|
|
59
|
+
}
|
|
60
|
+
abort() {
|
|
61
|
+
// no-op for tests
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const noopGetStatus = (() => null) as unknown as Parameters<
|
|
68
|
+
typeof useAssemblyStatusWithPolling
|
|
69
|
+
>[0];
|
|
70
|
+
const noopRefresh = refreshMock as unknown as RefreshAssemblyFn;
|
|
71
|
+
|
|
72
|
+
queryMock.mockImplementation((fn, args) => queryHandler(fn, args));
|
|
73
|
+
|
|
74
|
+
describe("useAssemblyStatusWithPolling", () => {
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
vi.useRealTimers();
|
|
77
|
+
refreshMock.mockClear();
|
|
78
|
+
actionMock.mockClear();
|
|
79
|
+
queryMock.mockClear();
|
|
80
|
+
currentResults = null;
|
|
81
|
+
currentStatus = null;
|
|
82
|
+
queryHandler = () => currentStatus;
|
|
83
|
+
queryMock.mockImplementation((fn, args) => queryHandler(fn, args));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("does not trigger immediate refresh on status change", async () => {
|
|
87
|
+
vi.useFakeTimers();
|
|
88
|
+
currentStatus = { ok: "ASSEMBLY_UPLOADING" };
|
|
89
|
+
|
|
90
|
+
const { rerender, unmount } = renderHook(
|
|
91
|
+
({ assemblyId }: { assemblyId: string }) =>
|
|
92
|
+
useAssemblyStatusWithPolling(noopGetStatus, noopRefresh, assemblyId, {
|
|
93
|
+
pollIntervalMs: 1000,
|
|
94
|
+
}),
|
|
95
|
+
{ initialProps: { assemblyId: "asm_1" } },
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
await act(async () => {
|
|
99
|
+
await Promise.resolve();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expect(refreshMock).toHaveBeenCalledTimes(1);
|
|
103
|
+
|
|
104
|
+
await act(async () => {
|
|
105
|
+
vi.advanceTimersByTime(1000);
|
|
106
|
+
await Promise.resolve();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(refreshMock).toHaveBeenCalledTimes(2);
|
|
110
|
+
|
|
111
|
+
currentStatus = { ok: "ASSEMBLY_COMPLETED" };
|
|
112
|
+
rerender({ assemblyId: "asm_1" });
|
|
113
|
+
|
|
114
|
+
await act(async () => {
|
|
115
|
+
await Promise.resolve();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(refreshMock).toHaveBeenCalledTimes(2);
|
|
119
|
+
|
|
120
|
+
unmount();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("does not overlap refresh calls", async () => {
|
|
124
|
+
vi.useFakeTimers();
|
|
125
|
+
currentStatus = { ok: "ASSEMBLY_UPLOADING" };
|
|
126
|
+
let resolveRefresh: (() => void) | null = null;
|
|
127
|
+
refreshMock.mockImplementation(
|
|
128
|
+
() =>
|
|
129
|
+
new Promise<void>((resolve) => {
|
|
130
|
+
resolveRefresh = resolve;
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const { unmount } = renderHook(() =>
|
|
135
|
+
useAssemblyStatusWithPolling(noopGetStatus, noopRefresh, "asm_overlap", {
|
|
136
|
+
pollIntervalMs: 1000,
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
await act(async () => {
|
|
141
|
+
await Promise.resolve();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
expect(refreshMock).toHaveBeenCalledTimes(1);
|
|
145
|
+
|
|
146
|
+
await act(async () => {
|
|
147
|
+
vi.advanceTimersByTime(3000);
|
|
148
|
+
await Promise.resolve();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
expect(refreshMock).toHaveBeenCalledTimes(1);
|
|
152
|
+
|
|
153
|
+
await act(async () => {
|
|
154
|
+
resolveRefresh?.();
|
|
155
|
+
await Promise.resolve();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
await act(async () => {
|
|
159
|
+
vi.advanceTimersByTime(1000);
|
|
160
|
+
await Promise.resolve();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
expect(refreshMock).toHaveBeenCalledTimes(2);
|
|
164
|
+
|
|
165
|
+
unmount();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("useTransloaditUpload", () => {
|
|
170
|
+
afterEach(() => {
|
|
171
|
+
actionMock.mockClear();
|
|
172
|
+
queryMock.mockClear();
|
|
173
|
+
currentResults = null;
|
|
174
|
+
currentStatus = null;
|
|
175
|
+
queryHandler = () => currentStatus;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("uploads files and exposes status/results", async () => {
|
|
179
|
+
const createAssembly = vi.fn(async () => ({
|
|
180
|
+
assemblyId: "asm_123",
|
|
181
|
+
data: {
|
|
182
|
+
tus_url: "https://tus.example.com",
|
|
183
|
+
assembly_ssl_url: "https://api2.transloadit.com/assemblies/asm_123",
|
|
184
|
+
},
|
|
185
|
+
}));
|
|
186
|
+
|
|
187
|
+
const getStatus = {} as GetAssemblyStatusFn;
|
|
188
|
+
const listResults = {} as ListResultsFn;
|
|
189
|
+
const refreshAssembly = refreshMock as unknown as RefreshAssemblyFn;
|
|
190
|
+
currentStatus = { raw: { ok: "ASSEMBLY_UPLOADING" } };
|
|
191
|
+
currentResults = [{ stepName: "resize", raw: { ssl_url: "https://file" } }];
|
|
192
|
+
queryHandler = (fn) => {
|
|
193
|
+
if (fn === getStatus) return currentStatus;
|
|
194
|
+
if (fn === listResults) return currentResults;
|
|
195
|
+
return null;
|
|
196
|
+
};
|
|
197
|
+
queryMock.mockImplementation((fn, args) => queryHandler(fn, args));
|
|
198
|
+
|
|
199
|
+
const { result } = renderHook(() =>
|
|
200
|
+
useTransloaditUpload({
|
|
201
|
+
createAssembly: createAssembly as unknown as CreateAssemblyFn,
|
|
202
|
+
getStatus,
|
|
203
|
+
listResults,
|
|
204
|
+
refreshAssembly,
|
|
205
|
+
}),
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const file = new File(["hello"], "hello.txt", { type: "text/plain" });
|
|
209
|
+
|
|
210
|
+
await act(async () => {
|
|
211
|
+
await result.current.upload([file], {
|
|
212
|
+
steps: { resize: { robot: "/image/resize" } },
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
expect(createAssembly).toHaveBeenCalled();
|
|
217
|
+
expect(result.current.assemblyId).toBe("asm_123");
|
|
218
|
+
expect(result.current.results).toEqual(currentResults);
|
|
219
|
+
expect(result.current.status?.ok).toBe("ASSEMBLY_UPLOADING");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("reset clears upload state", async () => {
|
|
223
|
+
const createAssembly = vi.fn(async () => ({
|
|
224
|
+
assemblyId: "asm_reset",
|
|
225
|
+
data: {
|
|
226
|
+
tus_url: "https://tus.example.com",
|
|
227
|
+
assembly_ssl_url: "https://api2.transloadit.com/assemblies/asm_reset",
|
|
228
|
+
},
|
|
229
|
+
}));
|
|
230
|
+
|
|
231
|
+
const getStatus = {} as GetAssemblyStatusFn;
|
|
232
|
+
const listResults = {} as ListResultsFn;
|
|
233
|
+
const refreshAssembly = refreshMock as unknown as RefreshAssemblyFn;
|
|
234
|
+
currentStatus = { raw: { ok: "ASSEMBLY_UPLOADING" } };
|
|
235
|
+
currentResults = [{ stepName: "resize", raw: { ssl_url: "https://file" } }];
|
|
236
|
+
queryHandler = (fn) => {
|
|
237
|
+
if (fn === getStatus) return currentStatus;
|
|
238
|
+
if (fn === listResults) return currentResults;
|
|
239
|
+
return null;
|
|
240
|
+
};
|
|
241
|
+
queryMock.mockImplementation((fn, args) => queryHandler(fn, args));
|
|
242
|
+
|
|
243
|
+
const { result } = renderHook(() =>
|
|
244
|
+
useTransloaditUpload({
|
|
245
|
+
createAssembly: createAssembly as unknown as CreateAssemblyFn,
|
|
246
|
+
getStatus,
|
|
247
|
+
listResults,
|
|
248
|
+
refreshAssembly,
|
|
249
|
+
}),
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
const file = new File(["hello"], "hello.txt", { type: "text/plain" });
|
|
253
|
+
|
|
254
|
+
await act(async () => {
|
|
255
|
+
await result.current.upload([file], {
|
|
256
|
+
steps: { resize: { robot: "/image/resize" } },
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
expect(result.current.assemblyId).toBe("asm_reset");
|
|
261
|
+
|
|
262
|
+
act(() => {
|
|
263
|
+
result.current.reset();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(result.current.assemblyId).toBeNull();
|
|
267
|
+
expect(result.current.error).toBeNull();
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe("useTransloaditUppy", () => {
|
|
272
|
+
afterEach(() => {
|
|
273
|
+
actionMock.mockClear();
|
|
274
|
+
queryMock.mockClear();
|
|
275
|
+
currentResults = null;
|
|
276
|
+
currentStatus = null;
|
|
277
|
+
queryHandler = () => currentStatus;
|
|
278
|
+
queryMock.mockImplementation((fn, args) => queryHandler(fn, args));
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("uploads via uppy and exposes status/results", async () => {
|
|
282
|
+
const createAssembly = vi.fn(async () => ({
|
|
283
|
+
assemblyId: "asm_uppy",
|
|
284
|
+
data: {
|
|
285
|
+
tus_url: "https://tus.example.com",
|
|
286
|
+
assembly_ssl_url: "https://api2.transloadit.com/assemblies/asm_uppy",
|
|
287
|
+
},
|
|
288
|
+
}));
|
|
289
|
+
|
|
290
|
+
const uppy = {
|
|
291
|
+
getFiles: () => [
|
|
292
|
+
{
|
|
293
|
+
id: "file-1",
|
|
294
|
+
data: new File(["hello"], "hello.jpg", { type: "image/jpeg" }),
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
setFileMeta: vi.fn(),
|
|
298
|
+
setFileState: vi.fn(),
|
|
299
|
+
getPlugin: vi.fn(() => ({ setOptions: vi.fn() })),
|
|
300
|
+
upload: vi.fn(async () => ({ successful: [{ id: "file-1" }] })),
|
|
301
|
+
} as unknown as UppyLike;
|
|
302
|
+
|
|
303
|
+
const getStatus = {} as GetAssemblyStatusFn;
|
|
304
|
+
const listResults = {} as ListResultsFn;
|
|
305
|
+
const refreshAssembly = refreshMock as unknown as RefreshAssemblyFn;
|
|
306
|
+
currentStatus = { raw: { ok: "ASSEMBLY_UPLOADING" } };
|
|
307
|
+
currentResults = [{ stepName: "resize", raw: { ssl_url: "https://file" } }];
|
|
308
|
+
queryHandler = (fn) => {
|
|
309
|
+
if (fn === getStatus) return currentStatus;
|
|
310
|
+
if (fn === listResults) return currentResults;
|
|
311
|
+
return null;
|
|
312
|
+
};
|
|
313
|
+
queryMock.mockImplementation((fn, args) => queryHandler(fn, args));
|
|
314
|
+
|
|
315
|
+
const { result } = renderHook(() =>
|
|
316
|
+
useTransloaditUppy({
|
|
317
|
+
uppy,
|
|
318
|
+
createAssembly: createAssembly as unknown as FunctionReference<
|
|
319
|
+
"action",
|
|
320
|
+
"public",
|
|
321
|
+
{ fileCount: number },
|
|
322
|
+
{ assemblyId: string; data: Record<string, unknown> }
|
|
323
|
+
>,
|
|
324
|
+
getStatus,
|
|
325
|
+
listResults,
|
|
326
|
+
refreshAssembly,
|
|
327
|
+
}),
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
await act(async () => {
|
|
331
|
+
await result.current.startUpload();
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
expect(createAssembly).toHaveBeenCalled();
|
|
335
|
+
expect(uppy.upload).toHaveBeenCalled();
|
|
336
|
+
expect(result.current.assemblyId).toBe("asm_uppy");
|
|
337
|
+
expect(result.current.results).toEqual(currentResults);
|
|
338
|
+
expect(result.current.status?.ok).toBe("ASSEMBLY_UPLOADING");
|
|
339
|
+
});
|
|
340
|
+
});
|