import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; import { renderWithProviders as render, screen, fireEvent, cleanup, } from "../utils/test-utils"; import userEvent from "@testing-library/user-event"; import "@testing-library/jest-dom/vitest"; import { CommunityUploadScreen } from "../../app/(app)/create/screens/upload/CommunityUploadScreen"; import { clearPendingCommunityAvatarFile, readPendingCommunityAvatarFile, storePendingCommunityAvatarFile, } from "../../lib/create/pendingCommunityAvatarUpload"; import { fetchAuthSession } from "../../lib/create/api"; import messages from "../../messages/en/index"; vi.mock("../../lib/create/pendingCommunityAvatarUpload", () => ({ readPendingCommunityAvatarFile: vi.fn().mockResolvedValue(null), storePendingCommunityAvatarFile: vi.fn().mockResolvedValue(undefined), clearPendingCommunityAvatarFile: vi.fn().mockResolvedValue(undefined), })); vi.mock("../../lib/create/api", () => ({ fetchAuthSession: vi.fn().mockResolvedValue({ user: null }), })); const pngBytes = Uint8Array.from( Buffer.from( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==", "base64", ), ); const copy = messages.create.community.communityUpload; const uploadErrors = messages.create.upload.errors; function fileInput(): HTMLInputElement { const input = document.querySelector('input[type="file"]'); expect(input).toBeInstanceOf(HTMLInputElement); return input as HTMLInputElement; } describe("CommunityUploadScreen", () => { beforeEach(() => { vi.mocked(readPendingCommunityAvatarFile).mockResolvedValue(null); vi.mocked(storePendingCommunityAvatarFile).mockResolvedValue(undefined); vi.mocked(clearPendingCommunityAvatarFile).mockResolvedValue(undefined); vi.mocked(fetchAuthSession).mockResolvedValue({ user: null }); Object.defineProperty(URL, "createObjectURL", { value: vi.fn(() => "blob:pending-avatar"), writable: true, configurable: true, }); Object.defineProperty(URL, "revokeObjectURL", { value: vi.fn(), writable: true, configurable: true, }); }); afterEach(() => { cleanup(); Reflect.deleteProperty(URL, "createObjectURL"); Reflect.deleteProperty(URL, "revokeObjectURL"); }); it("renders HeaderLockup", () => { render(); expect( screen.getByRole("heading", { name: "Add a photo to identify your group", }), ).toBeInTheDocument(); }); it("renders Upload control and helper copy", () => { render(); expect(screen.getByRole("button", { name: "Upload" })).toBeInTheDocument(); expect( screen.getByText( /This photo will be used as a profile picture for your group/i, ), ).toBeInTheDocument(); }); it("restores a pending IndexedDB photo and lets the user remove it", async () => { const user = userEvent.setup(); const pending = new File([pngBytes], "avatar.png", { type: "image/png" }); vi.mocked(readPendingCommunityAvatarFile).mockResolvedValue(pending); render(); const preview = await screen.findByRole("img", { name: copy.previewAlt }); expect(preview).toHaveAttribute("src", "blob:pending-avatar"); const remove = screen.getByRole("button", { name: copy.clearPendingUploadAriaLabel, }); await user.click(remove); expect(clearPendingCommunityAvatarFile).toHaveBeenCalled(); expect(screen.getByRole("button", { name: "Upload" })).toBeInTheDocument(); expect( screen.queryByRole("img", { name: copy.previewAlt }), ).not.toBeInTheDocument(); }); it("rejects an empty file with a clear error and does not stage it", async () => { render(); await screen.findByText(copy.signInToUploadNote); fireEvent.change(fileInput(), { target: { files: [new File([], "empty.png", { type: "image/png" })] }, }); expect(await screen.findByRole("alert")).toHaveTextContent( uploadErrors.empty, ); expect(storePendingCommunityAvatarFile).not.toHaveBeenCalled(); }); it("rejects a text file renamed as PNG", async () => { render(); await screen.findByText(copy.signInToUploadNote); fireEvent.change(fileInput(), { target: { files: [new File(["hello"], "photo.png", { type: "image/png" })], }, }); expect(await screen.findByRole("alert")).toHaveTextContent( uploadErrors.invalidType, ); expect(storePendingCommunityAvatarFile).not.toHaveBeenCalled(); }); it("rejects SVG uploads", async () => { render(); await screen.findByText(copy.signInToUploadNote); fireEvent.change(fileInput(), { target: { files: [ new File( [''], "photo.png", { type: "image/png" }, ), ], }, }); expect(await screen.findByRole("alert")).toHaveTextContent(uploadErrors.svg); expect(storePendingCommunityAvatarFile).not.toHaveBeenCalled(); }); });