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

@@ -0,0 +1,98 @@
import { describe, it, expect } from "vitest";
import { groupProgress } from "../../src/server/services/progress-grouper.js";
import type { ParsedMessage } from "../../src/shared/types.js";
function makeMsg(
overrides: Partial<ParsedMessage> & { uuid: string; rawIndex: number }
): ParsedMessage {
return {
category: "assistant_text",
content: "test",
...overrides,
};
}
describe("progress-grouper", () => {
it("partitions parented progress into toolProgress map", () => {
const messages: ParsedMessage[] = [
makeMsg({ uuid: "tc-1", rawIndex: 0, category: "tool_call", toolUseId: "toolu_1" }),
makeMsg({ uuid: "hp-1", rawIndex: 1, category: "hook_progress", parentToolUseId: "toolu_1", progressSubtype: "hook" }),
makeMsg({ uuid: "hp-2", rawIndex: 2, category: "hook_progress", parentToolUseId: "toolu_1", progressSubtype: "bash" }),
makeMsg({ uuid: "txt-1", rawIndex: 3, category: "assistant_text" }),
];
const result = groupProgress(messages);
expect(result.toolProgress["toolu_1"]).toHaveLength(2);
expect(result.toolProgress["toolu_1"][0].uuid).toBe("hp-1");
expect(result.toolProgress["toolu_1"][1].uuid).toBe("hp-2");
});
it("removes parented progress from messages array", () => {
const messages: ParsedMessage[] = [
makeMsg({ uuid: "tc-1", rawIndex: 0, category: "tool_call", toolUseId: "toolu_1" }),
makeMsg({ uuid: "hp-1", rawIndex: 1, category: "hook_progress", parentToolUseId: "toolu_1" }),
makeMsg({ uuid: "txt-1", rawIndex: 2, category: "assistant_text" }),
];
const result = groupProgress(messages);
expect(result.messages).toHaveLength(2);
expect(result.messages.map((m) => m.uuid)).toEqual(["tc-1", "txt-1"]);
});
it("keeps orphaned progress (no parentToolUseId) in messages", () => {
const messages: ParsedMessage[] = [
makeMsg({ uuid: "hp-orphan", rawIndex: 0, category: "hook_progress", progressSubtype: "hook" }),
makeMsg({ uuid: "txt-1", rawIndex: 1, category: "assistant_text" }),
];
const result = groupProgress(messages);
expect(result.messages).toHaveLength(2);
expect(result.messages[0].uuid).toBe("hp-orphan");
expect(Object.keys(result.toolProgress)).toHaveLength(0);
});
it("keeps progress with invalid parentToolUseId (no matching tool_call) in messages", () => {
const messages: ParsedMessage[] = [
makeMsg({ uuid: "hp-invalid", rawIndex: 0, category: "hook_progress", parentToolUseId: "toolu_nonexistent" }),
makeMsg({ uuid: "txt-1", rawIndex: 1, category: "assistant_text" }),
];
const result = groupProgress(messages);
expect(result.messages).toHaveLength(2);
expect(result.messages[0].uuid).toBe("hp-invalid");
});
it("returns empty results for empty input", () => {
const result = groupProgress([]);
expect(result.messages).toEqual([]);
expect(result.toolProgress).toEqual({});
});
it("sorts each toolProgress group by rawIndex", () => {
const messages: ParsedMessage[] = [
makeMsg({ uuid: "tc-1", rawIndex: 0, category: "tool_call", toolUseId: "toolu_1" }),
makeMsg({ uuid: "hp-3", rawIndex: 5, category: "hook_progress", parentToolUseId: "toolu_1" }),
makeMsg({ uuid: "hp-1", rawIndex: 1, category: "hook_progress", parentToolUseId: "toolu_1" }),
makeMsg({ uuid: "hp-2", rawIndex: 3, category: "hook_progress", parentToolUseId: "toolu_1" }),
];
const result = groupProgress(messages);
const group = result.toolProgress["toolu_1"];
expect(group.map((m) => m.rawIndex)).toEqual([1, 3, 5]);
});
it("handles multiple tool_call parents independently", () => {
const messages: ParsedMessage[] = [
makeMsg({ uuid: "tc-1", rawIndex: 0, category: "tool_call", toolUseId: "toolu_1" }),
makeMsg({ uuid: "hp-1a", rawIndex: 1, category: "hook_progress", parentToolUseId: "toolu_1" }),
makeMsg({ uuid: "tc-2", rawIndex: 2, category: "tool_call", toolUseId: "toolu_2" }),
makeMsg({ uuid: "hp-2a", rawIndex: 3, category: "hook_progress", parentToolUseId: "toolu_2" }),
makeMsg({ uuid: "hp-2b", rawIndex: 4, category: "hook_progress", parentToolUseId: "toolu_2" }),
];
const result = groupProgress(messages);
expect(result.toolProgress["toolu_1"]).toHaveLength(1);
expect(result.toolProgress["toolu_2"]).toHaveLength(2);
expect(result.messages).toHaveLength(2); // only the 2 tool_calls
});
});