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.
142 lines
5.1 KiB
C#
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));
|
|
}
|
|
}
|