mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 16:08:05 +00:00
Adds a `stubDesktop` fixture that switches the browser-mode workbench to desktop mode over a stubbed IPC layer: tests answer the commands they need, push Channel messages and IdeEvents, and assert on the commands invoked. Unstubbed commands fail as unavailable; documents keep the in-memory backend. New flows covered: - editing: modified marker and ⌘S save, ⌘R Replace All with one undo step - code intelligence: completion, ⌘B to another file, Find Usages, in-place rename, diagnostics with an ⌥⏎ quick fix - VCS: ⌘K commit with message validation and default inclusion - run: ⌃R output streaming and exit, ⌘F2 stop - debug: breakpoint, stop with frames and variables, step over, resume - search: Find in Files results opening at the match line; the Search Everywhere file case no longer needs fixme - terminal: typed command reaches the shell and its output renders - project: Close Project to Welcome, trust prompt, open a recent project
116 lines
4.7 KiB
TypeScript
116 lines
4.7 KiB
TypeScript
/**
|
|
* A debugging session: a breakpoint set in the editor reaches the adapter, the session stops there
|
|
* with its frames and variables, and stepping and resuming go back to the adapter. The adapter is
|
|
* stubbed at the IPC layer; `crates/ide-dap/tests` drives real ones.
|
|
*/
|
|
import { expect, ipcCalls, openFromTree, stubDesktop, test } from "./fixtures";
|
|
|
|
const ROOT = "/dev/acme-api";
|
|
const FILE = `${ROOT}/build.gradle.kts`;
|
|
const SESSION = 3;
|
|
|
|
test("stops at a breakpoint, shows frames and variables, steps and resumes", async ({ workbench: page }) => {
|
|
const editor = await openFromTree(page, "build.gradle.kts");
|
|
await stubDesktop(page);
|
|
await page.evaluate(
|
|
async ({ file, root, session }) => {
|
|
const config = {
|
|
id: "python:app",
|
|
name: "app",
|
|
kind: "python",
|
|
command: "python3",
|
|
args: ["app.py"],
|
|
cwd: root,
|
|
env: {},
|
|
detected: false,
|
|
isTest: false,
|
|
debugAdapter: "debugpy",
|
|
};
|
|
const h = window.__ipc.handlers;
|
|
const debug = (event: Record<string, unknown>) => window.__ipc.emit({ type: "debug", session, event });
|
|
const stopAt = (line: number) => {
|
|
h.debug_stack_trace = () => [
|
|
{ id: 11, name: "create", path: file, line, column: 0, presentationHint: null },
|
|
{ id: 12, name: "handle", path: file, line: 16, column: 0, presentationHint: null },
|
|
];
|
|
debug({ kind: "stopped", threadId: 1, reason: "breakpoint", description: null, allThreadsStopped: true });
|
|
};
|
|
h.run_configs = () => [config];
|
|
h.run_apply_settings = (args) => args.configs;
|
|
h.debug_set_breakpoints = (args) =>
|
|
(args.breakpoints as { line: number }[]).map((b, i) => ({
|
|
id: i + 1,
|
|
verified: true,
|
|
line: b.line,
|
|
message: null,
|
|
}));
|
|
h.debug_start = () => {
|
|
setTimeout(() => {
|
|
debug({ kind: "started", name: config.name, adapter: "debugpy", parent: null });
|
|
debug({ kind: "initialized" });
|
|
stopAt(20);
|
|
}, 50);
|
|
return session;
|
|
};
|
|
h.debug_threads = () => [{ id: 1, name: "MainThread" }];
|
|
h.debug_scopes = () => [{ name: "Locals", variablesReference: 100, expensive: false }];
|
|
h.debug_variables = () => [
|
|
{
|
|
name: "req",
|
|
value: "CreateOrderRequest(sku='A-1', qty=2)",
|
|
typeName: "CreateOrderRequest",
|
|
variablesReference: 0,
|
|
evaluateName: "req",
|
|
},
|
|
{ name: "qty", value: "2", typeName: "int", variablesReference: 0, evaluateName: "qty" },
|
|
];
|
|
h.debug_step = () => {
|
|
setTimeout(() => {
|
|
debug({ kind: "continued", threadId: 1, allThreadsContinued: true });
|
|
stopAt(21);
|
|
}, 50);
|
|
return null;
|
|
};
|
|
h.debug_resume = () => {
|
|
setTimeout(() => {
|
|
debug({ kind: "continued", threadId: 1, allThreadsContinued: true });
|
|
debug({ kind: "exited", code: 0 });
|
|
debug({ kind: "terminated" });
|
|
}, 50);
|
|
return null;
|
|
};
|
|
const runModule = "/src/features/run/store.ts";
|
|
const { loadConfigs, selectConfig } = await import(/* @vite-ignore */ runModule);
|
|
await loadConfigs();
|
|
selectConfig(config.id);
|
|
},
|
|
{ file: FILE, root: ROOT, session: SESSION },
|
|
);
|
|
|
|
// Breakpoint and execution lines carry CSS-module classes (`_bpLine_<hash>`).
|
|
const line = (text: string) => editor.locator(".cm-line", { hasText: text });
|
|
await line("val order: Order").click();
|
|
await page.keyboard.press("Meta+F8");
|
|
await expect(line("val order: Order")).toHaveClass(/_bpLine_/);
|
|
|
|
await page.keyboard.press("Control+d");
|
|
await expect.poll(() => ipcCalls(page, "debug_start")).toHaveLength(1);
|
|
const set = (await ipcCalls(page, "debug_set_breakpoints")).filter((a) => a.path === FILE).at(-1);
|
|
expect(set?.breakpoints).toEqual([expect.objectContaining({ line: 20 })]);
|
|
|
|
const debug = page.getByRole("region", { name: "Debug", exact: true });
|
|
await expect(debug.getByText("create", { exact: true })).toBeVisible();
|
|
await expect(debug.getByText("handle", { exact: true })).toBeVisible();
|
|
await expect(debug.getByText("CreateOrderRequest(sku='A-1', qty=2)")).toBeVisible();
|
|
await expect(line("val order: Order")).toHaveClass(/_execLine_/);
|
|
|
|
await page.keyboard.press("F8");
|
|
await expect.poll(() => ipcCalls(page, "debug_step")).toEqual([{ session: SESSION, threadId: 1, kind: "over" }]);
|
|
await expect(line("return order.toDto()")).toHaveClass(/_execLine_/);
|
|
await expect(line("val order: Order")).not.toHaveClass(/_execLine_/);
|
|
|
|
await page.keyboard.press("F9");
|
|
await expect.poll(() => ipcCalls(page, "debug_resume")).toHaveLength(1);
|
|
await expect(debug.getByText("CreateOrderRequest(sku='A-1', qty=2)")).toBeHidden();
|
|
});
|