feat : give error code when upload fail

This commit is contained in:
2026-10-10 03:07:47 +07:00 Unverified
parent 5e75da41c5
commit 941b252163
2 changed files with 71 additions and 2 deletions
+66
View File
@@ -0,0 +1,66 @@
// @vitest-environment happy-dom
import { act, createElement } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({ error: vi.fn(), success: vi.fn() }));
vi.mock("sonner", () => ({ toast: mocks }));
import { MediaUploader } from "./media-uploader";
let container: HTMLDivElement;
let root: Root;
const fetchUpload = vi.fn();
const onReady = vi.fn();
const onUploadingChange = vi.fn();
beforeEach(async () => {
vi.clearAllMocks();
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
vi.stubGlobal("fetch", fetchUpload);
container = document.createElement("div");
document.body.append(container);
root = createRoot(container);
await act(async () => root.render(createElement(MediaUploader, { onReady, onUploadingChange })));
});
afterEach(async () => {
await act(async () => root.unmount());
container.remove();
vi.unstubAllGlobals();
});
async function selectImage() {
const input = container.querySelector("input")!;
Object.defineProperty(input, "files", { configurable: true, value: [new File(["image"], "test.png", { type: "image/png" })] });
await act(async () => input.dispatchEvent(new Event("change", { bubbles: true })));
}
describe("media upload responses", () => {
it("reports HTTP 503 when the network replaces the API error with HTML", async () => {
fetchUpload.mockResolvedValueOnce(new Response("<!doctype html><html>503 Service Unavailable</html>", {
status: 503, headers: { "Content-Type": "text/html" },
}));
await selectImage();
expect(mocks.error).toHaveBeenCalledWith(expect.stringContaining("HTTP 503"));
expect(onReady).not.toHaveBeenCalled();
expect(onUploadingChange.mock.calls).toEqual([[true], [false]]);
expect(container.querySelector("input")?.disabled).toBe(false);
});
it("preserves JSON API error details", async () => {
fetchUpload.mockResolvedValueOnce(Response.json({ error: "uploads-busy" }, { status: 429 }));
await selectImage();
expect(mocks.error).toHaveBeenCalledWith("uploads-busy");
expect(onReady).not.toHaveBeenCalled();
expect(container.querySelector("input")?.disabled).toBe(false);
});
it("passes a successful upload to the editor", async () => {
const media = { id: "media", url: "https://cdn.test/image.png", fileName: "test.png", width: 1, height: 1 };
fetchUpload.mockResolvedValueOnce(Response.json(media, { status: 201 }));
await selectImage();
expect(fetchUpload).toHaveBeenCalledWith("/api/admin/media", expect.objectContaining({ method: "POST", body: expect.any(FormData) }));
expect(onReady).toHaveBeenCalledExactlyOnceWith(media);
expect(mocks.success).toHaveBeenCalledTimes(1);
expect(onUploadingChange.mock.calls).toEqual([[true], [false]]);
});
});
+5 -2
View File
@@ -49,8 +49,11 @@ export function MediaUploader({
method: "POST", method: "POST",
body, body,
}); });
const result = await response.json() as UploadedMedia & { error?: string }; if (!response.ok) {
if (!response.ok) throw new Error(result.error || "upload"); const error = await response.json().catch(() => null) as { error?: string } | null;
throw new Error(error?.error || `อัปโหลดไม่สำเร็จ (HTTP ${response.status}) กรุณาลองอีกครั้ง`);
}
const result = await response.json() as UploadedMedia;
onReady(result); onReady(result);
toast.success("อัปโหลดรูปภาพแล้ว"); toast.success("อัปโหลดรูปภาพแล้ว");
} catch (cause) { } catch (cause) {