Publish the unified MIT-licensed Stuffbox SDK source
Hop-State: A_06FPAF0M83E7T0JJ7EP2WQ0 Hop-Proposal: R_06FPAF04S0D4G8TA8TNK3C0 Hop-Task: T_06FPADTV2YD4PD5GMTK3150 Hop-Attempt: AT_06FPADTV2ZYKEY8GH3JTK98
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
uploadFile,
|
||||
type UploadProgress,
|
||||
type UploadTransport,
|
||||
} from "../src/react/index.js";
|
||||
|
||||
class FakeUploadTarget {
|
||||
onprogress: ((event: ProgressEvent) => void) | null = null;
|
||||
}
|
||||
|
||||
class FakeXMLHttpRequest {
|
||||
static latest: FakeXMLHttpRequest | undefined;
|
||||
readonly upload = new FakeUploadTarget();
|
||||
readonly headers: Record<string, string> = {};
|
||||
status = 0;
|
||||
method = "";
|
||||
url = "";
|
||||
body: unknown;
|
||||
onload: (() => void) | null = null;
|
||||
onerror: (() => void) | null = null;
|
||||
onabort: (() => void) | null = null;
|
||||
|
||||
constructor() {
|
||||
FakeXMLHttpRequest.latest = this;
|
||||
}
|
||||
|
||||
open(method: string, url: string): void {
|
||||
this.method = method;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
setRequestHeader(name: string, value: string): void {
|
||||
this.headers[name.toLowerCase()] = value;
|
||||
}
|
||||
|
||||
send(body: unknown): void {
|
||||
this.body = body;
|
||||
const size = (body as { size?: number }).size ?? 0;
|
||||
this.upload.onprogress?.({ loaded: size } as ProgressEvent);
|
||||
this.status = 200;
|
||||
this.onload?.();
|
||||
}
|
||||
|
||||
abort(): void {
|
||||
this.onabort?.();
|
||||
}
|
||||
}
|
||||
|
||||
describe("direct React upload helper", () => {
|
||||
const original = globalThis.XMLHttpRequest;
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.XMLHttpRequest = original;
|
||||
FakeXMLHttpRequest.latest = undefined;
|
||||
});
|
||||
|
||||
it("PUTs the file to object storage, reports progress, then completes", async () => {
|
||||
globalThis.XMLHttpRequest = FakeXMLHttpRequest as unknown as typeof XMLHttpRequest;
|
||||
const file = { name: "photo.png", type: "image/png", size: 4 } as File;
|
||||
const progress: UploadProgress[] = [];
|
||||
const transport: UploadTransport = {
|
||||
createUpload: vi.fn().mockResolvedValue({
|
||||
id: "upl_1",
|
||||
uploadUrl: "https://objects.example/signed-put",
|
||||
method: "PUT",
|
||||
requiredHeaders: { "content-type": "image/png", "if-none-match": "*" },
|
||||
expiresAt: "2026-07-15T00:10:00.000Z",
|
||||
}),
|
||||
completeUpload: vi.fn().mockResolvedValue({
|
||||
id: "ast_1",
|
||||
publicId: "file_1",
|
||||
url: "https://stuffbox.example/f/file_1",
|
||||
filename: "photo.png",
|
||||
mimeType: "image/png",
|
||||
byteSize: 4,
|
||||
status: "active",
|
||||
createdAt: "2026-07-15T00:00:00.000Z",
|
||||
}),
|
||||
};
|
||||
|
||||
const asset = await uploadFile(transport, file, {
|
||||
onProgress: (value) => progress.push(value),
|
||||
});
|
||||
|
||||
expect(asset.id).toBe("ast_1");
|
||||
expect(transport.createUpload).toHaveBeenCalledWith(
|
||||
{ filename: "photo.png", mimeType: "image/png", size: 4 },
|
||||
{},
|
||||
);
|
||||
expect(FakeXMLHttpRequest.latest).toMatchObject({
|
||||
method: "PUT",
|
||||
url: "https://objects.example/signed-put",
|
||||
body: file,
|
||||
headers: { "content-type": "image/png", "if-none-match": "*" },
|
||||
});
|
||||
expect(transport.completeUpload).toHaveBeenCalledWith("upl_1", undefined, {});
|
||||
expect(progress.map((value) => value.phase)).toEqual([
|
||||
"creating",
|
||||
"uploading",
|
||||
"uploading",
|
||||
"uploading",
|
||||
"completing",
|
||||
"complete",
|
||||
]);
|
||||
expect(progress.at(-1)?.percent).toBe(100);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user