Files
session-viewer/tests/unit/filters.test.ts
teernisse 3fe8d7d3b5 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>
2026-01-30 23:05:01 -05:00

158 lines
5.8 KiB
TypeScript

import { describe, it, expect } from "vitest";
import type { ParsedMessage, MessageCategory } from "../../src/shared/types.js";
import { ALL_CATEGORIES, DEFAULT_HIDDEN_CATEGORIES } from "../../src/shared/types.js";
// Replicate the filter logic to test it in isolation
function filterMessages(
messages: ParsedMessage[],
enabledCategories: Set<MessageCategory>,
redactedUuids: Set<string> = new Set()
): ParsedMessage[] {
return messages.filter(
(m) => enabledCategories.has(m.category) && !redactedUuids.has(m.uuid)
);
}
function makeMsg(uuid: string, category: MessageCategory): ParsedMessage {
return { uuid, category, content: `Content for ${uuid}`, rawIndex: 0 };
}
describe("filters", () => {
const messages: ParsedMessage[] = [
makeMsg("1", "user_message"),
makeMsg("2", "assistant_text"),
makeMsg("3", "thinking"),
makeMsg("4", "tool_call"),
makeMsg("5", "tool_result"),
makeMsg("6", "system_message"),
makeMsg("7", "hook_progress"),
makeMsg("8", "file_snapshot"),
makeMsg("9", "summary"),
];
it("correctly includes messages by enabled category", () => {
const enabled = new Set<MessageCategory>(["user_message", "assistant_text"]);
const filtered = filterMessages(messages, enabled);
expect(filtered).toHaveLength(2);
expect(filtered.map((m) => m.category)).toEqual([
"user_message",
"assistant_text",
]);
});
it("correctly excludes messages by disabled category", () => {
const enabled = new Set<MessageCategory>(ALL_CATEGORIES);
enabled.delete("thinking");
const filtered = filterMessages(messages, enabled);
expect(filtered).toHaveLength(8);
expect(filtered.find((m) => m.category === "thinking")).toBeUndefined();
});
it("default filter state hides tool_result, system, hooks, and snapshots", () => {
const defaultEnabled = new Set(ALL_CATEGORIES);
for (const cat of DEFAULT_HIDDEN_CATEGORIES) {
defaultEnabled.delete(cat);
}
const filtered = filterMessages(messages, defaultEnabled);
expect(filtered.find((m) => m.category === "tool_result")).toBeUndefined();
expect(filtered.find((m) => m.category === "system_message")).toBeUndefined();
expect(filtered.find((m) => m.category === "hook_progress")).toBeUndefined();
expect(filtered.find((m) => m.category === "file_snapshot")).toBeUndefined();
expect(filtered).toHaveLength(5);
});
it("all-off filter returns empty array", () => {
const filtered = filterMessages(messages, new Set());
expect(filtered).toEqual([]);
});
it("all-on filter returns all messages", () => {
const filtered = filterMessages(messages, new Set(ALL_CATEGORIES));
expect(filtered).toHaveLength(9);
});
it("excludes redacted messages", () => {
const enabled = new Set(ALL_CATEGORIES);
const redacted = new Set(["1", "3"]);
const filtered = filterMessages(messages, enabled, redacted);
expect(filtered).toHaveLength(7);
expect(filtered.find((m) => m.uuid === "1")).toBeUndefined();
expect(filtered.find((m) => m.uuid === "3")).toBeUndefined();
});
it("getMatchCount returns count of messages matching search query", () => {
const lowerQuery = "content for 1";
const count = messages.filter((m) =>
m.content.toLowerCase().includes(lowerQuery)
).length;
expect(count).toBe(1);
});
it("getMatchCount returns 0 for empty query", () => {
const lowerQuery = "";
const count = lowerQuery
? messages.filter((m) => m.content.toLowerCase().includes(lowerQuery)).length
: 0;
expect(count).toBe(0);
});
it("conversation preset includes only user and assistant messages", () => {
const preset: MessageCategory[] = ["user_message", "assistant_text"];
const enabled = new Set(preset);
const filtered = filterMessages(messages, enabled);
expect(filtered).toHaveLength(2);
expect(filtered.every((m) => m.category === "user_message" || m.category === "assistant_text")).toBe(true);
});
it("debug preset includes only tool calls and results", () => {
const preset: MessageCategory[] = ["tool_call", "tool_result"];
const enabled = new Set(preset);
const filtered = filterMessages(messages, enabled);
expect(filtered).toHaveLength(2);
expect(filtered.every((m) => m.category === "tool_call" || m.category === "tool_result")).toBe(true);
});
it("hook_progress included when category is enabled", () => {
const enabled = new Set<MessageCategory>(ALL_CATEGORIES);
const filtered = filterMessages(messages, enabled);
expect(filtered.find((m) => m.category === "hook_progress")).toBeDefined();
});
it("hook_progress excluded when category is disabled", () => {
const enabled = new Set<MessageCategory>(ALL_CATEGORIES);
enabled.delete("hook_progress");
const filtered = filterMessages(messages, enabled);
expect(filtered.find((m) => m.category === "hook_progress")).toBeUndefined();
});
it("category counts are computed correctly", () => {
const counts: Record<string, number> = {};
for (const cat of ALL_CATEGORIES) {
counts[cat] = 0;
}
for (const msg of messages) {
counts[msg.category]++;
}
expect(counts["user_message"]).toBe(1);
expect(counts["assistant_text"]).toBe(1);
expect(counts["thinking"]).toBe(1);
expect(Object.values(counts).reduce((a, b) => a + b, 0)).toBe(9);
});
it("category counts exclude redacted messages", () => {
const redacted = new Set(["1", "2"]);
const counts: Record<string, number> = {};
for (const cat of ALL_CATEGORIES) {
counts[cat] = 0;
}
for (const msg of messages) {
if (!redacted.has(msg.uuid)) {
counts[msg.category]++;
}
}
expect(counts["user_message"]).toBe(0);
expect(counts["assistant_text"]).toBe(0);
expect(Object.values(counts).reduce((a, b) => a + b, 0)).toBe(7);
});
});