Файли
openidea/src/features/terminal/embedded.test.ts
T
l0ng-ai d837cb1acb fix(console): Ctrl+C copies selected output in consoles and terminals (#381)
On Windows and Linux, Ctrl+C in the Run/Debug consoles and other DOM output views did nothing because no handler copied the selection. Each output view (Run and Debug consoles, Build output and message detail, Git console, database output, Docker logs) now copies its own selection on Ctrl+C / Ctrl+Insert (Cmd+C on macOS) through a shared helper, leaving views with their own copy handling (database grid, Problems) untouched. In terminals, Ctrl+C copies a selection on Windows and Linux and otherwise interrupts; on macOS it always interrupts. The console menu shows the host's copy shortcut.

Not verified on a real Windows machine.

Fixes #372
2026-10-30 17:53:31 +08:00

104 рядки
3.5 KiB
TypeScript

import { afterEach, beforeEach, expect, it, vi } from "vitest";
import { createEmbeddedTerminal } from "./embedded";
import { installTerminalKeys } from "./keys";
import { installKeyboardDispatcher, useKeymap } from "@/platform/keymap";
import { registerAction } from "@/platform/actions";
import { usePlatform } from "@/platform/platformInfo";
const terminal = vi.hoisted(() => ({
selection: "selected run output",
handler: (_e: KeyboardEvent): boolean => true,
}));
vi.mock("@xterm/xterm", () => ({
Terminal: class {
cols = 80;
rows = 24;
options = {};
hasSelection() {
return !!terminal.selection;
}
getSelection() {
return terminal.selection;
}
attachCustomKeyEventHandler(handler: typeof terminal.handler) {
terminal.handler = handler;
}
loadAddon() {}
open() {}
dispose() {}
onData() {
return { dispose() {} };
}
onBinary() {
return { dispose() {} };
}
onResize() {
return { dispose() {} };
}
registerLinkProvider() {
return { dispose() {} };
}
},
}));
vi.mock("@xterm/addon-fit", () => ({ FitAddon: class {} }));
vi.mock("./session", () => ({ fontFor: () => ({}), LINK_HANDLER: {}, openExternal: vi.fn(), openFileLink: vi.fn() }));
vi.mock("./theme", () => ({ readTerminalTheme: () => ({}), remeasureWhenFontsLoad: vi.fn(), MIN_CONTRAST_RATIO: 1 }));
vi.mock("./fit", () => ({
fitScheduler: () => ({ schedule() {}, cancel() {} }),
settledResize: () => ({ resized() {}, cancel() {} }),
}));
beforeEach(() => usePlatform.setState({ os: "windows" }));
afterEach(() => usePlatform.setState({ os: "macos" }));
afterEach(() => {
document.body.replaceChildren();
vi.restoreAllMocks();
});
it("copies embedded Run terminal selections before xterm can send Ctrl+C", () => {
const writeText = vi.fn().mockResolvedValue(undefined);
Object.defineProperty(navigator, "clipboard", { configurable: true, value: { writeText } });
const host = document.createElement("div");
host.className = "xterm";
document.body.append(host);
const term = createEmbeddedTerminal(host, { onData: vi.fn(), onResize: vi.fn() });
const globalCopy = vi.fn();
const disposeAction = registerAction({ id: "edit.copy", title: "Copy", run: globalCopy });
useKeymap.setState({ preset: "intellij-windows", overrides: {} });
const disposeKeys = installTerminalKeys();
const disposeDispatcher = installKeyboardDispatcher();
const shell = vi.fn();
host.addEventListener("keydown", (event) => {
if (terminal.handler(event)) shell();
});
try {
terminal.selection = "selected run output";
for (const modifiers of [{ ctrlKey: true }, { ctrlKey: true, shiftKey: true }, { metaKey: true }]) {
host.dispatchEvent(
new KeyboardEvent("keydown", {
key: "c",
code: "KeyC",
bubbles: true,
cancelable: true,
...modifiers,
}),
);
}
expect(writeText).toHaveBeenCalledTimes(3);
expect(writeText).toHaveBeenCalledWith("selected run output");
expect(shell).not.toHaveBeenCalled();
expect(globalCopy).not.toHaveBeenCalled();
terminal.handler(new KeyboardEvent("keyup", { key: "c", code: "KeyC", ctrlKey: true }));
expect(writeText).toHaveBeenCalledTimes(3);
terminal.selection = "";
host.dispatchEvent(new KeyboardEvent("keydown", { key: "c", code: "KeyC", ctrlKey: true, bubbles: true }));
expect(shell).toHaveBeenCalledOnce();
expect(globalCopy).not.toHaveBeenCalled();
} finally {
disposeDispatcher();
disposeKeys();
disposeAction();
term.dispose();
}
});