import { ImageFormModal } from "@/components/Exercises/forms/ImageModal"; import { ImageFormData } from "@/components/Exercises/models/exercise"; import { ImageStyle } from "@/components/Exercises/models/image"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; const emptyImage: ImageFormData = { url: "", file: undefined, author: "", authorUrl: "", title: "", objectUrl: "", derivativeSourceUrl: "", style: ImageStyle.PHOTO, isAi: false, }; const editingImage: ImageFormData = { ...emptyImage, url: "https://example.com/squat.jpg", title: "An existing image", }; describe("ImageFormModal", () => { beforeEach(() => { // jsdom doesn't define URL.createObjectURL; the dropzone calls it after picking a file if (!URL.createObjectURL) { URL.createObjectURL = vi.fn(() => "blob://mock"); } else { vi.spyOn(URL, "createObjectURL").mockReturnValue("blob://mock"); } }); afterEach(() => { vi.restoreAllMocks(); }); test("does not render when image is null", () => { render( ); expect(screen.queryByText("exercises.imageDetails")).not.toBeInTheDocument(); }); test("renders the dropzone placeholder when image has no URL", () => { render( ); expect(screen.getByText("exercises.dropOrClickImage")).toBeInTheDocument(); const dropzone = screen.getByTestId("image-dropzone"); expect(dropzone.querySelector("img")).toBeNull(); }); test("renders the preview image when image.url is set", () => { render( ); const dropzone = screen.getByTestId("image-dropzone"); const img = dropzone.querySelector("img"); expect(img).not.toBeNull(); expect(img!.src).toContain("https://example.com/squat.jpg"); expect(screen.queryByText("exercises.dropOrClickImage")).not.toBeInTheDocument(); }); test("submit is disabled until an image is present", async () => { const user = userEvent.setup(); render( ); const submit = screen.getByTestId("submit-edit-image-form"); expect(submit).toBeDisabled(); const fileInput = screen.getByTestId("image-dropzone-input") as HTMLInputElement; const file = new File([new Uint8Array([1, 2, 3])], "img.png", { type: "image/png" }); await user.upload(fileInput, file); expect(submit).not.toBeDisabled(); expect(screen.getByTestId("image-dropzone").querySelector("img")).not.toBeNull(); }); test("submitting the form passes the picked file, license fields and AI flag", async () => { const user = userEvent.setup(); const onSubmit = vi.fn(); render( ); // Pick a file via the dropzone's hidden input const fileInput = screen.getByTestId("image-dropzone-input") as HTMLInputElement; const file = new File([new Uint8Array([1, 2, 3])], "img.png", { type: "image/png" }); await user.upload(fileInput, file); // Toggle the AI checkbox await user.click(screen.getByTestId("image-is-ai-checkbox")); // Submit await user.click(screen.getByTestId("submit-edit-image-form")); expect(onSubmit).toHaveBeenCalledTimes(1); const submittedValues = onSubmit.mock.calls[0][0] as ImageFormData; expect(submittedValues.file).toBe(file); expect(submittedValues.url).toBe("blob://mock"); expect(submittedValues.isAi).toBe(true); }); test("clicking the backdrop calls onClose", async () => { const user = userEvent.setup(); const onClose = vi.fn(); render( ); const backdrop = document.querySelector(".MuiBackdrop-root"); expect(backdrop).not.toBeNull(); await user.click(backdrop!); expect(onClose).toHaveBeenCalled(); }); test("non-image files are ignored by the dropzone", async () => { const user = userEvent.setup(); render( ); const fileInput = screen.getByTestId("image-dropzone-input") as HTMLInputElement; const notAnImage = new File(["hello"], "notes.txt", { type: "text/plain" }); await user.upload(fileInput, notAnImage); // Dropzone still shows the placeholder, submit still disabled expect(screen.getByText("exercises.dropOrClickImage")).toBeInTheDocument(); expect(screen.getByTestId("submit-edit-image-form")).toBeDisabled(); }); });