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 <luopeihuan@gmail.com>
Co-authored-by: RoundRedDot <roundreddot@gmail.com>
This commit is contained in:
paseo-bot[bot]
2026-10-16 07:11:03 +02:00
co-authored by luke RoundRedDot
parent 199aae9dde
commit 80aa0ca32a
3 changed files with 90 additions and 33 deletions
@@ -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<void> {
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<void> {
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();
}
});
@@ -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 <div>
<button onMouseEnter={() => panel.current.showPopover()}>Hover plugin preview</button>
<div ref={panel} popover="manual" role="dialog" aria-label="Plugin preview">Plugin hover preview</div>
</div>;
}
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<void>): Promise<void> {
await withDirectoryPlugin({
pluginId: "timeline-popover-e2e",
source: POPOVER_CLIENT_SOURCE,
run,
});
}
@@ -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");