feat(webui): polish agent output and app discovery
This commit is contained in:
@@ -340,7 +340,7 @@ describe("AgentActivityCluster", () => {
|
||||
vi.advanceTimersByTime(901);
|
||||
});
|
||||
expect(screen.queryByTestId("agent-activity-scroll")).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /1 steps/i })).toHaveAttribute(
|
||||
expect(screen.getByRole("button", { name: "Thought" })).toHaveAttribute(
|
||||
"aria-expanded",
|
||||
"false",
|
||||
);
|
||||
@@ -401,7 +401,7 @@ describe("AgentActivityCluster", () => {
|
||||
expect(screen.queryByText("Thought for 0s")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders file edit totals and a compact expanded file list", async () => {
|
||||
it("renders file edits as one-line activity rows", async () => {
|
||||
const restoreMotion = installReducedMotion();
|
||||
try {
|
||||
render(
|
||||
@@ -430,33 +430,25 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("button", { name: /edited app\.tsx/i })).toBeInTheDocument();
|
||||
expect(screen.getByTestId("activity-header-file-reference")).toHaveTextContent("app.tsx");
|
||||
expect(screen.getByTestId("activity-header-file-reference")).toHaveAttribute(
|
||||
"aria-label",
|
||||
"/Users/renxubin/project/src/app.tsx",
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: /edited app\.tsx/i }));
|
||||
|
||||
expect(screen.queryByText("Edited files")).not.toBeInTheDocument();
|
||||
const fileRef = screen.getByTestId("activity-file-reference");
|
||||
expect(fileRef).toHaveTextContent("src/app.tsx");
|
||||
expect(fileRef).toHaveAttribute("aria-label", "/Users/renxubin/project/src/app.tsx");
|
||||
expect(fileRef).toHaveAttribute("aria-label", "src/app.tsx");
|
||||
expect(screen.queryByTestId("activity-header-file-reference")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("file-edit-diff")).not.toBeInTheDocument();
|
||||
for (const diffPair of screen.getAllByTestId("activity-diff-pair")) {
|
||||
expect(diffPair).toHaveClass("items-baseline");
|
||||
expect(diffPair).toHaveClass("leading-[inherit]");
|
||||
expect(diffPair.className).not.toContain("translate-y");
|
||||
}
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText("+12").length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText("-3").length).toBeGreaterThan(0);
|
||||
});
|
||||
expect(screen.getByText("+12")).toBeInTheDocument();
|
||||
expect(screen.getByText("-3")).toBeInTheDocument();
|
||||
} finally {
|
||||
restoreMotion();
|
||||
}
|
||||
});
|
||||
|
||||
it("renders GitHub-like file edit diffs when the local preference is enabled", () => {
|
||||
it("keeps file edits flat even when the legacy diff preference is enabled", () => {
|
||||
localStorage.setItem(
|
||||
"nanobot-webui.settings-preferences",
|
||||
JSON.stringify({ fileEditDisplayMode: "diff" }),
|
||||
@@ -496,20 +488,17 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("file-edit-diff")).toBeInTheDocument();
|
||||
expect(screen.queryByText("@@ -10,2 +10,2 @@")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("return <Old />;")).toBeInTheDocument();
|
||||
expect(screen.getByText("return <New />;")).toBeInTheDocument();
|
||||
expect(screen.getAllByText("11").length).toBeGreaterThanOrEqual(2);
|
||||
expect(screen.getAllByTestId("activity-header-file-reference")).toHaveLength(1);
|
||||
expect(screen.queryByTestId("activity-file-reference")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("file-edit-diff")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("return <Old />;")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("return <New />;")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("activity-file-reference")).toHaveTextContent("src/app.tsx");
|
||||
expect(screen.getAllByTestId("activity-diff-pair")).toHaveLength(1);
|
||||
} finally {
|
||||
localStorage.removeItem("nanobot-webui.settings-preferences");
|
||||
}
|
||||
});
|
||||
|
||||
it("renders folded separators between separated file edit hunks", () => {
|
||||
it("does not render diff hunks inside the activity list", () => {
|
||||
localStorage.setItem(
|
||||
"nanobot-webui.settings-preferences",
|
||||
JSON.stringify({ fileEditDisplayMode: "diff" }),
|
||||
@@ -555,17 +544,16 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("file-edit-diff-hunk-gap")).toHaveTextContent(
|
||||
"21 unchanged lines hidden",
|
||||
);
|
||||
expect(screen.queryByTestId("file-edit-diff-hunk-gap")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("@@ -25,3 +25,3 @@")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("return newSecond;")).toBeInTheDocument();
|
||||
expect(screen.queryByText("return newSecond;")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("activity-file-reference")).toHaveTextContent("src/app.tsx");
|
||||
} finally {
|
||||
localStorage.removeItem("nanobot-webui.settings-preferences");
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps long file edit diffs collapsed until opened", () => {
|
||||
it("summarizes long file edit diffs without an expansion control", () => {
|
||||
localStorage.setItem(
|
||||
"nanobot-webui.settings-preferences",
|
||||
JSON.stringify({ fileEditDisplayMode: "diff" }),
|
||||
@@ -604,40 +592,16 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
const toggle = screen.getByTestId("file-edit-diff-toggle");
|
||||
expect(toggle).toHaveAttribute("aria-expanded", "false");
|
||||
expect(toggle).toHaveTextContent("View large diff");
|
||||
expect(toggle).toHaveTextContent("165 lines");
|
||||
expect(screen.queryByTestId("file-edit-diff-toggle")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("file-edit-diff")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("line-1")).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(toggle);
|
||||
|
||||
expect(toggle).toHaveAttribute("aria-expanded", "true");
|
||||
expect(screen.getByText("line-160")).toBeInTheDocument();
|
||||
expect(screen.queryByText("line-161")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("file-edit-diff-expand-lines")).toHaveTextContent("Show 5 more lines");
|
||||
|
||||
fireEvent.click(screen.getByTestId("file-edit-diff-expand-lines"));
|
||||
|
||||
expect(screen.getByText("line-165")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("file-edit-diff-collapse-lines")).toHaveTextContent("Show fewer lines");
|
||||
|
||||
fireEvent.click(screen.getByTestId("file-edit-diff-collapse-lines"));
|
||||
|
||||
expect(screen.queryByText("line-165")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("file-edit-diff-expand-lines")).toHaveTextContent("Show 5 more lines");
|
||||
|
||||
fireEvent.click(toggle);
|
||||
|
||||
expect(toggle).toHaveAttribute("aria-expanded", "false");
|
||||
expect(screen.queryByTestId("file-edit-diff")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("+165")).toBeInTheDocument();
|
||||
} finally {
|
||||
localStorage.removeItem("nanobot-webui.settings-preferences");
|
||||
}
|
||||
});
|
||||
|
||||
it("does not mount collapsed file edit diff rows until opened", () => {
|
||||
it("ignores the legacy collapsed diff mode in the activity list", () => {
|
||||
localStorage.setItem(
|
||||
"nanobot-webui.settings-preferences",
|
||||
JSON.stringify({ fileEditDisplayMode: "collapsed_diff" }),
|
||||
@@ -677,24 +641,16 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
const toggle = screen.getByTestId("file-edit-diff-toggle");
|
||||
expect(toggle).toHaveAttribute("aria-expanded", "false");
|
||||
expect(toggle).toHaveTextContent("View diff");
|
||||
expect(toggle).toHaveTextContent("3 lines");
|
||||
expect(screen.queryByTestId("file-edit-diff-toggle")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("file-edit-diff")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("return <New />;")).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(toggle);
|
||||
|
||||
expect(toggle).toHaveAttribute("aria-expanded", "true");
|
||||
expect(screen.getByTestId("file-edit-diff")).toBeInTheDocument();
|
||||
expect(screen.getByText("return <New />;")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("activity-file-reference")).toHaveTextContent("src/app.tsx");
|
||||
} finally {
|
||||
localStorage.removeItem("nanobot-webui.settings-preferences");
|
||||
}
|
||||
});
|
||||
|
||||
it("offers the file preview entry point when a diff payload is truncated", () => {
|
||||
it("opens the edited file directly instead of expanding a truncated diff", () => {
|
||||
localStorage.setItem(
|
||||
"nanobot-webui.settings-preferences",
|
||||
JSON.stringify({ fileEditDisplayMode: "diff" }),
|
||||
@@ -735,15 +691,9 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
const toggle = screen.getByTestId("file-edit-diff-toggle");
|
||||
expect(toggle).toHaveAttribute("aria-expanded", "false");
|
||||
expect(toggle).toHaveTextContent("View large diff");
|
||||
expect(screen.queryByTestId("file-edit-diff-toggle")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("file-edit-diff-truncated")).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.click(toggle);
|
||||
|
||||
expect(screen.getByTestId("file-edit-diff-truncated")).toHaveTextContent("Diff truncated");
|
||||
fireEvent.click(screen.getByTestId("file-edit-diff-open-file"));
|
||||
fireEvent.click(screen.getByTestId("activity-file-reference"));
|
||||
|
||||
expect(onOpenFilePreview).toHaveBeenCalledWith("/repo/src/app.tsx");
|
||||
} finally {
|
||||
@@ -778,8 +728,8 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("button", { name: /deleted angry-birds\.html/i })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: /edited angry-birds\.html/i })).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Deleted")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Edited")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders file-only edits without a redundant disclosure", () => {
|
||||
@@ -812,7 +762,8 @@ describe("AgentActivityCluster", () => {
|
||||
expect(screen.queryByRole("button", { name: /edited app\.tsx/i })).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("agent-activity-scroll")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Edited")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("activity-header-file-reference")).toHaveTextContent("app.tsx");
|
||||
expect(screen.queryByTestId("activity-header-file-reference")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("activity-file-reference")).toHaveTextContent("src/app.tsx");
|
||||
expect(screen.getByText("+12")).toBeInTheDocument();
|
||||
expect(screen.getByText("-3")).toBeInTheDocument();
|
||||
});
|
||||
@@ -879,10 +830,7 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
const cliRuns = screen.getByTestId("activity-cli-runs");
|
||||
expect(cliRuns).toHaveTextContent("Using");
|
||||
expect(cliRuns).toHaveTextContent("@blender");
|
||||
expect(cliRuns).toHaveTextContent("--json --background scene.blend");
|
||||
expect(screen.getByText("Using Blender · --json --background scene.blend")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("activity-cli-logo-blender")).toBeInTheDocument();
|
||||
expect(screen.queryByText(/run_cli_app/)).not.toBeInTheDocument();
|
||||
});
|
||||
@@ -930,9 +878,9 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
const searchRow = screen.getByText("Searching").closest("li");
|
||||
const cliRow = screen.getByText("@blender").closest("li");
|
||||
const fetchRow = screen.getByText("Reading").closest("li");
|
||||
const searchRow = screen.getByText("Searched nanobot architecture").closest('[data-testid="activity-step"]');
|
||||
const cliRow = screen.getByText("Used Blender · --json project new").closest('[data-testid="activity-step"]');
|
||||
const fetchRow = screen.getByText("example.com/diagram").closest('[data-testid="activity-step"]');
|
||||
|
||||
expect(searchRow).not.toBeNull();
|
||||
expect(cliRow).not.toBeNull();
|
||||
@@ -941,6 +889,181 @@ describe("AgentActivityCluster", () => {
|
||||
expect(cliRow!.compareDocumentPosition(fetchRow!) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders web search results as lightweight branded source rows", () => {
|
||||
const line = 'web_search({"query":"agent frameworks"})';
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={[{
|
||||
id: "t-web-search-results",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: line,
|
||||
traces: [line],
|
||||
toolEvents: [{
|
||||
phase: "end",
|
||||
call_id: "call-web-search",
|
||||
name: "web_search",
|
||||
arguments: { query: "agent frameworks" },
|
||||
result: [
|
||||
"Results for: agent frameworks",
|
||||
"",
|
||||
"1. OpenAI Agents SDK",
|
||||
" https://openai.com/index/new-tools-for-building-agents/?utm_source=test",
|
||||
" Build and deploy agentic applications.",
|
||||
"2. Building effective agents",
|
||||
" https://www.anthropic.com/engineering/building-effective-agents",
|
||||
" Practical patterns for reliable agents.",
|
||||
"3. Internal dashboard",
|
||||
" http://localhost:3000/search",
|
||||
].join("\n"),
|
||||
}],
|
||||
createdAt: 1,
|
||||
}]}
|
||||
isTurnStreaming
|
||||
hasBodyBelow={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Searched agent frameworks")).toBeInTheDocument();
|
||||
expect(screen.queryByText("2 sources")).not.toBeInTheDocument();
|
||||
|
||||
const openAiLink = screen.getByText("OpenAI Agents SDK").closest("a");
|
||||
const anthropicLink = screen.getByText("Building effective agents").closest("a");
|
||||
expect(openAiLink).toHaveAttribute(
|
||||
"href",
|
||||
"https://openai.com/index/new-tools-for-building-agents/",
|
||||
);
|
||||
expect(openAiLink).not.toHaveAttribute("title");
|
||||
expect(anthropicLink).toHaveAttribute(
|
||||
"href",
|
||||
"https://www.anthropic.com/engineering/building-effective-agents",
|
||||
);
|
||||
expect(screen.getByText("openai.com/index/new-tools-for-building-agents")).toBeInTheDocument();
|
||||
expect(screen.getByText("anthropic.com/engineering/building-effective-agents")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("activity-web-favicon-openai.com")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("activity-web-favicon-anthropic.com")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Internal dashboard")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Build and deploy agentic applications.")).not.toBeInTheDocument();
|
||||
const searchStep = screen.getByText("Searched agent frameworks").closest(
|
||||
'[data-testid="activity-step"]',
|
||||
);
|
||||
const openAiStep = openAiLink!.closest('[data-testid="activity-step"]');
|
||||
const anthropicStep = anthropicLink!.closest('[data-testid="activity-step"]');
|
||||
expect(openAiStep).toContainElement(
|
||||
screen.getByText("openai.com/index/new-tools-for-building-agents"),
|
||||
);
|
||||
expect(anthropicStep).toContainElement(
|
||||
screen.getByText("anthropic.com/engineering/building-effective-agents"),
|
||||
);
|
||||
expect(searchStep?.parentElement).toBe(openAiStep?.parentElement);
|
||||
expect(searchStep?.parentElement).toBe(anthropicStep?.parentElement);
|
||||
expect(searchStep?.parentElement?.querySelector("ul, li, section")).toBeNull();
|
||||
expect(screen.getAllByTestId("activity-step")).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("redacts credentials from web search queries, titles, and links", () => {
|
||||
const query = "release notes access_token=signed-secret";
|
||||
const line = `web_search(${JSON.stringify({ query })})`;
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={[{
|
||||
id: "t-web-search-secret",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: line,
|
||||
traces: [line],
|
||||
toolEvents: [{
|
||||
phase: "end",
|
||||
call_id: "call-web-search-secret",
|
||||
name: "web_search",
|
||||
arguments: { query },
|
||||
result: [
|
||||
"1. Release sk-proj-secret1234",
|
||||
" https://example.com/release?api_key=url-secret#details",
|
||||
].join("\n"),
|
||||
}],
|
||||
createdAt: 1,
|
||||
}]}
|
||||
isTurnStreaming={false}
|
||||
hasBodyBelow={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText(/signed-secret|secret1234|url-secret/)).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Searched release notes access_token=<redacted>")).toBeInTheDocument();
|
||||
expect(screen.getByText("Release <redacted>")).toBeInTheDocument();
|
||||
expect(screen.getByText("Release <redacted>").closest("a")).toHaveAttribute(
|
||||
"href",
|
||||
"https://example.com/release",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders persisted search progress as one human-readable action", () => {
|
||||
const line = 'web_search({"query":"site:linkedin.com/company Evomap startup"})';
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={[
|
||||
{
|
||||
id: "search-start",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: line,
|
||||
traces: [line],
|
||||
toolEvents: [{
|
||||
phase: "start",
|
||||
name: "web_search",
|
||||
arguments: { query: "site:linkedin.com/company Evomap startup" },
|
||||
}],
|
||||
createdAt: 1,
|
||||
},
|
||||
{
|
||||
id: "search-end",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: line,
|
||||
traces: [line],
|
||||
toolEvents: [{
|
||||
phase: "error",
|
||||
name: "web_search",
|
||||
arguments: { query: "site:linkedin.com/company Evomap startup" },
|
||||
error: "Search provider rate limited the request",
|
||||
}],
|
||||
createdAt: 2,
|
||||
},
|
||||
]}
|
||||
isTurnStreaming
|
||||
hasBodyBelow={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getAllByTestId("activity-step")).toHaveLength(1);
|
||||
expect(screen.getByText("Could not search LinkedIn · Evomap startup")).toBeInTheDocument();
|
||||
expect(screen.queryByText(/site:linkedin/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Web research")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders reasoning as a single flat activity row", () => {
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={[{
|
||||
id: "r-flat",
|
||||
role: "assistant",
|
||||
content: "",
|
||||
reasoning: "**Planning** a focused search\nfor official sources",
|
||||
reasoningStreaming: true,
|
||||
isStreaming: true,
|
||||
createdAt: 1,
|
||||
}]}
|
||||
isTurnStreaming
|
||||
hasBodyBelow={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Planning a focused search for official sources")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Thinking…")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Thinking")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("labels rejected CLI app calls as failed instead of ran", () => {
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
@@ -966,11 +1089,14 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /failed @github/i }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Worked" }));
|
||||
|
||||
expect(screen.getByTestId("activity-cli-runs")).toHaveTextContent("Failed");
|
||||
expect(screen.getByTestId("activity-cli-runs")).toHaveTextContent("@github");
|
||||
expect(screen.getByTestId("activity-cli-runs")).toHaveTextContent("Error: CLI app 'github' not found");
|
||||
const row = screen.getByText("Could not use GitHub · --json repo view").closest(
|
||||
'[data-testid="activity-step"]',
|
||||
);
|
||||
expect(row).toBeInTheDocument();
|
||||
expect(row).not.toHaveAttribute("title");
|
||||
expect(screen.queryByText("Error: CLI app 'github' not found")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Ran CLI")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -999,11 +1125,9 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
const mcpRuns = screen.getByTestId("activity-mcp-runs");
|
||||
expect(mcpRuns).toHaveTextContent("Using");
|
||||
expect(mcpRuns).toHaveTextContent("Browserbase");
|
||||
expect(mcpRuns).toHaveTextContent("browser_navigate");
|
||||
expect(mcpRuns).toHaveTextContent("url: https://example.com");
|
||||
expect(screen.getByText("Opening example.com · Browserbase")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Using")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/browser_navigate/)).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("activity-mcp-logo-browserbase")).toBeInTheDocument();
|
||||
expect(screen.queryByText(/mcp_browserbase_browser_navigate/)).not.toBeInTheDocument();
|
||||
});
|
||||
@@ -1025,9 +1149,11 @@ describe("AgentActivityCluster", () => {
|
||||
);
|
||||
|
||||
const favicon = screen.getByTestId("activity-web-favicon-auth0.com");
|
||||
expect(favicon.querySelector("img")?.getAttribute("src")).toContain("auth0.com");
|
||||
expect(screen.getByText("Reading")).toBeInTheDocument();
|
||||
expect(screen.getByText("auth0.com/blog/jwt-security-best-practices")).toBeInTheDocument();
|
||||
expect(favicon).toHaveAttribute("src", expect.stringContaining("auth0.com"));
|
||||
const row = screen.getByText("auth0.com/blog/jwt-security-best-practices").closest(
|
||||
'[data-testid="activity-step"]',
|
||||
);
|
||||
expect(row).toHaveTextContent("Reading");
|
||||
});
|
||||
|
||||
it("renders plain-text fetch progress with the site favicon", () => {
|
||||
@@ -1047,8 +1173,42 @@ describe("AgentActivityCluster", () => {
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("activity-web-favicon-auth0.com")).toBeInTheDocument();
|
||||
expect(screen.getByText("Reading")).toBeInTheDocument();
|
||||
expect(screen.getByText("auth0.com/blog/jwt-security-best-practices")).toBeInTheDocument();
|
||||
const row = screen.getByText("auth0.com/blog/jwt-security-best-practices").closest(
|
||||
'[data-testid="activity-step"]',
|
||||
);
|
||||
expect(row).toHaveTextContent("Reading");
|
||||
});
|
||||
|
||||
it("renders a completed fetch as one linked title and URL row", () => {
|
||||
const line = 'web_fetch({"url":"https://example.com/docs"})';
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={[{
|
||||
id: "t-web-fetch-title",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: line,
|
||||
traces: [line],
|
||||
toolEvents: [{
|
||||
phase: "end",
|
||||
call_id: "fetch-title",
|
||||
name: "web_fetch",
|
||||
arguments: { url: "https://example.com/docs" },
|
||||
result: "# Example documentation\n\nPage body",
|
||||
}],
|
||||
createdAt: 1,
|
||||
}]}
|
||||
isTurnStreaming={false}
|
||||
hasBodyBelow={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
const title = screen.getByText("Example documentation");
|
||||
const url = screen.getByText("example.com/docs");
|
||||
const row = title.closest('[data-testid="activity-step"]');
|
||||
expect(row).toContainElement(url);
|
||||
expect(title.closest("a")).toHaveAttribute("href", "https://example.com/docs");
|
||||
expect(screen.getAllByTestId("activity-step")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("does not request favicons for private web fetch targets", () => {
|
||||
@@ -1068,10 +1228,11 @@ describe("AgentActivityCluster", () => {
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId("activity-web-favicon-localhost")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("url: http://localhost:3000/dashboard")).toBeInTheDocument();
|
||||
expect(screen.getByText("Reading Private address")).toBeInTheDocument();
|
||||
expect(screen.queryByText("http://localhost:3000/dashboard")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows readable argument previews for generic tool traces", () => {
|
||||
it("presents generic tool traces as one-line semantic actions", () => {
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={[{
|
||||
@@ -1091,9 +1252,109 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("find_files query: thread · glob: *.tsx")).toBeInTheDocument();
|
||||
expect(screen.getByText("list_dir path: memory")).toBeInTheDocument();
|
||||
expect(screen.getByText("grep pattern: dream_cursor")).toBeInTheDocument();
|
||||
expect(screen.getByText("Found files *.tsx")).toBeInTheDocument();
|
||||
expect(screen.getByText("Listed files memory")).toBeInTheDocument();
|
||||
expect(screen.getByText("Searching files “dream_cursor”")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Technical details")).not.toBeInTheDocument();
|
||||
expect(document.querySelector("details")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("groups repeated searches over internal tool results without exposing raw paths", () => {
|
||||
const pattern = "Jul (1[0-7]), 2026|July (1[0-7]), 2026|2026-07-(1[0-7])";
|
||||
const secondPattern = "Anthropic|OpenAI|DeepMind";
|
||||
const firstPath = "/Users/test/.nanobot/workspace/.nanobot/tool-results/websocket_session/call_first-result.txt";
|
||||
const secondPath = "/Users/test/.nanobot/workspace/.nanobot/tool-results/websocket_session/call_second-result.txt";
|
||||
const traces = [
|
||||
`grep(${JSON.stringify({ pattern, path: firstPath })})`,
|
||||
`grep(${JSON.stringify({ pattern: secondPattern, path: secondPath })})`,
|
||||
];
|
||||
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={[{
|
||||
id: "t-grouped-grep",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: traces.join("\n"),
|
||||
traces,
|
||||
createdAt: 1,
|
||||
}]}
|
||||
isTurnStreaming={false}
|
||||
hasBodyBelow={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
const run = screen.getByText(/Reviewed sources.*2 files/).closest('[data-testid="activity-step"]');
|
||||
expect(run).toBeInTheDocument();
|
||||
expect(screen.queryByText(firstPath)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(secondPath)).not.toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByText(pattern)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(secondPattern)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("call_first-result.txt")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("call_second-result.txt")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("surfaces generic tool failures without dumping their arguments", () => {
|
||||
const args = { pattern: "needle", path: "workspace/file.txt" };
|
||||
const line = `grep(${JSON.stringify(args)})`;
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={[{
|
||||
id: "t-grep-error",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: line,
|
||||
traces: [line],
|
||||
toolEvents: [{
|
||||
phase: "error",
|
||||
call_id: "call-grep-error",
|
||||
name: "grep",
|
||||
arguments: args,
|
||||
error: JSON.stringify({
|
||||
message: "Permission denied",
|
||||
headers: { Authorization: "Bearer sk-live-secret" },
|
||||
token: "super-secret",
|
||||
}),
|
||||
}],
|
||||
createdAt: 1,
|
||||
}]}
|
||||
isTurnStreaming={false}
|
||||
hasBodyBelow={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
const row = screen.getByText("Could not search files “needle”").closest(
|
||||
'[data-testid="activity-step"]',
|
||||
);
|
||||
expect(row).toBeInTheDocument();
|
||||
expect(row).not.toHaveAttribute("title");
|
||||
expect(screen.queryByText(/Permission denied/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/super-secret/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/sk-live-secret/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/Authorization/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("redacts credentials from generic tool URL details", () => {
|
||||
const line = 'download_asset({"url":"https://user:password@example.com/file?access_token=signed-secret&format=png"})';
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={[{
|
||||
id: "t-generic-url-secret",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: line,
|
||||
traces: [line],
|
||||
createdAt: 1,
|
||||
}]}
|
||||
isTurnStreaming={false}
|
||||
hasBodyBelow={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText(/password|signed-secret/)).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Completed Download asset")).toBeInTheDocument();
|
||||
expect(screen.queryByText(/example\.com/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("summarizes long shell traces instead of dumping scripts", () => {
|
||||
@@ -1121,15 +1382,36 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /1 tool calls/i }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Worked" }));
|
||||
|
||||
expect(screen.getByText("Command")).toBeInTheDocument();
|
||||
expect(screen.getByText(/cat << 'EOF' \| bash · script, 6 lines/)).toBeInTheDocument();
|
||||
expect(screen.getByText("Ran command cat << 'EOF' | bash · script, 6 lines")).toBeInTheDocument();
|
||||
expect(screen.queryByText(/SECRET_TOKEN/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/for id in/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/^Done$/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("presents time checks as an intent instead of a raw command", () => {
|
||||
const line = `exec(${JSON.stringify({ command: "date '+%Y-%m-%d %H:%M:%S %Z'" })})`;
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={[{
|
||||
id: "t-date",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: line,
|
||||
traces: [line],
|
||||
createdAt: 1,
|
||||
}]}
|
||||
isTurnStreaming
|
||||
hasBodyBelow={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Checking current time")).toBeInTheDocument();
|
||||
expect(screen.queryByText(/%Y-%m-%d/)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Web")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render zero diff counters for completed edits", () => {
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
@@ -1156,7 +1438,7 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("button", { name: /edited app\.tsx/i })).toBeInTheDocument();
|
||||
expect(screen.getByText("Edited")).toBeInTheDocument();
|
||||
expect(screen.queryByText("+0")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("-0")).not.toBeInTheDocument();
|
||||
});
|
||||
@@ -1220,7 +1502,6 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("button", { name: /preparing edit/i })).toBeInTheDocument();
|
||||
expect(screen.getByText("Preparing file edit…")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -1251,9 +1532,10 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /failed angry-birds\.html/i }));
|
||||
|
||||
expect(screen.getByText("Target text was not found in angry-birds.html.")).toBeInTheDocument();
|
||||
const row = screen.getByText("Could not edit").closest('[data-testid="activity-step"]');
|
||||
expect(row).toBeInTheDocument();
|
||||
expect(row).not.toHaveAttribute("title");
|
||||
expect(screen.queryByText("Target text was not found in angry-birds.html.")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps permission errors readable for failed file edits", () => {
|
||||
@@ -1283,9 +1565,10 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /failed composition\.html/i }));
|
||||
|
||||
expect(screen.getByText("No permission to change this location.")).toBeInTheDocument();
|
||||
const row = screen.getByText("Could not edit").closest('[data-testid="activity-step"]');
|
||||
expect(row).toBeInTheDocument();
|
||||
expect(row).not.toHaveAttribute("title");
|
||||
expect(screen.queryByText("No permission to change this location.")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/\[Errno 13\]/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -1358,18 +1641,18 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
const toggle = screen.getByRole("button", { name: "Edited 3 changes" });
|
||||
expect(toggle).toHaveTextContent("+8");
|
||||
expect(toggle).toHaveTextContent("-7");
|
||||
fireEvent.click(toggle);
|
||||
|
||||
const fileRefs = screen.getAllByTestId("activity-file-reference");
|
||||
expect(fileRefs).toHaveLength(3);
|
||||
expect(fileRefs.every((ref) => ref.textContent?.includes("minecraft-fps/index.html"))).toBe(true);
|
||||
expect(screen.getByText("patch failed")).toBeInTheDocument();
|
||||
expect(screen.getAllByTestId("file-edit-diff")).toHaveLength(2);
|
||||
expect(screen.getByText("<canvas />")).toBeInTheDocument();
|
||||
expect(screen.getByText("const fps = 60;")).toBeInTheDocument();
|
||||
const failedRow = screen.getByText("Could not edit").closest(
|
||||
'[data-testid="activity-step"]',
|
||||
);
|
||||
expect(failedRow).toBeInTheDocument();
|
||||
expect(failedRow).not.toHaveAttribute("title");
|
||||
expect(screen.queryByText("patch failed")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("file-edit-diff")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("<canvas />")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("const fps = 60;")).not.toBeInTheDocument();
|
||||
expect(screen.getAllByText("+2").length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText("-1").length).toBeGreaterThan(0);
|
||||
expect(screen.getAllByText("+6").length).toBeGreaterThan(0);
|
||||
@@ -1379,7 +1662,7 @@ describe("AgentActivityCluster", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("renders tool event embeds as inline activity evidence", () => {
|
||||
it("keeps tool event embeds out of the flat activity list", () => {
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={[{
|
||||
@@ -1406,15 +1689,91 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Web")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("activity-evidence-preview")).toBeInTheDocument();
|
||||
expect(screen.getByRole("img", { name: "Homepage screenshot" })).toHaveAttribute(
|
||||
"src",
|
||||
"/api/media/signed/screenshot.png",
|
||||
);
|
||||
expect(screen.queryByText("Web")).not.toBeInTheDocument();
|
||||
const row = screen.getByText("example.com").closest('[data-testid="activity-step"]');
|
||||
expect(row).toHaveTextContent("Read");
|
||||
expect(screen.queryByTestId("activity-evidence-preview")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/Found image/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("img", { name: "Homepage screenshot" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows missing evidence as a file-safe placeholder", () => {
|
||||
it("keeps image generation status to one activity line", () => {
|
||||
const message: UIMessage = {
|
||||
id: "image-run",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: 'generate_image({"prompt":"an orange nanobot on a desk","aspect_ratio":"4:3"})',
|
||||
traces: ['generate_image({"prompt":"an orange nanobot on a desk","aspect_ratio":"4:3"})'],
|
||||
toolEvents: [{
|
||||
phase: "start",
|
||||
call_id: "image-call",
|
||||
name: "generate_image",
|
||||
arguments: { prompt: "an orange nanobot on a desk", aspect_ratio: "4:3" },
|
||||
}],
|
||||
createdAt: 1,
|
||||
};
|
||||
const { rerender } = render(
|
||||
<AgentActivityCluster messages={[message]} isTurnStreaming hasBodyBelow={false} />,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Generating image")).toBeInTheDocument();
|
||||
|
||||
rerender(
|
||||
<AgentActivityCluster
|
||||
messages={[{
|
||||
...message,
|
||||
toolEvents: [{
|
||||
...message.toolEvents![0],
|
||||
phase: "end",
|
||||
files: [{
|
||||
url: "/api/media/signed/generated.png",
|
||||
name: "generated.png",
|
||||
type: "image/png",
|
||||
}],
|
||||
}],
|
||||
}]}
|
||||
isTurnStreaming={false}
|
||||
hasBodyBelow={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Generated image")).toBeInTheDocument();
|
||||
expect(screen.queryByRole("img", { name: "generated.png" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps image-generation failures visible and actionable", () => {
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={[{
|
||||
id: "image-error",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: 'generate_image({"prompt":"a launch poster"})',
|
||||
traces: ['generate_image({"prompt":"a launch poster"})'],
|
||||
toolEvents: [{
|
||||
phase: "error",
|
||||
call_id: "image-error-call",
|
||||
name: "generate_image",
|
||||
arguments: { prompt: "a launch poster" },
|
||||
error: "Image provider quota exceeded",
|
||||
}],
|
||||
createdAt: 1,
|
||||
}]}
|
||||
isTurnStreaming={false}
|
||||
hasBodyBelow={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Could not generate image")).toBeInTheDocument();
|
||||
const row = screen.getByText("Could not generate image").closest(
|
||||
'[data-testid="activity-step"]',
|
||||
);
|
||||
expect(row).toBeInTheDocument();
|
||||
expect(row).not.toHaveAttribute("title");
|
||||
expect(screen.queryByText("Image provider quota exceeded")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not add a secondary evidence row when evidence is missing", () => {
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={[{
|
||||
@@ -1437,8 +1796,104 @@ describe("AgentActivityCluster", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Vision")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("activity-evidence-preview")).toBeInTheDocument();
|
||||
expect(screen.getByText("missing.png")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Vision")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Captured screenshot")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("activity-evidence-preview")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("missing.png")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps every default activity action on one structural line", () => {
|
||||
render(
|
||||
<AgentActivityCluster
|
||||
messages={[
|
||||
{
|
||||
id: "reasoning-line",
|
||||
role: "assistant",
|
||||
content: "",
|
||||
reasoning: "**Planning** the next step\nwithout a nested title",
|
||||
reasoningStreaming: false,
|
||||
createdAt: 1,
|
||||
},
|
||||
{
|
||||
id: "tool-line",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: 'grep({"pattern":"needle","path":"workspace/file.txt"})',
|
||||
traces: ['grep({"pattern":"needle","path":"workspace/file.txt"})'],
|
||||
createdAt: 2,
|
||||
},
|
||||
{
|
||||
id: "fetch-line",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: 'web_fetch({"url":"https://example.com/docs"})',
|
||||
traces: ['web_fetch({"url":"https://example.com/docs"})'],
|
||||
createdAt: 3,
|
||||
},
|
||||
]}
|
||||
isTurnStreaming={false}
|
||||
hasBodyBelow={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
const steps = screen.getAllByTestId("activity-step");
|
||||
expect(steps.length).toBeGreaterThanOrEqual(3);
|
||||
for (const step of steps) {
|
||||
expect(step).toHaveClass("grid-cols-[1.125rem_minmax(0,1fr)]");
|
||||
const line = step.children[1]?.firstElementChild;
|
||||
expect(line).toHaveClass("overflow-hidden");
|
||||
expect(line).toHaveClass("whitespace-nowrap");
|
||||
expect(step.querySelector("br")).not.toBeInTheDocument();
|
||||
expect(step.querySelector('[data-testid="activity-evidence-preview"]')).not.toBeInTheDocument();
|
||||
}
|
||||
expect(document.querySelector("details")).not.toBeInTheDocument();
|
||||
expect(document.querySelector("ul, li, section")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not expose tool inputs or credentials in the activity surface", () => {
|
||||
const cliLine = 'run_cli_app({"name":"blender","args":["--token","xoxb-1234567890-secret","render"],"json":true})';
|
||||
const mcpLine = 'mcp_browserbase_browser_fill({"element":"Password","text":"mcp-private-value"})';
|
||||
const genericLine = 'third_party_sync({"token":"sk-proj-1234567890-secret","payload":"private-payload"})';
|
||||
const { container } = render(
|
||||
<AgentActivityCluster
|
||||
messages={[
|
||||
{
|
||||
id: "private-cli",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: cliLine,
|
||||
traces: [cliLine],
|
||||
createdAt: 1,
|
||||
},
|
||||
{
|
||||
id: "private-mcp",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: mcpLine,
|
||||
traces: [mcpLine],
|
||||
createdAt: 2,
|
||||
},
|
||||
{
|
||||
id: "private-generic",
|
||||
role: "tool",
|
||||
kind: "trace",
|
||||
content: genericLine,
|
||||
traces: [genericLine],
|
||||
createdAt: 3,
|
||||
},
|
||||
]}
|
||||
isTurnStreaming
|
||||
hasBodyBelow={false}
|
||||
cliApps={[BLENDER_CLI_APP]}
|
||||
mcpPresets={[BROWSERBASE_MCP]}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container.innerHTML).not.toContain("xoxb-1234567890-secret");
|
||||
expect(container.innerHTML).not.toContain("mcp-private-value");
|
||||
expect(container.innerHTML).not.toContain("sk-proj-1234567890-secret");
|
||||
expect(container.innerHTML).not.toContain("private-payload");
|
||||
expect(container.textContent).not.toMatch(/run_cli_app\(|browser_fill\(|third_party_sync\(/);
|
||||
expect(container.textContent).toMatch(/<redacted>|••••/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user