brackt/app/components/ui/__tests__/SportIconUploader.test.tsx
2026-05-08 21:19:09 -07:00

101 lines
3.6 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { SportIconUploader } from "../SportIconUploader";
import type { FormEvent } from "react";
beforeEach(() => {
vi.restoreAllMocks();
});
describe("SportIconUploader", () => {
it("stores the initial icon URL in a hidden input", () => {
const { container } = render(
<SportIconUploader uploadUrl="/api/upload-sport-icon" initialIconUrl="nfl.svg" sportName="NFL" />
);
expect(container.querySelector('input[name="iconUrl"]')).toHaveValue("nfl.svg");
expect(screen.getByText("Current icon")).toBeInTheDocument();
});
it("rejects non-SVG files before upload", async () => {
const { container } = render(<SportIconUploader uploadUrl="/api/upload-sport-icon" />);
const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement;
fireEvent.change(fileInput, {
target: { files: [new File(["not svg"], "icon.png", { type: "image/png" })] },
});
expect(await screen.findByText("Choose an SVG file.")).toBeInTheDocument();
});
it("chooses an icon from an existing sport", () => {
const { container } = render(
<SportIconUploader
uploadUrl="/api/upload-sport-icon"
existingIconOptions={[
{ id: "sport-1", name: "NFL", iconUrl: "nfl.svg" },
{ id: "sport-2", name: "NBA", iconUrl: "nba.svg" },
]}
/>
);
fireEvent.change(screen.getByRole("combobox", { name: "Use an existing sport icon" }), {
target: { value: "nba.svg" },
});
expect(container.querySelector('input[name="iconUrl"]')).toHaveValue("nba.svg");
expect(screen.getByText("Current icon")).toBeInTheDocument();
});
it("uploads an SVG and updates the hidden input", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ iconUrl: "https://res.cloudinary.com/demo/image/upload/v1/sports-icons/nfl.svg" }),
})
);
const { container } = render(<SportIconUploader uploadUrl="/api/upload-sport-icon" />);
const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement;
fireEvent.change(fileInput, {
target: { files: [new File(["<svg />"], "icon.svg", { type: "image/svg+xml" })] },
});
fireEvent.click(await screen.findByRole("button", { name: "Upload SVG" }));
await waitFor(() => {
expect(container.querySelector('input[name="iconUrl"]')).toHaveValue(
"https://res.cloudinary.com/demo/image/upload/v1/sports-icons/nfl.svg"
);
});
expect(screen.getByText("Icon uploaded. Save the sport to apply it.")).toBeInTheDocument();
});
it("does not submit a parent form when uploading an SVG", async () => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ iconUrl: "https://res.cloudinary.com/demo/image/upload/v1/sports-icons/nfl.svg" }),
})
);
const onSubmit = vi.fn((event: FormEvent<HTMLFormElement>) => event.preventDefault());
const { container } = render(
<form onSubmit={onSubmit}>
<SportIconUploader uploadUrl="/api/upload-sport-icon" />
</form>
);
const fileInput = container.querySelector('input[type="file"]') as HTMLInputElement;
fireEvent.change(fileInput, {
target: { files: [new File(["<svg />"], "icon.svg", { type: "image/svg+xml" })] },
});
fireEvent.click(await screen.findByRole("button", { name: "Upload SVG" }));
await waitFor(() => expect(global.fetch).toHaveBeenCalled());
expect(onSubmit).not.toHaveBeenCalled();
});
});