'))).toBeNull();
+ for (const value of [
+ "data:image/png;base64,AAAA", "file:///tmp/a.png", "javascript:alert(1)",
+ "https://user:pass@images.example.test/a.png", "https://@images.example.test/a.png",
+ "images.example.test/a.png"
+ ]) expect(clipboardImageURL(asClipboard(value))).toBeNull();
+ expect(clipboardImageURL(null)).toBeNull();
+ });
+
+ it("rejects oversized URLs and embedded whitespace", () => {
+ expect(clipboardImageURL(asClipboard(`https://images.example.test/${"a".repeat(8192)}`))).toBeNull();
+ expect(clipboardImageURL(asClipboard("https://images.example.test/a\tb"))).toBeNull();
+ });
+});
diff --git a/tests/create-studio-paste.test.ts b/tests/create-studio-paste.test.ts
new file mode 100644
index 0000000..a2b3764
--- /dev/null
+++ b/tests/create-studio-paste.test.ts
@@ -0,0 +1,61 @@
+import { readFile } from "node:fs/promises";
+import { describe, expect, it } from "vitest";
+
+const createStudioUrl = new URL("../components/create-studio.tsx", import.meta.url);
+
+async function pasteFlowSource() {
+ const source = await readFile(createStudioUrl, "utf8");
+ const start = source.indexOf("function handleImagePaste(");
+ const end = source.indexOf("async function uploadTemplatePreview(", start);
+ expect(start).toBeGreaterThanOrEqual(0);
+ expect(end).toBeGreaterThan(start);
+ return { source, flow: source.slice(start, end) };
+}
+
+describe("create studio image paste wiring", () => {
+ it("handles paste in both the prompt and workspace, leaving text paste to the browser", async () => {
+ const { source, flow } = await pasteFlowSource();
+ expect(source).toContain("onPaste={handleImagePaste}");
+ expect(source).toContain("onPasteImage={handleImagePaste}");
+ expect(flow.indexOf("if (!files.length && !imageURL) return;")).toBeLessThan(flow.indexOf("event.preventDefault()"));
+ expect(flow).toContain("clipboardImageFiles(event.clipboardData)");
+ });
+
+ it("requires confirmation before downloading an entire URL and keeps canceled links as native text paste", async () => {
+ const { flow } = await pasteFlowSource();
+ expect(flow).toContain("files.length ? null : clipboardImageURL(event.clipboardData)");
+ const confirmation = flow.indexOf("if (imageURL && !window.confirm(");
+ expect(confirmation).toBeGreaterThanOrEqual(0);
+ expect(confirmation).toBeLessThan(flow.indexOf("event.preventDefault()"));
+ expect(flow).toContain('fetch("/api/assets/import-image"');
+ expect(flow).toContain('headers: { "Content-Type": "application/json" }');
+ expect(flow).toContain("receiveMaterialAssets(() => importImageURL(imageURL), options, 1)");
+ expect(flow).toContain("receiveMaterialAssets(() => uploadAssetFiles(selectedFiles), options, allFiles.length)");
+ expect(flow).toContain("new URL(imageURL).hostname");
+ expect(flow).toContain('asset.kind !== "image"');
+ expect(flow).toContain('options.importedURL && options.seedreamSourceMode === "single"');
+ });
+
+ it("blocks concurrent uploads and discards a stale result after a mode or target switch", async () => {
+ const { flow } = await pasteFlowSource();
+ expect(flow).toContain("if (uploadingRef.current || busy)");
+ expect(flow).toContain("uploadingRef.current = true");
+ expect(flow).toContain("uploadingRef.current = false");
+ expect(flow).toContain("options.paste.contextKey !== pasteContextRef.current");
+ expect(flow.indexOf("options.paste.contextKey !== pasteContextRef.current"))
+ .toBeLessThan(flow.indexOf("setMaterials(next)"));
+ });
+
+ it("routes special modes through existing source limits and adds references only for basic image or video", async () => {
+ const { flow } = await pasteFlowSource();
+ expect(flow).toContain('imageCreationMode === "interactive" ? "append" : "single"');
+ expect(flow).toContain("isSeedreamLayerInputFile(files[0])");
+ expect(flow).toContain("if (selectedSeedreamSource && !window.confirm(");
+ expect(flow).toContain("options.maxFiles = 1");
+ expect(flow).toContain("options.replaceAll = true");
+ expect(flow).toContain("options.maxFiles = remaining");
+ expect(flow).toContain("insertReferences: !specializedImageMode");
+ expect(flow).toContain('const labels = addedMaterials.map((material) => material.label || "");');
+ expect(flow).toContain("insertClipboardImageReferences(items[pasted.mode], labels, selection)");
+ });
+});