160 lines
5.1 KiB
TypeScript
160 lines
5.1 KiB
TypeScript
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(<CommunityUploadScreen />);
|
|
expect(
|
|
screen.getByRole("heading", {
|
|
name: "Add a photo to identify your group",
|
|
}),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders Upload control and helper copy", () => {
|
|
render(<CommunityUploadScreen />);
|
|
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(<CommunityUploadScreen />);
|
|
|
|
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(<CommunityUploadScreen />);
|
|
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(<CommunityUploadScreen />);
|
|
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(<CommunityUploadScreen />);
|
|
await screen.findByText(copy.signInToUploadNote);
|
|
|
|
fireEvent.change(fileInput(), {
|
|
target: {
|
|
files: [
|
|
new File(
|
|
['<svg xmlns="http://www.w3.org/2000/svg"></svg>'],
|
|
"photo.png",
|
|
{ type: "image/png" },
|
|
),
|
|
],
|
|
},
|
|
});
|
|
|
|
expect(await screen.findByRole("alert")).toHaveTextContent(uploadErrors.svg);
|
|
expect(storePendingCommunityAvatarFile).not.toHaveBeenCalled();
|
|
});
|
|
});
|