Add comprehensive test suite for progress tracking system

Test fixture updates:
- Add toolUseId fields (toolu_read1, toolu_edit1) to tool_use blocks
- Add parentToolUseID-linked progress events for read and edit tools
- Add orphaned SessionStart progress event (no parent)
- Update tool_result references to match new toolUseId values
- Add bash_progress and mcp_progress subtypes for subtype derivation

session-parser tests (7 new):
- toolUseId extraction from tool_use blocks with and without id field
- parentToolUseId and progressSubtype extraction from hook_progress
- Subtype derivation for bash_progress, mcp_progress, agent_progress
- Fallback to "hook" for unknown data types
- Undefined parentToolUseId when field is absent

progress-grouper tests (7 new):
- Partition parented progress into toolProgress map
- Remove parented progress from filtered messages array
- Keep orphaned progress (no parentToolUseId) in main stream
- Keep progress with invalid parentToolUseId (no matching tool_call)
- Empty input handling
- Sort each group by rawIndex
- Multiple tool_call parents tracked independently

agent-progress-parser tests (full suite):
- Parse user text events with prompt/agentId metadata extraction
- Parse tool_use blocks into AgentToolCall events
- Parse tool_result blocks with content extraction
- Parse text content as text_response with line counting
- Handle multiple content blocks in single turn
- Post-pass tool_result→tool_call linking (sourceTool, language)
- Empty input and malformed JSON → raw_content fallback
- stripLineNumbers for cat-n prefixed output
- summarizeToolCall for Read, Grep, Glob, Bash, Task, WarpGrep, etc.

ProgressBadge component tests:
- Collapsed state shows pill counts, hides content
- Expanded state shows all event content via markdown
- Subtype counting accuracy
- Agent-only events route to AgentProgressView

AgentProgressView component tests:
- Prompt banner rendering with truncation
- Agent ID and turn count display
- Summary rows with timestamps and tool names
- Click-to-expand drill-down content

html-exporter tests (8 new):
- Collapsible rendering for thinking, tool_call, tool_result
- Toggle button and JavaScript inclusion
- Non-collapsible messages lack collapse attributes
- Diff content detection and highlighting
- Progress badge rendering with toolProgress data

filters tests (2 new):
- hook_progress included/excluded by category toggle

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-30 23:05:01 -05:00
parent d4de363227
commit 3fe8d7d3b5
8 changed files with 1376 additions and 6 deletions

View File

@@ -16,13 +16,15 @@ function makeMessage(
function makeExportRequest(
messages: ParsedMessage[],
visible?: string[],
redacted?: string[]
redacted?: string[],
toolProgress?: Record<string, ParsedMessage[]>
): ExportRequest {
return {
session: {
id: "test-session",
project: "test-project",
messages,
toolProgress,
},
visibleMessageUuids: visible || messages.map((m) => m.uuid),
redactedMessageUuids: redacted || [],
@@ -118,4 +120,149 @@ describe("html-exporter", () => {
// Verify singular — should NOT contain "1 messages"
expect(html).not.toMatch(/\b1 messages\b/);
});
// ─── Collapsible messages ───
it("thinking messages render collapsed by default", async () => {
const msg = makeMessage({
uuid: "think-1",
category: "thinking",
content: "Line one\nLine two\nLine three\nLine four\nLine five",
});
const html = await generateExportHtml(makeExportRequest([msg]));
expect(html).toContain('data-collapsed="true"');
});
it("tool_call messages render collapsed with tool name preview", async () => {
const msg = makeMessage({
uuid: "tc-1",
category: "tool_call",
content: "",
toolName: "Read",
toolInput: '{"path": "/foo/bar.ts"}',
toolUseId: "tu-1",
});
const html = await generateExportHtml(makeExportRequest([msg]));
expect(html).toContain('data-collapsed="true"');
expect(html).toContain("collapsed-preview");
expect(html).toContain("Read");
});
it("tool_result messages render collapsed with content preview", async () => {
const msg = makeMessage({
uuid: "tr-1",
category: "tool_result",
content: "This is the first line of a tool result that should be truncated in the preview display when collapsed",
});
const html = await generateExportHtml(makeExportRequest([msg]));
expect(html).toContain('data-collapsed="true"');
expect(html).toContain("collapsed-preview");
});
it("collapsible messages include toggle button", async () => {
const msg = makeMessage({
uuid: "think-2",
category: "thinking",
content: "Some thoughts",
});
const html = await generateExportHtml(makeExportRequest([msg]));
expect(html).toContain("collapsible-toggle");
});
it("non-collapsible messages do not have collapse attributes on their message div", async () => {
const msg = makeMessage({
uuid: "user-1",
category: "user_message",
content: "Hello there",
});
const html = await generateExportHtml(makeExportRequest([msg]));
// Extract the message div — it should NOT have data-collapsed (CSS will have it as a selector)
const messageDiv = html.match(/<div class="message"[^>]*>/);
expect(messageDiv).not.toBeNull();
expect(messageDiv![0]).not.toContain("data-collapsed");
expect(messageDiv![0]).not.toContain("collapsible-toggle");
});
it("export includes toggle JavaScript", async () => {
const msg = makeMessage({ uuid: "msg-js", content: "Hello" });
const html = await generateExportHtml(makeExportRequest([msg]));
expect(html).toContain("<script>");
expect(html).toContain("collapsible-toggle");
});
it("tool_result with diff content gets diff highlighting", async () => {
const diffContent = [
"diff --git a/foo.ts b/foo.ts",
"--- a/foo.ts",
"+++ b/foo.ts",
"@@ -1,3 +1,3 @@",
" unchanged",
"-old line",
"+new line",
].join("\n");
const msg = makeMessage({
uuid: "tr-diff",
category: "tool_result",
content: diffContent,
});
const html = await generateExportHtml(makeExportRequest([msg]));
expect(html).toContain("diff-add");
expect(html).toContain("diff-del");
expect(html).toContain("diff-hunk");
});
it("tool_call with progress events renders progress badge", async () => {
const toolMsg = makeMessage({
uuid: "tc-prog",
category: "tool_call",
content: "",
toolName: "Bash",
toolUseId: "tu-prog",
});
const progressEvents: ParsedMessage[] = [
makeMessage({
uuid: "pe-1",
category: "hook_progress",
content: "Running...",
progressSubtype: "bash",
timestamp: "2025-01-01T12:00:00Z",
}),
makeMessage({
uuid: "pe-2",
category: "hook_progress",
content: "Done",
progressSubtype: "bash",
timestamp: "2025-01-01T12:00:01Z",
}),
];
const html = await generateExportHtml(
makeExportRequest([toolMsg], undefined, undefined, { "tu-prog": progressEvents })
);
expect(html).toContain("progress-badge");
expect(html).toContain("progress-drawer");
expect(html).toContain("bash");
expect(html).toContain("2");
});
it("tool_call without progress events has no badge", async () => {
const toolMsg = makeMessage({
uuid: "tc-no-prog",
category: "tool_call",
content: "",
toolName: "Read",
toolUseId: "tu-no-prog",
});
const html = await generateExportHtml(makeExportRequest([toolMsg]));
// The CSS will contain .progress-badge as a selector, but the message HTML should not
// have an actual progress-badge div element
expect(html).not.toContain('<div class="progress-badge">');
});
it("print CSS forces content visible", async () => {
const msg = makeMessage({ uuid: "msg-print", content: "Hello" });
const html = await generateExportHtml(makeExportRequest([msg]));
expect(html).toContain("@media print");
// Should override collapsed hidden state for print
expect(html).toMatch(/\.message-body\s*\{[^}]*display:\s*block/);
});
});