From 80aa0ca32a1c147fa331fd37d911bb47ae47e15d Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2026 07:11:03 +0200 Subject: [PATCH] test(app): exercise plugin Escape through the real composer Keep the contributor fix and replace the substitute shortcut-listener test with an installed-plugin browser regression. The first Escape must close the preview without cancelling the turn; the second must cancel it. Co-authored-by: luke Co-authored-by: RoundRedDot --- .../app/e2e/browser/plugin-popover.spec.ts | 47 +++++++++++++++++++ .../e2e/support/helpers/plugin-timeline.ts | 43 +++++++++++++++++ .../app/src/lib/overlay-root.browser.test.tsx | 33 ------------- 3 files changed, 90 insertions(+), 33 deletions(-) create mode 100644 packages/app/e2e/browser/plugin-popover.spec.ts diff --git a/packages/app/e2e/browser/plugin-popover.spec.ts b/packages/app/e2e/browser/plugin-popover.spec.ts new file mode 100644 index 0000000000..8ce3b35513 --- /dev/null +++ b/packages/app/e2e/browser/plugin-popover.spec.ts @@ -0,0 +1,47 @@ +import { expect, test, type Page } from "../support/fixtures"; +import { composerLocator, submitMessageWithButton } from "../support/helpers/composer"; +import { seedMockAgentWorkspace, openAgentRoute } from "../support/helpers/mock-agent"; +import { withPopoverTimelinePlugin } from "../support/helpers/plugin-timeline"; + +async function openPreviewWhileTyping(page: Page): Promise { + await composerLocator(page).focus(); + await page.getByRole("button", { name: "Hover plugin preview", exact: true }).hover(); + await expect(page.getByRole("dialog", { name: "Plugin preview" })).toBeVisible(); + await expect(composerLocator(page)).toBeFocused(); +} + +async function dismissPreview(page: Page): Promise { + await page.keyboard.press("Escape"); + await expect(page.getByRole("dialog", { name: "Plugin preview" })).not.toBeVisible(); +} + +test("Escape dismisses a plugin preview before interrupting the agent", async ({ page }) => { + test.setTimeout(120_000); + const agent = await seedMockAgentWorkspace({ + repoPrefix: "plugin-popover-", + title: "Plugin preview keyboard regression", + model: "e2e-fast-stream", + }); + try { + await withPopoverTimelinePlugin(async () => { + await openAgentRoute(page, agent); + await submitMessageWithButton(page, "Show the plugin preview."); + await agent.client.waitForFinish(agent.agentId, 30_000); + await expect(page.getByRole("button", { name: "Hover plugin preview" })).toBeVisible(); + await submitMessageWithButton(page, "withhold synthetic user message until interrupted"); + const stop = page.getByRole("button", { name: "Stop agent", exact: true }); + await expect(stop).toBeVisible(); + + await openPreviewWhileTyping(page); + await dismissPreview(page); + await expect(stop).toBeVisible(); + await expect(composerLocator(page)).toBeFocused(); + + await page.keyboard.press("Escape"); + await expect(stop).not.toBeVisible(); + await agent.client.waitForFinish(agent.agentId, 15_000); + }); + } finally { + await agent.cleanup(); + } +}); diff --git a/packages/app/e2e/support/helpers/plugin-timeline.ts b/packages/app/e2e/support/helpers/plugin-timeline.ts index 8ad7e0a632..8b0f680e2d 100644 --- a/packages/app/e2e/support/helpers/plugin-timeline.ts +++ b/packages/app/e2e/support/helpers/plugin-timeline.ts @@ -231,3 +231,46 @@ export async function attachPromptTimelineEvidence(page: Page, info: TestInfo): contentType: "image/png", }); } + +const POPOVER_CLIENT_SOURCE = `import React, { useEffect, useRef } from "react"; +import { z } from "zod"; + +function Preview() { + const panel = useRef(null); + useEffect(() => { + const close = (event) => { + if (event.key === "Escape" && panel.current.matches(":popover-open")) { + panel.current.hidePopover(); + } + }; + document.addEventListener("keydown", close, true); + return () => document.removeEventListener("keydown", close, true); + }, []); + return
+ +
Plugin hover preview
+
; +} + +export default function contribute(client) { + client.addTimelineTransformer({ + id: "preview", query: { itemType: "user_message" }, + transform({ item }) { + if (item.text !== "Show the plugin preview.") return; + return { items: [{ type: "plugin", kind: "preview", version: 1, data: {} }] }; + }, + }); + client.addTimelineRenderer({ + kind: "preview", version: 1, schema: z.object({}), Component: Preview, + }); + return () => {}; +}`; + +/** Installs a timeline preview that owns Escape through the DOM Popover API. */ +export async function withPopoverTimelinePlugin(run: () => Promise): Promise { + await withDirectoryPlugin({ + pluginId: "timeline-popover-e2e", + source: POPOVER_CLIENT_SOURCE, + run, + }); +} diff --git a/packages/app/src/lib/overlay-root.browser.test.tsx b/packages/app/src/lib/overlay-root.browser.test.tsx index 5737adbd54..bd20d2aeeb 100644 --- a/packages/app/src/lib/overlay-root.browser.test.tsx +++ b/packages/app/src/lib/overlay-root.browser.test.tsx @@ -95,39 +95,6 @@ describe("Escape with a plugin popover open", () => { document.body.replaceChildren(); }); - it("closes the popover first, and interrupts only on the next press", () => { - // A plugin's panel: shown with the Popover API, closed by its own Escape listener. - const panel = document.createElement("div"); - panel.setAttribute("popover", "manual"); - document.addEventListener("keydown", (event) => { - if (event.key === "Escape" && panel.matches(":popover-open")) panel.hidePopover(); - }); - const composer = document.createElement("textarea"); - document.body.append(composer, panel); - // The shortcut engine: a window capture listener that interrupts on Escape. - let interrupts = 0; - const shortcuts = (event: KeyboardEvent) => { - if (keyBelongsToDomPopover(event)) return; - if (event.key === "Escape") interrupts += 1; - }; - window.addEventListener("keydown", shortcuts, true); - try { - composer.focus(); - panel.showPopover(); - const escape = () => - composer.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true })); - - escape(); - expect(panel.matches(":popover-open")).toBe(false); - expect(interrupts).toBe(0); - - escape(); - expect(interrupts).toBe(1); - } finally { - window.removeEventListener("keydown", shortcuts, true); - } - }); - it("leaves other keys to the shortcuts while a popover is open", () => { const panel = document.createElement("div"); panel.setAttribute("popover", "manual");