Files
community-rule/tests/unit/flushPendingCommunityAvatar.test.ts
adilallo 8212423ec3 Keep a guest community logo on review and on the published rule.
Sign-in was clearing the staged file before it uploaded, and review only read the server URL, so a chosen logo disappeared after the upload step.
2026-11-05 20:02:40 -07:00

108 lines
3.7 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { CreateFlowUploadValidationError } from "../../lib/create/createFlowUploadValidation";
const readPendingCommunityAvatarFile = vi.fn();
const clearPendingCommunityAvatarFile = vi.fn();
const uploadCreateFlowFile = vi.fn();
vi.mock("../../lib/create/pendingCommunityAvatarUpload", () => ({
readPendingCommunityAvatarFile: (...args: unknown[]) =>
readPendingCommunityAvatarFile(...args),
clearPendingCommunityAvatarFile: (...args: unknown[]) =>
clearPendingCommunityAvatarFile(...args),
}));
vi.mock("../../lib/create/uploadToServer", () => ({
uploadCreateFlowFile: (...args: unknown[]) => uploadCreateFlowFile(...args),
}));
import {
flushPendingCommunityAvatar,
forgetFlushedCommunityAvatarUrl,
stateWithPendingCommunityAvatar,
} from "../../lib/create/flushPendingCommunityAvatar";
const pending = new File([new Uint8Array([1, 2, 3])], "logo.png", {
type: "image/png",
});
describe("flushPendingCommunityAvatar", () => {
beforeEach(() => {
readPendingCommunityAvatarFile.mockReset();
clearPendingCommunityAvatarFile.mockReset();
uploadCreateFlowFile.mockReset();
clearPendingCommunityAvatarFile.mockResolvedValue(undefined);
forgetFlushedCommunityAvatarUrl();
});
it("does nothing when no file is staged", async () => {
readPendingCommunityAvatarFile.mockResolvedValue(null);
await expect(flushPendingCommunityAvatar()).resolves.toEqual({
status: "none",
});
expect(uploadCreateFlowFile).not.toHaveBeenCalled();
});
it("uploads once and remembers the url after the file is cleared", async () => {
readPendingCommunityAvatarFile
.mockResolvedValueOnce(pending)
.mockResolvedValue(null);
uploadCreateFlowFile.mockResolvedValue({ url: "/api/uploads/logo" });
const [first, second] = await Promise.all([
flushPendingCommunityAvatar(),
flushPendingCommunityAvatar(),
]);
expect(first).toEqual({ status: "uploaded", url: "/api/uploads/logo" });
expect(second).toEqual(first);
expect(uploadCreateFlowFile).toHaveBeenCalledTimes(1);
expect(clearPendingCommunityAvatarFile).toHaveBeenCalledTimes(1);
await expect(flushPendingCommunityAvatar()).resolves.toEqual({
status: "uploaded",
url: "/api/uploads/logo",
});
expect(uploadCreateFlowFile).toHaveBeenCalledTimes(1);
});
it("drops an invalid file and leaves a transient failure staged", async () => {
readPendingCommunityAvatarFile.mockResolvedValue(pending);
uploadCreateFlowFile.mockRejectedValueOnce(
new CreateFlowUploadValidationError("tooLarge"),
);
await expect(flushPendingCommunityAvatar()).resolves.toEqual({
status: "invalid",
});
expect(clearPendingCommunityAvatarFile).toHaveBeenCalledTimes(1);
uploadCreateFlowFile.mockRejectedValueOnce(new Error("network"));
await expect(flushPendingCommunityAvatar()).resolves.toEqual({
status: "failed",
});
expect(clearPendingCommunityAvatarFile).toHaveBeenCalledTimes(1);
});
it("copies a successful upload onto publish state and blocks on failure", async () => {
readPendingCommunityAvatarFile.mockResolvedValue(pending);
uploadCreateFlowFile.mockResolvedValue({ url: "/api/uploads/logo" });
await expect(
stateWithPendingCommunityAvatar({ title: "Garden" }),
).resolves.toEqual({
ok: true,
state: { title: "Garden", communityAvatarUrl: "/api/uploads/logo" },
});
forgetFlushedCommunityAvatarUrl();
uploadCreateFlowFile.mockRejectedValueOnce(new Error("network"));
await expect(
stateWithPendingCommunityAvatar({
title: "Garden",
communityAvatarFileName: "logo.png",
}),
).resolves.toEqual({ ok: false, reason: "upload_failed" });
});
});