Files
ss-tools/frontend/src/lib/components/assistant/__tests__/ToolCallCard.test.ts
busya f87ebf5d4b feat(agent): Gradio-powered LangGraph agent chat with streaming, tool calls, file upload, conversation persistence
- Gradio 5.50.0 ChatInterface with type='messages' streaming
- LangGraph create_react_agent with InMemorySaver checkpointer
- 4 @tool functions: search_dashboards, get_health_summary, list_environments, get_task_status
- Structured ChatMessage metadata (7 discriminator types: stream_token, tool_start/end/error, confirm_required, confirm_resolved, error)
- HITL resume via second submit() with interrupt_before/Command
- Dual-identity RBAC: service JWT + user JWT for tool calls
- File upload (10 MB limit, pdfplumber/xlsx/JSON parser)
- Conversation persistence via POST /api/agent/conversations/save
- REST API: list, history, archive conversations; multi-tab gate; LLM config
- LLM provider selection via Admin -> LLM Settings (assistant_planner_provider)
- Svelte 5 AgentChatModel with stream event queue, dedup, stream_status watcher
- MarkdownRenderer using svelte-markdown with semantic Tailwind tokens
- ToolCallCard (3 states: executing/completed/failed)
- ConversationList with search, date grouping, infinite scroll
- ConnectionIndicator with Gradio health status
- /agent route with two-column layout
- Vite proxy /api/agent/gradio -> Gradio SSE
- Fixed: not_() SQLAlchemy operator, route collision with _admin_routes
- Fixed: conversation_id -> id normalization, .pyc cache staleness
- Fixed: event.data array parsing (Gradio returns [jsonStr, null])
- Requirements pinned: gradio==5.50.0, pydantic>=2.7,<=2.12.3
2026-06-10 10:27:19 +03:00

96 lines
3.4 KiB
TypeScript

// #region TestAgentChat.ToolCallCard [C:2] [TYPE Module] [SEMANTICS test,tool,call,card]
// @BRIEF L2 UX tests for ToolCallCard — renders executing/completed/failed states with proper visual indicators.
// @RELATION BINDS_TO -> [AgentChat.ToolCallCard]
// @UX_TEST: executing → spinner visible, tool name shown, no details button.
// @UX_TEST: completed → checkmark, details toggle shows result.
// @UX_TEST: failed → cross, details show error.
import { describe, it, expect } from "vitest";
import { render, screen, fireEvent } from "@testing-library/svelte";
import ToolCallCard from "../ToolCallCard.svelte";
// #region TestAgentChat.ToolCallCard.Executing [C:2] [TYPE Test]
// @UX_STATE executing — spinner icon, tool name in mono font, no Details button.
describe("ToolCallCard — executing state", () => {
it("renders tool name and spinner", () => {
render(ToolCallCard, {
props: {
tool: "search_dashboards",
input: { query: "test" },
status: "executing",
},
});
expect(screen.getByText("search_dashboards")).toBeTruthy();
// SVG spinner should be present (has animate-spin class in the SVG element)
const svg = document.querySelector("svg.animate-spin");
expect(svg).toBeTruthy();
});
it("does not show Details button while executing", () => {
render(ToolCallCard, {
props: {
tool: "search_dashboards",
status: "executing",
},
});
expect(screen.queryByText("Details")).toBeNull();
});
});
// #endregion TestAgentChat.ToolCallCard.Executing
// #region TestAgentChat.ToolCallCard.Completed [C:2] [TYPE Test]
// @UX_STATE completed — checkmark icon, Details button present, clicking shows result.
describe("ToolCallCard — completed state", () => {
it("renders tool name and checkmark", () => {
render(ToolCallCard, {
props: {
tool: "search_dashboards",
output: { found: 3 },
status: "completed",
},
});
expect(screen.getByText("search_dashboards")).toBeTruthy();
expect(screen.getByText("Details")).toBeTruthy();
});
it("toggles detail section on Details click", async () => {
render(ToolCallCard, {
props: {
tool: "search_dashboards",
output: { found: 3, items: ["a", "b"] },
status: "completed",
},
});
// Initially details should be hidden
expect(document.querySelector("details")).toBeNull();
// Click Details button using fireEvent for reactive flush
const detailsBtn = screen.getByText("Details");
await fireEvent.click(detailsBtn);
// After click, details elements appear
const postDetails = document.querySelector("details");
expect(postDetails).toBeTruthy();
const summary = postDetails?.querySelector("summary");
expect(summary).toBeTruthy();
});
});
// #endregion TestAgentChat.ToolCallCard.Completed
// #region TestAgentChat.ToolCallCard.Failed [C:2] [TYPE Test]
// @UX_STATE failed — cross icon, error visible inline.
describe("ToolCallCard — failed state", () => {
it("renders tool name, cross, and error detail", () => {
render(ToolCallCard, {
props: {
tool: "search_dashboards",
error: "API timeout after 30s",
status: "failed",
},
});
expect(screen.getByText("search_dashboards")).toBeTruthy();
expect(screen.getByText("Details")).toBeTruthy();
});
});
// #endregion TestAgentChat.ToolCallCard.Failed
// #endregion TestAgentChat.ToolCallCard