9e1ed2c9cb
Hop-State: A_06FPAF0M83E7T0JJ7EP2WQ0 Hop-Proposal: R_06FPAF04S0D4G8TA8TNK3C0 Hop-Task: T_06FPADTV2YD4PD5GMTK3150 Hop-Attempt: AT_06FPADTV2ZYKEY8GH3JTK98
110 lines
3.0 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
|