Files
ClaudeDo/tests/ClaudeDo.Ui.Tests/Helpers/StreamLineFormatterTests.cs
T
mika kuns 9bb0e67a97 feat(ui): colour agent output by kind and stop cutting tool calls at 120 chars
StreamLineFormatter now returns kind-tagged entries (text, tool, edit,
tool result, error, sys, result) instead of a flat string, so the
existing LogKind colours finally apply to stream-json output. Tool
argument/result limit raised from 120 to 500 chars.
2026-10-01 10:49:52 +02:00

142 lines
5.1 KiB
C#

using ClaudeDo.Ui.Helpers;
using ClaudeDo.Ui.ViewModels.Islands;
namespace ClaudeDo.Ui.Tests.Helpers;
public class StreamLineFormatterTests
{
private readonly StreamLineFormatter _formatter = new();
private (LogKind Kind, string Text) Single(string line) => Assert.Single(_formatter.FormatLine(line)!);
// --- Assistant text blocks ---
[Fact]
public void FormatLine_AssistantTextBlock_ReturnsClaudeEntry()
{
var line = """{"type":"assistant","message":{"content":[{"type":"text","text":"Hello world"}]}}""";
Assert.Equal((LogKind.Claude, "Hello world"), Single(line));
}
[Fact]
public void FormatLine_AssistantThinkingBlock_IsFiltered()
{
var line = """{"type":"assistant","message":{"content":[{"type":"thinking","text":"hidden"}]}}""";
Assert.Null(_formatter.FormatLine(line));
}
[Fact]
public void FormatLine_MixedBlocks_KeepOrderAndKinds()
{
var line = """{"type":"assistant","message":{"content":[{"type":"text","text":"Fixing it."},{"type":"tool_use","id":"x","name":"Edit","input":{"file_path":"C:/repo/src/Foo.cs"}}]}}""";
Assert.Equal(
[(LogKind.Claude, "Fixing it."), (LogKind.Edit, "[Edit] Foo.cs")],
_formatter.FormatLine(line));
}
// --- Tool use ---
[Fact]
public void FormatLine_AssistantToolUseBlock_ReturnsToolEntry()
{
var line = """{"type":"assistant","message":{"content":[{"type":"tool_use","id":"x","name":"Bash","input":{"command":"ls"}}]}}""";
Assert.Equal((LogKind.Tool, "[Bash] $ ls"), Single(line));
}
[Theory]
[InlineData("Write")]
[InlineData("NotebookEdit")]
public void FormatLine_FileMutatingTools_AreEditKind(string tool)
{
var line = $$$"""{"type":"assistant","message":{"content":[{"type":"tool_use","id":"x","name":"{{{tool}}}","input":{"file_path":"a/b.txt"}}]}}""";
Assert.Equal((LogKind.Edit, $"[{tool}] b.txt"), Single(line));
}
[Fact]
public void FormatLine_LongBashCommand_IsNotCutAt120Chars()
{
var cmd = "cd \\\"C:/Private/.claudedo-worktrees/claude-do/385-unit-branch-teil-1-branch-naming-basis-b\\\" && dotnet build ClaudeDo.slnx -c Release --no-restore -v minimal";
var line = $$$"""{"type":"assistant","message":{"content":[{"type":"tool_use","id":"x","name":"Bash","input":{"command":"{{{cmd}}}"}}]}}""";
var (_, text) = Single(line);
Assert.EndsWith("-v minimal", text);
}
// --- Tool results ---
[Fact]
public void FormatLine_ToolResult_IsToolResultKind()
{
var line = """{"type":"user","message":{"content":[{"type":"tool_result","content":"Build succeeded."}]}}""";
Assert.Equal((LogKind.ToolResult, "→ Build succeeded."), Single(line));
}
[Fact]
public void FormatLine_ToolResultError_IsStderrKind()
{
var line = """{"type":"user","message":{"content":[{"type":"tool_result","is_error":true,"content":"boom"}]}}""";
Assert.Equal((LogKind.Stderr, "→ error: boom"), Single(line));
}
// --- Result, system, fallback ---
[Fact]
public void FormatLine_InputJsonDelta_ReturnsNull()
{
var line = """{"type":"stream_event","event":{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"cmd\":"}}}""";
Assert.Null(_formatter.FormatLine(line));
}
[Fact]
public void FormatLine_Result_ReturnsDoneEntry()
{
var line = """{"type":"result","result":"Done."}""";
Assert.Equal((LogKind.Done, "--- Result ---\nDone."), Single(line));
}
[Fact]
public void FormatLine_ApiRetry_ReturnsSysEntry()
{
var line = """{"type":"system","subtype":"api_retry"}""";
Assert.Equal((LogKind.Sys, "[Retrying API call...]"), Single(line));
}
[Fact]
public void FormatLine_SystemUnknownSubtype_ReturnsNull()
{
var line = """{"type":"system","subtype":"some_unknown_subtype"}""";
Assert.Null(_formatter.FormatLine(line));
}
[Fact]
public void FormatLine_AssistantType_ReturnsNull()
{
var line = """{"type":"assistant","message":{}}""";
Assert.Null(_formatter.FormatLine(line));
}
[Fact]
public void FormatLine_MalformedJson_ReturnsRawStdoutEntry()
{
Assert.Equal((LogKind.Stdout, "not json at all"), Single("not json at all"));
}
[Fact]
public void FormatLine_MessageStartAndDelta_ReturnsNull()
{
var start = """{"type":"stream_event","event":{"type":"message_start","message":{}}}""";
var delta = """{"type":"stream_event","event":{"type":"message_delta","delta":{}}}""";
Assert.Null(_formatter.FormatLine(start));
Assert.Null(_formatter.FormatLine(delta));
}
// --- Line splitting ---
[Fact]
public void FromEntries_SplitsMultilineText_KeepingKind_AndDroppingBlanks()
{
var lines = LogLineViewModel.FromEntries([(LogKind.Done, "--- Result ---\n\nDone.\r\n")]).ToList();
Assert.Equal(["--- Result ---", "Done."], lines.Select(l => l.Text));
Assert.All(lines, l => Assert.Equal(LogKind.Done, l.Kind));
}
}