Files
finanzen/src/components/chat/AgentChat.test.tsx
2026-06-16 11:02:00 +02:00

210 lines
6.2 KiB
TypeScript

import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, test } from "vitest";
import {
AgentConversation,
AgentMessage,
AgentPromptInput,
type AgentChatMessage,
} from "./AgentChat";
import {
buildSourcesFromToolTrace,
buildReasoningSteps,
getToolTraceSummary,
scrollConversationToBottom,
} from "./agentChatModel";
const assistantMessage: AgentChatMessage = {
id: "assistant-1",
role: "assistant",
content: "Deine groesste Ausgabe war Miete.",
toolTrace: [
{
name: "summarize_transactions",
inputSummary: "Mai 2026",
resultSummary: "12 Umsaetze zusammengefasst",
},
{
name: "list_transactions",
inputSummary: "Miete",
resultSummary: "1 Treffer",
},
],
};
describe("AgentChat phase 1 components", () => {
test("scrolls the conversation container directly to the bottom", () => {
const container = {
scrollTop: 0,
scrollHeight: 1200,
};
scrollConversationToBottom(container);
expect(container.scrollTop).toBe(1200);
});
test("summarizes tool traces for compact agent transparency", () => {
expect(getToolTraceSummary(undefined)).toBe("Keine Werkzeuge");
expect(getToolTraceSummary([])).toBe("Keine Werkzeuge");
expect(getToolTraceSummary(assistantMessage.toolTrace)).toBe("2 Werkzeuge verwendet");
});
test("renders conversation messages with assistant tool trace details", () => {
const markup = renderToStaticMarkup(
<AgentConversation
messages={[
{ id: "user-1", role: "user", content: "Was war teuer?" },
assistantMessage,
]}
/>,
);
expect(markup).toContain("Was war teuer?");
expect(markup).toContain("Deine groesste Ausgabe war Miete.");
expect(markup).toContain("2 Werkzeuge verwendet");
expect(markup).toContain("summarize_transactions");
expect(markup).toContain("12 Umsaetze zusammengefasst");
});
test("renders the prompt input with ready and busy states", () => {
const readyMarkup = renderToStaticMarkup(
<AgentPromptInput
value="Bitte analysieren"
disabled={false}
isSubmitting={false}
placeholder="Welche Auswertung soll ich machen?"
onChange={() => undefined}
onSubmit={() => undefined}
/>,
);
const busyMarkup = renderToStaticMarkup(
<AgentPromptInput
value="Bitte analysieren"
disabled
isSubmitting
placeholder="Chat wird vorbereitet..."
onChange={() => undefined}
onSubmit={() => undefined}
/>,
);
expect(readyMarkup).toContain("Welche Auswertung soll ich machen?");
expect(readyMarkup).toContain("Senden");
expect(busyMarkup).toContain("Antwort laeuft");
expect(busyMarkup).toContain("disabled");
});
test("disables prompt submit for whitespace-only input", () => {
const markup = renderToStaticMarkup(
<AgentPromptInput
value=" "
disabled={false}
isSubmitting={false}
placeholder="Welche Auswertung soll ich machen?"
onChange={() => undefined}
onSubmit={() => undefined}
/>,
);
expect(markup).toContain("disabled");
});
test("renders a thinking indicator while the agent is submitting", () => {
const markup = renderToStaticMarkup(
<AgentConversation messages={[assistantMessage]} isSubmitting />,
);
expect(markup).toContain("Denk mit der KI nach...");
});
test("renders a single message without a tool section when no trace exists", () => {
const markup = renderToStaticMarkup(
<AgentMessage message={{ id: "assistant-empty", role: "assistant", content: "Hallo" }} />,
);
expect(markup).toContain("Assistant");
expect(markup).toContain("Hallo");
expect(markup).not.toContain("Werkzeuge verwendet");
});
});
describe("AgentChat phase 2 reasoning disclosure", () => {
test("builds safe reasoning steps from tool traces without exposing raw inputs", () => {
const steps = buildReasoningSteps(assistantMessage.toolTrace);
expect(steps).toEqual([
{
label: "summarize_transactions",
description: "12 Umsaetze zusammengefasst",
status: "complete",
},
{
label: "list_transactions",
description: "1 Treffer",
status: "complete",
},
]);
expect(JSON.stringify(steps)).not.toContain("Mai 2026");
});
test("renders an assistant work-progress disclosure from tool traces", () => {
const markup = renderToStaticMarkup(<AgentMessage message={assistantMessage} />);
expect(markup).toContain("So wurde gearbeitet");
expect(markup).toContain("summarize_transactions");
expect(markup).toContain("12 Umsaetze zusammengefasst");
});
test("marks the current agent step as active while submitting", () => {
const markup = renderToStaticMarkup(
<AgentConversation messages={[assistantMessage]} isSubmitting />,
);
expect(markup).toContain("Antwort wird vorbereitet");
expect(markup).toContain("data-status=\"active\"");
});
});
describe("AgentChat phase 3 sources and inline citations", () => {
test("builds private finance sources from tool traces", () => {
expect(buildSourcesFromToolTrace(assistantMessage.toolTrace)).toEqual([
{
id: "tool-1",
title: "summarize_transactions",
description: "12 Umsaetze zusammengefasst",
},
{
id: "tool-2",
title: "list_transactions",
description: "1 Treffer",
},
]);
});
test("renders source list and inline citation markers for assistant messages", () => {
const markup = renderToStaticMarkup(
<AgentMessage
message={{
id: "assistant-cited",
role: "assistant",
content: "Die Miete war der groesste Posten [1].",
sources: [
{
id: "tool-1",
title: "list_transactions",
description: "1 Mietumsatz gefunden",
},
],
citations: [{ marker: "1", sourceId: "tool-1" }],
}}
/>,
);
expect(markup).toContain("Quellen");
expect(markup).toContain("list_transactions");
expect(markup).toContain("1 Mietumsatz gefunden");
expect(markup).toContain("[1]");
expect(markup).toContain("data-source-id=\"tool-1\"");
});
});