From 0d1f13556f52e71ce465b214fbbb4734d1200b09 Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2026 03:03:35 +0200 Subject: [PATCH] test(terminal): reproduce swallowed image-only paste Retain the failing browser clipboard reproduction for #3725 without changing terminal behavior. Text paste remains a passing control. Co-authored-by: Gerard Balaoro --- .../terminal-image-paste.browser.test.ts | 55 +++++++++++++++++++ packages/app/vitest.clipboard-repro.config.ts | 28 ++++++++++ 2 files changed, 83 insertions(+) create mode 100644 packages/app/src/terminal/runtime/terminal-image-paste.browser.test.ts create mode 100644 packages/app/vitest.clipboard-repro.config.ts diff --git a/packages/app/src/terminal/runtime/terminal-image-paste.browser.test.ts b/packages/app/src/terminal/runtime/terminal-image-paste.browser.test.ts new file mode 100644 index 0000000000..bce92783f7 --- /dev/null +++ b/packages/app/src/terminal/runtime/terminal-image-paste.browser.test.ts @@ -0,0 +1,55 @@ +import { page, userEvent } from "@vitest/browser/context"; +import { expect, test } from "vitest"; +import { TerminalEmulatorRuntime } from "./terminal-emulator-runtime"; + +function mountTerminal() { + const inputs: string[] = []; + const root = document.createElement("div"); + root.style.cssText = "width:720px;height:360px"; + const host = document.createElement("div"); + host.style.cssText = "width:100%;height:100%"; + root.append(host); + document.body.append(root); + const runtime = new TerminalEmulatorRuntime({ isMac: false }); + runtime.setCallbacks({ callbacks: { onInput: (data) => inputs.push(data) } }); + runtime.mount({ root, host, initialSnapshot: null, scrollback: 1000, theme: {} }); + return { + inputs, + dispose: () => { + runtime.unmount(); + root.remove(); + }, + }; +} + +test("image-only Ctrl+V reaches a local TUI's native clipboard handler", async () => { + const terminal = mountTerminal(); + try { + await userEvent.click(page.getByRole("textbox", { name: "Terminal input" })); + const canvas = document.createElement("canvas"); + canvas.width = canvas.height = 8; + canvas.getContext("2d")!.fillRect(0, 0, 8, 8); + const png = await new Promise((resolve) => canvas.toBlob((blob) => resolve(blob!))); + await navigator.clipboard.write([new ClipboardItem({ "image/png": png })]); + expect(await navigator.clipboard.readText()).toBe(""); + expect((await navigator.clipboard.read())[0]!.types).toContain("image/png"); + + await userEvent.keyboard("{Control>}v{/Control}"); + + await expect.poll(() => terminal.inputs).toContain("\x16"); + } finally { + terminal.dispose(); + } +}); + +test("Ctrl+V continues to paste clipboard text", async () => { + const terminal = mountTerminal(); + try { + await userEvent.click(page.getByRole("textbox", { name: "Terminal input" })); + await navigator.clipboard.writeText("clipboard text control"); + await userEvent.keyboard("{Control>}v{/Control}"); + await expect.poll(() => terminal.inputs).toEqual(["clipboard text control"]); + } finally { + terminal.dispose(); + } +}); diff --git a/packages/app/vitest.clipboard-repro.config.ts b/packages/app/vitest.clipboard-repro.config.ts new file mode 100644 index 0000000000..f7e44a0204 --- /dev/null +++ b/packages/app/vitest.clipboard-repro.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from "vitest/config"; +import { playwright } from "@vitest/browser-playwright"; +import base from "./vitest.config"; + +// Run only the intentionally failing #3725 reproduction with clipboard access. +export default defineConfig({ + ...base, + test: { + ...base.test, + projects: [ + { + extends: true, + test: { + name: "clipboard-repro", + include: ["src/terminal/runtime/terminal-image-paste.browser.test.ts"], + browser: { + enabled: true, + headless: true, + provider: playwright({ + contextOptions: { permissions: ["clipboard-read", "clipboard-write"] }, + }), + instances: [{ browser: "chromium" }], + }, + }, + }, + ], + }, +});