mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 16:08:05 +00:00
Root cause: Cmd/Ctrl+A in the Run console fell through to the browser and selected the whole window, and copying only saw the rows the virtualizer had mounted. Select All now selects the console only (also in its context menu), and copying after it — keyboard, context menu or native copy — takes the whole buffer. copySelectionOnKeyDown accepts a textOf callback for other views that do not mount all their text. Refs #746 Co-authored-by: ntbang0901 <121003585+ntbang0901@users.noreply.github.com>
106 lines
4.5 KiB
TypeScript
106 lines
4.5 KiB
TypeScript
import { expect, test, type Page } from "@playwright/test";
|
|
|
|
/** Mount the real console with enough output to recycle rows while selecting. */
|
|
async function consoleOutput(page: Page, follow = false) {
|
|
await page.goto("/");
|
|
await page.evaluate(async (follow) => {
|
|
const reactPath = "/node_modules/.vite/deps/react.js";
|
|
const domPath = "/node_modules/.vite/deps/react-dom_client.js";
|
|
const viewPath = "/src/features/run/ConsoleView.tsx";
|
|
const modelPath = "/src/features/run/console.ts";
|
|
const react = await import(reactPath);
|
|
const dom = await import(domPath);
|
|
const { createElement, useState } = react.default ?? react;
|
|
const { createRoot } = dom.default ?? dom;
|
|
const { ConsoleView } = await import(viewPath);
|
|
const { ConsoleModel } = await import(modelPath);
|
|
const model = new ConsoleModel();
|
|
model.append({ kind: "stdout", text: Array.from({ length: 500 }, (_, i) => `output ${i}`).join("\n") });
|
|
const host = document.createElement("div");
|
|
host.style.cssText = "position:fixed;inset:0;z-index:99999;display:flex;height:300px;background:white";
|
|
document.body.append(host);
|
|
function Console() {
|
|
const [following, setFollowing] = useState(follow);
|
|
return createElement(ConsoleView, {
|
|
model,
|
|
version: 1,
|
|
softWrap: false,
|
|
follow: following,
|
|
onFollowChange: setFollowing,
|
|
cwd: "/",
|
|
});
|
|
}
|
|
createRoot(host).render(createElement(Console));
|
|
}, follow);
|
|
const log = page.getByRole("log", { name: "Console", exact: true });
|
|
await expect(log.locator("[data-index]").first()).toBeVisible();
|
|
return log;
|
|
}
|
|
|
|
test("keeps a console selection intact when scrolling beyond the virtual window", async ({ page }) => {
|
|
const log = await consoleOutput(page);
|
|
await log.evaluate((el) => {
|
|
el.scrollTop = 200 * 20;
|
|
});
|
|
const start = log.locator('[data-index="205"]');
|
|
await expect(start).toBeVisible();
|
|
await start.evaluate((el) => {
|
|
const selection = window.getSelection()!;
|
|
selection.setBaseAndExtent(el.firstChild!.firstChild!, 2, el.nextElementSibling!.firstChild!.firstChild!, 5);
|
|
});
|
|
await expect.poll(() => page.evaluate(() => window.getSelection()?.toString())).toBe("tput 205\noutpu");
|
|
await log.evaluate((el) => {
|
|
el.scrollTop = 350 * 20;
|
|
});
|
|
await expect(log.locator('[data-index="350"]')).toBeVisible();
|
|
await expect.poll(() => page.evaluate(() => window.getSelection()?.toString())).toBe("tput 205\noutpu");
|
|
await page.evaluate(() => window.getSelection()?.removeAllRanges());
|
|
await expect(start).toHaveCount(0);
|
|
});
|
|
|
|
test("preserves all lines in a drag selection across virtual windows", async ({ page }) => {
|
|
const log = await consoleOutput(page);
|
|
await log.evaluate((el) => {
|
|
el.scrollTop = 200 * 20;
|
|
});
|
|
const start = log.locator('[data-index="205"]');
|
|
await expect(start).toBeVisible();
|
|
const box = (await start.boundingBox())!;
|
|
await page.mouse.move(box.x + 20, box.y + 10);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 60, box.y + 30, { steps: 5 });
|
|
await log.evaluate((el) => {
|
|
el.scrollTop = 280 * 20;
|
|
});
|
|
const end = log.locator('[data-index="285"]');
|
|
await expect(end).toBeVisible();
|
|
const endBox = (await end.boundingBox())!;
|
|
await page.mouse.move(endBox.x + 60, endBox.y + 10, { steps: 5 });
|
|
await page.mouse.up();
|
|
const text = await page.evaluate(() => window.getSelection()?.toString());
|
|
expect(text).toContain("output 206\noutput 207");
|
|
expect(text).toContain("output 250\noutput 251");
|
|
expect(text).toContain("output 284");
|
|
});
|
|
|
|
test("Select All takes the whole buffer, not just the mounted rows", async ({ page }) => {
|
|
const log = await consoleOutput(page);
|
|
await page.evaluate(() => {
|
|
(window as unknown as { copied: string[] }).copied = [];
|
|
Object.defineProperty(navigator, "clipboard", {
|
|
configurable: true,
|
|
value: { writeText: async (text: string) => (window as unknown as { copied: string[] }).copied.push(text) },
|
|
});
|
|
});
|
|
await log.focus();
|
|
await page.keyboard.press("Meta+A");
|
|
// Selection stays inside the console: nothing from the workbench behind it is selected.
|
|
const selected = await page.evaluate(() => window.getSelection()?.toString() ?? "");
|
|
expect(selected).not.toContain("Project");
|
|
await page.keyboard.press("Meta+C");
|
|
const [copied] = await page.evaluate(() => (window as unknown as { copied: string[] }).copied);
|
|
expect(copied?.split("\n")).toHaveLength(500);
|
|
expect(copied).toContain("output 0\n");
|
|
expect(copied).toMatch(/output 499$/);
|
|
});
|