vicky469_aside/tests/streamedAgentReplyController.test.ts
2026-06-06 19:16:06 +08:00

178 lines
5.8 KiB
TypeScript

import * as assert from "node:assert/strict";
import test from "node:test";
import {
formatAgentProcessLogText,
StreamedAgentReplyController,
} from "../src/ui/views/streamedAgentReplyController";
function createFakeNode(id: string) {
return {
id,
cloneNode: () => createFakeNode(`${id}:clone`),
};
}
class FakeTextValue {
public textContent = "";
}
class FakeLabelElement extends FakeTextValue {
public className = "";
public hidden = false;
public style = { display: "" };
}
class FakeContainerElement {
public className = "";
public childNodes: unknown[] = [];
private readonly attributes = new Map<string, string>();
public replaceChildren(...nodes: unknown[]): void {
this.childNodes = nodes;
}
public setAttribute(name: string, value: string): void {
this.attributes.set(name, value);
}
public removeAttribute(name: string): void {
this.attributes.delete(name);
}
public getAttribute(name: string): string | null {
return this.attributes.get(name) ?? null;
}
}
class FakeStatusChildElement extends FakeContainerElement {
public textContent = "";
}
class FakeStatusElement extends FakeContainerElement {
public readonly ownerDocument = {
createElement: () => new FakeStatusChildElement(),
};
public readonly parentElement = null;
public appendChild(node: unknown): void {
this.childNodes.push(node);
}
}
test("streamed agent reply controller restores borrowed nodes without cloning away handlers", () => {
const controller = new StreamedAgentReplyController("thread-1") as any;
const metaValueEl = new FakeTextValue();
const labelEl = new FakeLabelElement();
const statusEl = new FakeContainerElement();
const footerMetaEl = new FakeContainerElement();
const contentEl = new FakeContainerElement();
const actionsEl = new FakeContainerElement();
const statusNode = createFakeNode("status");
const contentNode = createFakeNode("content");
const actionNode = createFakeNode("action");
const footerActionNode = createFakeNode("footer-action");
controller.ownsCard = false;
controller.metaValueEl = metaValueEl;
controller.labelEl = labelEl;
controller.statusEl = statusEl;
controller.footerMetaEl = footerMetaEl;
controller.contentEl = contentEl;
controller.actionsEl = actionsEl;
controller.borrowedSnapshot = {
metaText: "saved meta",
labelClassName: "saved-label",
labelText: "Codex",
labelHidden: false,
labelDisplay: "",
statusClassName: "saved-status",
statusNodes: [statusNode],
statusAriaLabel: "saved status",
statusTitle: null,
footerMetaClassName: "saved-footer-meta",
footerMetaNodes: [labelEl, statusEl, footerActionNode],
contentNodes: [contentNode],
actionsClassName: "saved-actions",
actionsNodes: [actionNode],
};
controller.restoreBorrowedCard();
assert.equal(metaValueEl.textContent, "saved meta");
assert.equal(labelEl.className, "saved-label");
assert.equal(labelEl.textContent, "Codex");
assert.equal(statusEl.className, "saved-status");
assert.equal(statusEl.getAttribute("aria-label"), "saved status");
assert.equal(statusEl.childNodes[0], statusNode);
assert.equal(footerMetaEl.className, "saved-footer-meta");
assert.equal(footerMetaEl.childNodes[2], footerActionNode);
assert.equal(contentEl.childNodes[0], contentNode);
assert.equal(actionsEl.className, "saved-actions");
assert.equal(actionsEl.childNodes[0], actionNode);
});
test("streamed agent reply controller hides borrowed footer actions while streaming", () => {
const controller = new StreamedAgentReplyController("thread-1") as any;
const labelEl = new FakeLabelElement();
const statusEl = new FakeContainerElement();
const footerMetaEl = new FakeContainerElement();
const addToFileNode = createFakeNode("add-to-file");
footerMetaEl.childNodes = [labelEl, statusEl, addToFileNode];
controller.ownsCard = false;
controller.labelEl = labelEl;
controller.statusEl = statusEl;
controller.footerMetaEl = footerMetaEl;
controller.syncBorrowedFooterMeta();
assert.deepEqual(footerMetaEl.childNodes, [labelEl, statusEl]);
});
test("streamed agent reply controller formats process log separately from reply text", () => {
assert.equal(
formatAgentProcessLogText({
processLogLines: [
"Reading thread context",
"Running command: rg \"Codex\" src",
],
}),
"Reading thread context\nRunning command: rg \"Codex\" src",
);
assert.equal(formatAgentProcessLogText({ processLogLines: [] }), "");
});
test("streamed agent reply controller shows only the latest process line as status hint", () => {
const controller = new StreamedAgentReplyController("thread-1") as any;
const statusEl = new FakeStatusElement();
controller.syncStatus(statusEl, "Codex", {
runId: "run-1",
threadId: "thread-1",
requestedAgent: "codex",
runtime: "direct-cli",
status: "running",
statusHintText: "Running command: rg \"Codex\" src",
processLogLines: [
"Reading thread context",
"Running command: rg \"Codex\" src",
],
partialText: "",
startedAt: 100,
updatedAt: 101,
});
const hintEl = statusEl.childNodes.find((node) =>
node instanceof FakeStatusChildElement
&& node.className === "aside-agent-run-status-hint"
) as FakeStatusChildElement | undefined;
assert.equal(
hintEl?.textContent,
"Running command: rg \"Codex\" src",
);
assert.equal(
statusEl.getAttribute("aria-label"),
"Codex Running command: rg \"Codex\" src. running",
);
});