3e237e177a
* feat: add code-walkthrough backend (MCP tool, service, persistence, WS events) * feat: render agent code walkthroughs as line-anchored cards + editor window * test: add code-walkthrough re-emit mock-agent scenario * fix: rework code-walkthrough UX (floating card, line reveal, minimize, clickable file) * fix: improve code walkthrough editor UX * fix: backfill walkthrough launcher after websocket connect * fix: improve walkthrough requests and editor anchors * fix: clear walkthrough highlights on close * feat: add walkthrough discard and prompt customization * fix: send walkthrough requests through saved prompt * fix: support postgres built-in prompt seeding * fix: address walkthrough review feedback * fix: address walkthrough review follow-ups * fix: harden walkthrough lifecycle races * fix: reuse walkthrough notification cleanup * fix: guard stale walkthrough file opens * fix: abort stale file opens before paint * fix: stabilize github settings e2e readiness * fix: use stable github settings e2e selector
101 lines
3.1 KiB
TypeScript
101 lines
3.1 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import {
|
|
computeWalkthroughConnectorPath,
|
|
getWalkthroughEditorAnchor,
|
|
isWalkthroughAnchorTargetVisible,
|
|
setWalkthroughEditorAnchor,
|
|
subscribeWalkthroughEditorAnchor,
|
|
type WalkthroughViewportRect,
|
|
} from "./walkthrough-editor-anchor";
|
|
|
|
function rect(overrides: Partial<WalkthroughViewportRect>): WalkthroughViewportRect {
|
|
const left = overrides.left ?? 0;
|
|
const top = overrides.top ?? 0;
|
|
const width = overrides.width ?? 100;
|
|
const height = overrides.height ?? 40;
|
|
return {
|
|
left,
|
|
top,
|
|
width,
|
|
height,
|
|
right: overrides.right ?? left + width,
|
|
bottom: overrides.bottom ?? top + height,
|
|
};
|
|
}
|
|
|
|
describe("computeWalkthroughConnectorPath", () => {
|
|
it("draws from the nearest card side to the walkthrough range", () => {
|
|
const path = computeWalkthroughConnectorPath(
|
|
rect({ left: 700, top: 80, width: 300, height: 220 }),
|
|
rect({ left: 100, top: 180, width: 500, height: 36 }),
|
|
);
|
|
|
|
expect(path).toMatch(/^M 700\.0 198\.0 C /);
|
|
expect(path).toContain("600.0 198.0");
|
|
});
|
|
|
|
it("returns null for empty geometry", () => {
|
|
expect(computeWalkthroughConnectorPath(rect({ width: 0 }), rect({}))).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("isWalkthroughAnchorTargetVisible", () => {
|
|
it("returns true when the anchor point resolves inside the editor container", () => {
|
|
const container = document.createElement("div");
|
|
const child = document.createElement("span");
|
|
container.appendChild(child);
|
|
document.body.appendChild(container);
|
|
Object.defineProperty(container, "getBoundingClientRect", {
|
|
value: () => rect({ left: 0, top: 0, width: 300, height: 300 }),
|
|
});
|
|
|
|
expect(
|
|
isWalkthroughAnchorTargetVisible(container, rect({ left: 20, top: 20 }), () => child),
|
|
).toBe(true);
|
|
|
|
container.remove();
|
|
});
|
|
|
|
it("returns false when a hidden dock tab leaves stale coordinates over another element", () => {
|
|
const container = document.createElement("div");
|
|
const other = document.createElement("button");
|
|
document.body.append(container, other);
|
|
Object.defineProperty(container, "getBoundingClientRect", {
|
|
value: () => rect({ left: 0, top: 0, width: 300, height: 300 }),
|
|
});
|
|
|
|
expect(
|
|
isWalkthroughAnchorTargetVisible(container, rect({ left: 20, top: 20 }), () => other),
|
|
).toBe(false);
|
|
|
|
container.remove();
|
|
other.remove();
|
|
});
|
|
});
|
|
|
|
describe("setWalkthroughEditorAnchor", () => {
|
|
it("does not notify subscribers when the anchor did not change", () => {
|
|
const listener = vi.fn();
|
|
const unsubscribe = subscribeWalkthroughEditorAnchor(listener);
|
|
const anchor = {
|
|
key: "task:0:repo:file.ts",
|
|
taskId: "task",
|
|
stepIndex: 0,
|
|
file: "file.ts",
|
|
repo: "repo",
|
|
line: 1,
|
|
lineEnd: 2,
|
|
rect: rect({ left: 10, top: 20 }),
|
|
container: document.createElement("div"),
|
|
};
|
|
|
|
setWalkthroughEditorAnchor(anchor);
|
|
setWalkthroughEditorAnchor({ ...anchor, rect: { ...anchor.rect } });
|
|
|
|
expect(listener).toHaveBeenCalledTimes(1);
|
|
expect(getWalkthroughEditorAnchor()).toEqual(anchor);
|
|
setWalkthroughEditorAnchor(null);
|
|
unsubscribe();
|
|
});
|
|
});
|