67 lines
2.8 KiB
TypeScript
67 lines
2.8 KiB
TypeScript
// @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]]);
|
|
});
|
|
});
|