Files
stuffbox-sdk/tests/react-upload.test.ts
T
cyph3rasi 9e1ed2c9cb Publish the unified MIT-licensed Stuffbox SDK source
Hop-State: A_06FPAF0M83E7T0JJ7EP2WQ0
Hop-Proposal: R_06FPAF04S0D4G8TA8TNK3C0
Hop-Task: T_06FPADTV2YD4PD5GMTK3150
Hop-Attempt: AT_06FPADTV2ZYKEY8GH3JTK98
2026-07-15 03:04:24 -07:00

110 lines
3.0 KiB
TypeScript

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);
});
});