1package tui
2
3import (
4 "encoding/json"
5 "fmt"
6 "strings"
7 "testing"
8
9 "github.com/patriceckhart/zot/packages/provider"
10)
11
12// boxGlyphs are the runes that only appear when a tool call is drawn
13// inside the bordered panel. Flat mode must emit none of them.
14const boxGlyphs = "┌└│┐┘"
15
16func assertNoBoxGlyphs(t *testing.T, plain string) {
17 t.Helper()
18 if strings.ContainsAny(plain, boxGlyphs) {
19 t.Fatalf("flat render still contains box glyphs:\n%s", plain)
20 }
21}
22
23func TestFlatToolRenderDropsBorders(t *testing.T) {
24 args := json.RawMessage(`{"command":"echo hi"}`)
25 v := View{
26 Theme: Dark,
27 FlatTools: true,
28 ToolCalls: []ToolCallView{
29 {ID: "toolu_1", Name: "bash", Args: ShortArgs("bash", args), Result: "hi\n"},
30 },
31 }
32 plain := stripANSI(strings.Join(v.Build(80), "\n"))
33 assertNoBoxGlyphs(t, plain)
34 if !strings.Contains(plain, "bash") {
35 t.Fatalf("flat header lost the tool name:\n%s", plain)
36 }
37 if !strings.Contains(plain, "hi") {
38 t.Fatalf("flat render lost the output:\n%s", plain)
39 }
40}
41
42// The box render of the same call still has borders — the toggle is
43// what changes, not the data.
44func TestBoxToolRenderKeepsBorders(t *testing.T) {
45 args := json.RawMessage(`{"command":"echo hi"}`)
46 v := View{
47 Theme: Dark,
48 ToolCalls: []ToolCallView{
49 {ID: "toolu_1", Name: "bash", Args: ShortArgs("bash", args), Result: "hi\n"},
50 },
51 }
52 plain := stripANSI(strings.Join(v.Build(80), "\n"))
53 if !strings.ContainsAny(plain, boxGlyphs) {
54 t.Fatalf("box render should contain border glyphs:\n%s", plain)
55 }
56}
57
58func TestFlatToolRenderLiveBodyHasNoBorders(t *testing.T) {
59 args := json.RawMessage(`{"command":"printf 'start' && sleep 60"}`)
60 v := View{
61 Theme: Dark,
62 FlatTools: true,
63 ToolCalls: []ToolCallView{
64 {ID: "toolu_1", Name: "bash", Args: ShortArgs("bash", args), RawJSONBuf: string(args)},
65 },
66 }
67 plain := stripANSI(strings.Join(v.BuildLive(100), "\n"))
68 assertNoBoxGlyphs(t, plain)
69 if !strings.Contains(plain, "$ printf 'start'") {
70 t.Fatalf("flat live body lost the streamed command:\n%s", plain)
71 }
72}
73
74// Truncation + the ctrl+o expand footer must survive flat mode.
75func TestFlatToolRenderKeepsTruncationFooter(t *testing.T) {
76 var b strings.Builder
77 for i := 0; i < ToolCollapseLines+50; i++ {
78 fmt.Fprintf(&b, "line %d\n", i)
79 }
80 args := json.RawMessage(`{"command":"seq 999"}`)
81 collapsed := View{
82 Theme: Dark,
83 FlatTools: true,
84 ToolCalls: []ToolCallView{
85 {ID: "toolu_1", Name: "bash", Args: ShortArgs("bash", args), Result: b.String()},
86 },
87 }
88 plain := stripANSI(strings.Join(collapsed.Build(80), "\n"))
89 assertNoBoxGlyphs(t, plain)
90 if !strings.Contains(plain, "ctrl+o to expand") {
91 t.Fatalf("flat render dropped the truncation footer:\n%s", plain)
92 }
93
94 // ExpandAll shows everything and still has no borders.
95 collapsed.ExpandAll = true
96 full := stripANSI(strings.Join(collapsed.Build(80), "\n"))
97 assertNoBoxGlyphs(t, full)
98 if strings.Contains(full, "ctrl+o to expand") {
99 t.Fatalf("expanded flat render should not show the footer:\n%s", full)
100 }
101 if !strings.Contains(full, fmt.Sprintf("line %d", ToolCollapseLines+40)) {
102 t.Fatalf("expanded flat render is missing later lines:\n%s", full)
103 }
104}
105
106// A finished tool result that comes back as a RoleTool message (the
107// transcript path, distinct from the in-flight ToolCalls path) also
108// renders flat.
109func TestFlatToolRenderTranscriptResult(t *testing.T) {
110 callArgs := json.RawMessage(`{"command":"echo done"}`)
111 v := View{
112 Theme: Dark,
113 FlatTools: true,
114 Messages: []provider.Message{
115 {
116 Role: provider.RoleAssistant,
117 Content: []provider.Content{
118 provider.ToolCallBlock{ID: "toolu_1", Name: "bash", Arguments: callArgs},
119 },
120 },
121 {
122 Role: provider.RoleTool,
123 Content: []provider.Content{
124 provider.ToolResultBlock{CallID: "toolu_1", Content: []provider.Content{
125 provider.TextBlock{Text: "done"},
126 }},
127 },
128 },
129 },
130 }
131 plain := stripANSI(strings.Join(v.Build(80), "\n"))
132 assertNoBoxGlyphs(t, plain)
133 if !strings.Contains(plain, "bash") || !strings.Contains(plain, "done") {
134 t.Fatalf("flat transcript result lost header or output:\n%s", plain)
135 }
136}