Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions webviews/test/agent-pane-mermaid.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,14 @@ async function show(source: string, streaming = false) {
}

/// The lines a code card in `element` shows. The highlighted card (HighlightedCode.tsx) draws them in
/// its `<diffs-container>`'s shadow root, which `textContent` leaves out, once Pierre has highlighted.
const codeLines = (element: Element | null | undefined) =>
[...(element?.querySelector("diffs-container")?.shadowRoot?.querySelectorAll("[data-line]") ?? [])].map(
/// its `<diffs-container>`'s shadow root, which `textContent` leaves out. Without a worker,
/// HighlightedCode keeps the plain card; both paths must preserve the refused source.
const codeLines = (element: Element | null | undefined) => {
const lines = element?.querySelector("diffs-container")?.shadowRoot?.querySelectorAll("[data-line]");
return [...(lines?.length ? lines : (element?.querySelectorAll(".cv-code-plain__line") ?? []))].map(
(line) => line.textContent ?? "",
);
};

const FLOW = "flowchart LR\n Start --> Check{Valid?}\n Check -->|Yes| Done";

Expand Down Expand Up @@ -130,11 +133,10 @@ test("a source both renderers refuse shows the source and why", async () => {
mermaidCalls.length = 0;
await show("```mermaid\nflowchart TD\n A --> \n```");
const diagram = host.querySelector(".cv-diagram[data-language='mermaid']");
// The source card's lines appear when Pierre's highlighter has loaded, which takes longer on a
// loaded runner.
// Wait for the refused source in either the highlighted or workerless code card.
await settleUntil(() => codeLines(diagram).length > 0, "the source card's lines");
expect(diagram?.querySelector(".cv-diagram__error")?.textContent ?? "").toContain("Parse error");
expect(codeLines(diagram).join("\n")).toContain("flowchart TD");
expect(codeLines(diagram).join("\n")).toBe("flowchart TD\n A --> ");
expect(mermaidCalls.length).toBe(1);
});

Expand Down
19 changes: 7 additions & 12 deletions webviews/test/agent-pane-pending-steer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// while the steer waits, and a click asks acpmux to stop the agent's current step with
// `_acpmux/queue_edit` `send_now` for that steer's own promptId, through the page's
// `chat.queue.edit` action. It never sends session/cancel: Send now does not end the turn.
import { afterAll, expect, jest, test } from "bun:test";
import { afterAll, expect, test } from "bun:test";
import { JSDOM, VirtualConsole } from "jsdom";
import { CHAT, connect, fakeDaemon, record } from "./support/agentPairDaemon";

Expand Down Expand Up @@ -108,21 +108,16 @@ test("Send now on a waiting steer asks queue_edit send_now for its promptId, and
expect(asked.map((request) => request.params)).toEqual([{ sessionId: CHAT, promptId: "p-steer", op: "send_now" }]);
expect(daemon.requests.some((request) => request.method === "session/cancel")).toBe(false);

// The agent read it: Send now folds away (PendingSteer.tsx), then is gone.
// This daemon does not accept Send now, so the control stays until the agent reads it.
expect(sendNowButtons()).toHaveLength(1);
// A later prompt marks this steer read. Its queue controls disappear without a closing timer.
await act(async () => {
daemon.push(record([["mux", "user_message", { promptId: "p-next", text: "Then open a PR" }]], 3));
await new Promise((resolve) => setTimeout(resolve, 0));
});
// The fold's timer is the behavior: it starts on this render, on fake timers that run it out.
jest.useFakeTimers();
try {
await render();
expect(sendNowButtons().map((button) => Boolean(button.closest(".cv-user__pending--leaving")))).toEqual([true]);
await act(() => void jest.advanceTimersByTime(1_000));
} finally {
jest.useRealTimers();
}
expect(sendNowButtons().length).toBe(0);
await render();
expect(sendNowButtons()).toHaveLength(0);
expect(message.querySelector(".cv-user__pending")).toBeNull();
} finally {
await act(async () => root.unmount());
}
Expand Down
4 changes: 2 additions & 2 deletions webviews/test/agent-pane-turn-tokens-muted.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The running turn's token count is secondary to its words (Leo, 2026-10-21: "the token font
// shouldnt be this same color"). "Working for 35s" (or "Thinking 35s") keeps the line's muted color;
// "· 679.9K tokens" after it takes the tool rows' quieter one, the theme's tertiary text
// "· 679.9K out" after it takes the tool rows' quieter one, the theme's tertiary text
// (`--cv-text-dim`, `--agent-soft`, the host's textTertiary), never a literal color, in tabular
// numerals so the count ticks in place. Both lines that say it, the Codex layout's live line at the
// bottom (Thinking.tsx) and the line over the turn with that layout off (WorkingFor.tsx), draw the
Expand All @@ -26,7 +26,7 @@ for (const [name, line] of Object.entries(tokenLines))
test(`${name}: the count is its own span, which the quieter rule keys on`, () => {
const html = tokenLineMarkup(line);
expect(html).toContain(
'<span class="cv-worked__count"><span class="cv-worked__sep" aria-hidden="true">·</span><span class="cv-worked__tokens">679.9K tokens</span></span>',
'<span class="cv-worked__count"><span class="cv-worked__sep" aria-hidden="true">·</span><span class="cv-worked__tokens">679.9K out</span></span>',
);
// The line's words come before the count's span, outside it.
expect(html.indexOf("cv-worked__count")).toBeGreaterThan(html.search(/Working for|Thinking/));
Expand Down
9 changes: 7 additions & 2 deletions webviews/test/chat-minimap.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,13 @@ test("off by default; on, but not with Full Width or a column too wide for the r
await pane.render("Done.");
await act(async () => flushFrames());
expect(pane.host.querySelector(".acpmux-chat-minimap")).toBeNull();
// The thread rail at the left is there either way.
expect(pane.host.querySelectorAll("[data-tick]").length).toBe(PROMPTS);
// The thread rail stays available independently, with a window of the full prompt list.
const ticks = pane.host.querySelectorAll("[data-tick]");
expect(ticks.length).toBeGreaterThan(0);
expect(ticks.length).toBeLessThan(PROMPTS);
const entries = pane.host.querySelectorAll(".acpmux-minimap__list [aria-setsize]");
expect(entries.length).toBe(ticks.length);
for (const entry of entries) expect(entry.getAttribute("aria-setsize")).toBe(String(PROMPTS));
await act(async () => setChatDisplay({ "agentPane.display.vscodeMinimap": true }));
await pane.render("Done.");
await act(async () => flushFrames());
Expand Down
14 changes: 12 additions & 2 deletions webviews/test/running-wave.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Running indicators side by side share one shimmer: consecutive wave indices for the running ones,
// none for finished or failed ones, one shared start for every item, and no wave under Reduce
// Motion or the motion gate. running-wave-loops.test.ts runs the stylesheet in Chromium.
import { JSDOM } from "jsdom";
import { readFileSync } from "node:fs";
import path from "node:path";
import { expect, test } from "bun:test";
Expand Down Expand Up @@ -77,15 +78,24 @@ test("the Inspector's running Subagents lines are in the wave, top to bottom; th
agent("run-new", "running", 3),
];
const html = renderToStaticMarkup(<SubagentsSection agents={agents} waiting={new Set()} onOpen={() => {}} />);
// Each line's icon (subagents/AgentLine.tsx), then the one row of the ended ones.
// Running lines keep their wave; the collapsed ended row renders still avatars.
const icons = [
...html.matchAll(/<span class="acpmux-lineage__icon([^"]*)"(?: style="([^"]*)")? data-state="(\w+)"/g),
];
expect(icons.map((match) => [match[3], match[1].trim(), match[2] ?? ""])).toEqual([
["running", WAVE_CLASS, "--wave-index:0"],
["running", WAVE_CLASS, "--wave-index:1"],
["failed", "", ""],
]);
const dom = new JSDOM(html);
try {
const ended = dom.window.document.querySelector(".acpmux-chat-inspector-subagents__ended")!;
expect(ended.getAttribute("aria-expanded")).toBe("false");
expect(ended.textContent).toContain("1 done · 1 failed");
expect(ended.querySelectorAll(".acpmux-chat-inspector-subagents__avatars > span")).toHaveLength(2);
expect(ended.querySelector(`.${WAVE_CLASS}, [style*='--wave-index']`)).toBeNull();
} finally {
dom.window.close();
}
});

test("the wave runs only with motion: Reduce Motion, the motion gate, static indicators and a hidden page stop it", () => {
Expand Down
7 changes: 6 additions & 1 deletion webviews/test/transcript-row-message.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,12 @@ test("opening it grows the message in place: the card holds the text and nothing
await act(async () => toggle().click());
expect(toggle().getAttribute("aria-expanded")).toBe("true");
const card = host.querySelector(".cv-agentmsg__card")!;
expect(card.textContent).toBe("first line\nsecond line");
// Markdown represents the source newline with a line break, not a text node.
expect(card.textContent).toBe("first linesecond line");
const breaks = card.querySelectorAll("br");
expect(breaks).toHaveLength(1);
expect(breaks[0]?.previousSibling?.textContent).toBe("first line");
expect(breaks[0]?.nextSibling?.textContent).toBe("second line");
expect(card.classList.contains("selectable")).toBe(true);
expect(toggle().getAttribute("aria-controls")).toBe(card.id);
// One scroller in the card, the message's text, capped by the stylesheet (conversation.css).
Expand Down
Loading