import { describe, expect, it } from "vitest"; import { CanvasShell, ControlsPanel, defineToolcraft, fireEvent, render, renderControlsPanelWithSchema, screen, StateProbe, ToolcraftRoot, waitFor, } from "../testing/controls-panel-test-utils"; describe("ControlsPanel media upload image attachments", () => { it("renders single-layer file uploads as a removable preview", () => { const schema = defineToolcraft({ canvas: { enabled: true, upload: true }, panels: { controls: { sections: [ { controls: { source: { accept: "PNG, SVG", label: "Image", target: "input.source", type: "fileDrop", }, }, layout: "standalone", title: "Input", }, ], title: "Generation Controls", }, }, }); const { container } = renderControlsPanelWithSchema(schema, undefined, { layers: [ { id: "layer-1", kind: "layer", name: "material", visible: true, }, ], mediaAssets: [ { dataUrl: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='64' viewBox='0 0 96 64'%3E%3Crect width='96' height='64' fill='%23777'/%3E%3C/svg%3E", fileName: "material.svg", id: "media-1", layerId: "layer-1", mimeType: "image/svg+xml", position: { x: 0, y: 0 }, size: { height: 64, unit: "px", width: 96 }, }, ], selectedLayerId: "layer-1", }); expect(screen.getByRole("img", { name: "material.svg" })).toBeTruthy(); const transformActions = container.querySelector( '[data-slot="actions-control"][aria-label="Image transforms"]', ); const transformActionButtons = transformActions?.querySelector( '[data-slot="actions-control-buttons"]', ); expect(transformActions).toBeTruthy(); expect(transformActions?.parentElement?.style.gap).toBe("6px"); expect(transformActionButtons?.className).toContain("grid-cols-3"); expect(transformActionButtons?.getAttribute("data-actions-columns")).toBe("3"); expect(screen.queryByRole("button", { name: "90° Left" })).toBeNull(); for (const [name, visibleLabel] of [ ["90° Right", "90°"], ["Flip horizontal", "Flip H"], ["Flip vertical", "Flip V"], ] as const) { const button = screen.getByRole("button", { name }); expect(button).toBeTruthy(); expect(button.textContent).toContain(visibleLabel); } expect( screen.queryByText("Click to upload an image"), ).toBeNull(); const initialPreviewFrameStyle = container .querySelector('[data-slot="file-upload-preview-frame"]') ?.getAttribute("style"); expect(initialPreviewFrameStyle).toBe( "aspect-ratio: 96 / 64; max-height: 196px; width: 100%;", ); expect(screen.getByRole("img", { name: "material.svg" }).getAttribute("style")).toBe( "transform: translate(-50%, -50%);", ); fireEvent.click(screen.getByRole("button", { name: "90° Right" })); expect(screen.getByTestId("media-transforms").textContent).toBe( '[{"rotationDeg":90}]', ); expect( container.querySelector('[data-slot="file-upload-preview-frame"]')?.getAttribute("style"), ).toBe(initialPreviewFrameStyle); expect(screen.getByRole("img", { name: "material.svg" }).getAttribute("style")).toBe( "transform: translate(-50%, -50%) rotate(90deg); width: 66.6667%;", ); fireEvent.click(screen.getByRole("button", { name: "Flip horizontal" })); expect(screen.getByTestId("media-transforms").textContent).toBe( '[{"rotationDeg":90,"flipHorizontal":true}]', ); expect(screen.getByRole("img", { name: "material.svg" }).getAttribute("style")).toBe( "transform: translate(-50%, -50%) rotate(90deg) scale(-1, 1); width: 66.6667%;", ); fireEvent.click(screen.getByRole("button", { name: "Flip vertical" })); expect(screen.getByTestId("media-transforms").textContent).toBe( '[{"rotationDeg":90,"flipHorizontal":true,"flipVertical":true}]', ); fireEvent.click(screen.getByRole("button", { name: "Remove material.svg" })); expect(screen.getByTestId("media-count").textContent).toBe("0"); expect(screen.queryByRole("img", { name: "material.svg" })).toBeNull(); expect(screen.getByText("Click to upload an image")).toBeTruthy(); }); it("renders schema default media as a removable fileDrop attachment", () => { const schema = defineToolcraft({ canvas: { enabled: true, upload: true }, media: { defaultAssets: [ { dataUrl: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='64' viewBox='0 0 96 64'%3E%3Crect width='96' height='64' fill='%23777'/%3E%3C/svg%3E", fileName: "default-background.svg", id: "default-background", layerId: "default-background-layer", mimeType: "image/svg+xml", size: { height: 64, unit: "px", width: 96 }, sourceTarget: "input.source", }, ], }, panels: { controls: { sections: [ { controls: { source: { accept: "PNG, SVG", label: "Image", target: "input.source", type: "fileDrop", }, }, layout: "standalone", title: "Input", }, ], title: "Generation Controls", }, }, }); renderControlsPanelWithSchema(schema); expect(screen.getByRole("img", { name: "default-background.svg" })).toBeTruthy(); expect(screen.getByTestId("media-ids").textContent).toBe("default-background"); fireEvent.click( screen.getByRole("button", { name: "Remove default-background.svg" }), ); expect(screen.getByTestId("media-count").textContent).toBe("0"); expect(screen.queryByRole("img", { name: "default-background.svg" })).toBeNull(); expect(screen.getByText("Click to upload an image")).toBeTruthy(); fireEvent.click(screen.getByRole("button", { name: "Reset Input section" })); expect(screen.getByRole("img", { name: "default-background.svg" })).toBeTruthy(); expect(screen.getByTestId("media-count").textContent).toBe("1"); fireEvent.click( screen.getByRole("button", { name: "Remove default-background.svg" }), ); fireEvent.click(screen.getByRole("button", { name: "Reset controls" })); expect(screen.getByRole("img", { name: "default-background.svg" })).toBeTruthy(); expect(screen.getByTestId("media-count").textContent).toBe("1"); }); it("keeps the current editable canvas size when uploading an image through fileDrop", async () => { const schema = defineToolcraft({ canvas: { enabled: true, size: { height: 720, unit: "px", width: 1280 }, upload: true, }, panels: { controls: { sections: [ { controls: { source: { label: "Image", target: "input.source", type: "fileDrop", }, }, layout: "standalone", title: "Input", }, ], title: "Generation Controls", }, }, }); const { container } = renderControlsPanelWithSchema(schema); const input = container.querySelector('input[type="file"]'); expect(input).toBeTruthy(); expect(screen.getByTestId("canvas-size").textContent).toBe("1280,720"); fireEvent.change(input as HTMLInputElement, { target: { files: [new File(["image"], "wide.png", { type: "image/png" })], }, }); await waitFor(() => { expect(screen.getByTestId("media-count").textContent).toBe("1"); }); expect(screen.getByTestId("canvas-size").textContent).toBe("1280,720"); expect(screen.getByTestId("media-sizes").textContent).toBe("1280x720"); }); it("removes single-layer file uploads from the canvas and returns the file control to empty", () => { const schema = defineToolcraft({ canvas: { enabled: true, upload: true }, panels: { controls: { sections: [ { controls: { source: { accept: "PNG, SVG", label: "Image", target: "input.source", type: "fileDrop", }, }, layout: "standalone", title: "Input", }, ], title: "Generation Controls", }, }, }); render(
, ); expect(screen.getByRole("button", { name: "Select material.svg" })).toBeTruthy(); expect(screen.getAllByRole("img", { name: "material.svg" })).toHaveLength(2); fireEvent.click(screen.getByRole("button", { name: "Remove material.svg" })); expect(screen.queryByRole("button", { name: "Select material.svg" })).toBeNull(); expect(screen.queryAllByRole("img", { name: "material.svg" })).toHaveLength(0); expect(screen.getByText("Click to upload an image")).toBeTruthy(); expect(screen.getByTestId("media-count").textContent).toBe("0"); }); it("keeps file upload deletion in the layers panel for multi-layer apps", () => { const schema = defineToolcraft({ canvas: { enabled: true, upload: true }, panels: { controls: { sections: [ { controls: { source: { accept: "PNG, SVG", label: "Image", target: "input.source", type: "fileDrop", }, }, layout: "standalone", title: "Input", }, ], title: "Generation Controls", }, layers: true, }, }); renderControlsPanelWithSchema(schema, undefined, { layers: [ { id: "layer-1", kind: "layer", name: "material", visible: true, }, ], mediaAssets: [ { dataUrl: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='64' viewBox='0 0 96 64'%3E%3Crect width='96' height='64' fill='%23777'/%3E%3C/svg%3E", fileName: "material.svg", id: "media-1", layerId: "layer-1", mimeType: "image/svg+xml", position: { x: 0, y: 0 }, size: { height: 64, unit: "px", width: 96 }, }, ], selectedLayerId: "layer-1", }); expect(screen.queryByRole("img", { name: "material.svg" })).toBeNull(); expect( screen.queryByRole("button", { name: "Remove material.svg" }), ).toBeNull(); expect(screen.getByText("Click to upload an image")).toBeTruthy(); }); });