package ui import ( "bytes" "encoding/json" "strings" "testing" "harness/internal/agent" "harness/internal/llm" ) func TestSummarizeInputIsSortedAndBounded(t *testing.T) { tests := []struct { name string in string want string }{ {name: "sorted keys", in: `{"path":"main.go","offset":10}`, want: "offset=10, path=main.go"}, {name: "empty input", in: `{}`, want: ""}, {name: "unparseable falls back to raw", in: `not json`, want: "not json"}, {name: "long value truncated", in: `{"cmd":"` + strings.Repeat("x", 80) + `"}`, want: "cmd=" + strings.Repeat("x", 60) + "…"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { if got := summarizeInput(json.RawMessage(tc.in)); got != tc.want { t.Errorf("got %q, want %q", got, tc.want) } }) } } func TestToolResultIsSummarizedUnlessVerbose(t *testing.T) { long := strings.Join([]string{"1", "2", "3", "4", "5", "6"}, "\n") ev := agent.Event{Kind: agent.EventToolResult, Tool: "read_file", Result: long} var quiet, loud bytes.Buffer New(&quiet).Handle(ev) (&Renderer{W: &loud, Verbose: true}).Handle(ev) if !strings.Contains(quiet.String(), "2 more lines") { t.Errorf("default should summarize, got:\n%s", quiet.String()) } if strings.Contains(loud.String(), "more lines") || !strings.Contains(loud.String(), "6") { t.Errorf("verbose should print everything, got:\n%s", loud.String()) } } func TestErrorResultIsLabelled(t *testing.T) { var buf bytes.Buffer New(&buf).Handle(agent.Event{Kind: agent.EventToolResult, Result: "no such file", IsError: true}) if !strings.Contains(buf.String(), "error: no such file") { t.Errorf("got %q, want the failure marked", buf.String()) } } func TestUsageLine(t *testing.T) { var buf bytes.Buffer New(&buf).Usage(llm.Usage{InputTokens: 1200, OutputTokens: 300, CacheReadInputTokens: 900}, 4) if got := buf.String(); !strings.Contains(got, "4 turns") || !strings.Contains(got, "900 cached") { t.Errorf("got %q, want turns and cache reads", got) } }