v0.3.90 6f6753c

view_collapse_tools_test.go RawHistoryFolder

1package tui
2
3import (
4	"encoding/json"
5	"strings"
6	"testing"
7
8	"github.com/patriceckhart/zot/packages/provider"
9)
10
11func TestCollapseToolCallPreservesLayout(t *testing.T) {
12	for _, layout := range []string{"box", "flat", "compact"} {
13		for _, tool := range []struct {
14			name, args, preview string
15		}{
16			{"bash", `{"command":"printf hello"}`, "$ printf hello"},
17			{"write", `{"path":"test.txt","content":"hello"}`, "hello"},
18			{"edit", `{"path":"test.txt","edits":[{"oldText":"old","newText":"hello"}]}`, "hello"},
19		} {
20			t.Run(layout+"/"+tool.name, func(t *testing.T) {
21				v := View{Theme: Dark, CollapseToolCall: true, FlatTools: layout == "flat", CompactMode: layout == "compact"}
22				for _, expanded := range []bool{false, true} {
23					v.ExpandAll = expanded
24					rows := v.RenderToolCall(ToolCallView{Name: tool.name, RawJSONBuf: tool.args}, 80)
25					found := false
26					for _, row := range rows {
27						plain := stripANSI(row)
28						if strings.Contains(plain, tool.preview) {
29							found = true
30							want := 0
31							if layout == "box" {
32								want = 2
33							}
34							if got := strings.Count(plain, "│"); got != want {
35								t.Errorf("expanded=%v: got %d borders, want %d: %s", expanded, got, want, plain)
36							}
37						}
38						if layout != "box" && strings.ContainsAny(plain, "┌┐└┘│") {
39							t.Errorf("expanded=%v: unexpected frame: %s", expanded, plain)
40						}
41						if visibleWidth(row) > 80 {
42							t.Errorf("expanded=%v: row exceeds terminal width: %s", expanded, plain)
43						}
44					}
45					if !found {
46						t.Fatalf("expanded=%v: missing preview: %v", expanded, rows)
47					}
48				}
49			})
50		}
51	}
52}
53
54func TestCollapseToolCallPreservesErrors(t *testing.T) {
55	for _, layout := range []string{"box", "flat", "compact"} {
56		for _, text := range []string{"", "failed", strings.Repeat("output\n", ToolCollapseLines+1)} {
57			for _, live := range []bool{false, true} {
58				v := View{Theme: Dark, CollapseToolCall: true, FlatTools: layout == "flat", CompactMode: layout == "compact"}
59				var rows []string
60				if live {
61					rows = v.RenderToolCall(ToolCallView{Name: "bash", Result: text, Error: true}, 80)
62				} else {
63					rows = v.renderMessage(provider.Message{Role: provider.RoleTool, Content: []provider.Content{
64						provider.ToolResultBlock{IsError: true, Content: []provider.Content{provider.TextBlock{Text: text}}},
65					}}, 80, false)
66				}
67				rendered := strings.Join(rows, "\n")
68				if !strings.Contains(rendered, v.Theme.FG256(v.Theme.Error, "error")) {
69					t.Errorf("%s live=%v: missing error indicator: %s", layout, live, stripANSI(rendered))
70				}
71				if text == "failed" && !strings.Contains(rendered, v.Theme.FG256(v.Theme.Error, text)) {
72					t.Errorf("%s live=%v: missing error styling: %q", layout, live, rendered)
73				}
74				if layout != "box" && strings.ContainsAny(stripANSI(rendered), "┌┐└┘│") {
75					t.Errorf("%s live=%v: unexpected frame: %s", layout, live, stripANSI(rendered))
76				}
77			}
78		}
79	}
80}
81
82func TestCollapseToolCallShowsLastBoxPreviewLine(t *testing.T) {
83	args := json.RawMessage(`{"command":"echo preview"}`)
84	v := View{Theme: Dark, CollapseToolCall: true, ToolCalls: []ToolCallView{{
85		ID: "toolu_1", Name: "bash", Args: ShortArgs("bash", args), Result: "first-output\nlast-output\n",
86	}}}
87	plain := stripANSI(strings.Join(v.BuildLive(80), "\n"))
88	if !strings.Contains(plain, "bash") {
89		t.Fatalf("collapsed render lost tool header:\n%s", plain)
90	}
91	if !strings.Contains(plain, "last-output") || strings.Contains(plain, "first-output") {
92		t.Fatalf("collapsed render did not show only the last result line:\n%s", plain)
93	}
94	if !strings.ContainsAny(plain, "┌┐") || !strings.ContainsAny(plain, "└┘") || !strings.Contains(plain, "│") {
95		t.Fatalf("collapsed box render did not retain the complete empty frame:\n%s", plain)
96	}
97	v.ExpandAll = true
98	expanded := stripANSI(strings.Join(v.BuildLive(80), "\n"))
99	if !strings.Contains(expanded, "first-output") {
100		t.Fatalf("ctrl-o expansion did not reveal non-compact collapsed tool result:\n%s", expanded)
101	}
102}
103
104func TestCollapseToolCallShowsLastCompactPreviewLine(t *testing.T) {
105	args := json.RawMessage(`{"command":"echo preview"}`)
106	v := View{
107		Theme:            Dark,
108		CompactMode:      true,
109		CollapseToolCall: true,
110		Messages: []provider.Message{
111			{Role: provider.RoleAssistant, Content: []provider.Content{
112				provider.ToolCallBlock{ID: "toolu_1", Name: "bash", Arguments: args},
113			}},
114			{Role: provider.RoleTool, Content: []provider.Content{
115				provider.ToolResultBlock{CallID: "toolu_1", Content: []provider.Content{provider.TextBlock{Text: "first-output\nlast-output"}}},
116			}},
117		},
118	}
119	plain := stripANSI(strings.Join(v.Build(80), "\n"))
120	if !strings.Contains(plain, "bash") {
121		t.Fatalf("collapsed compact render lost tool header:\n%s", plain)
122	}
123	if !strings.Contains(plain, "last-output") || strings.Contains(plain, "first-output") {
124		t.Fatalf("collapsed compact render did not show only the last result line:\n%s", plain)
125	}
126	v.ExpandAll = true
127	expanded := stripANSI(strings.Join(v.Build(80), "\n"))
128	if !strings.Contains(expanded, "first-output") {
129		t.Fatalf("ctrl-o expansion did not reveal collapsed tool result:\n%s", expanded)
130	}
131}
132
133func TestCollapseToolCallShowsLastLivePreviewLine(t *testing.T) {
134	args := json.RawMessage(`{"command":"echo preview"}`)
135	v := View{Theme: Dark, CollapseToolCall: true, ToolCalls: []ToolCallView{{
136		ID: "toolu_1", Name: "bash", Args: ShortArgs("bash", args), RawJSONBuf: string(args),
137	}}}
138	plain := stripANSI(strings.Join(v.BuildLive(80), "\n"))
139	if !strings.Contains(plain, "$ echo") {
140		t.Fatalf("collapsed live render lost its one-line preview:\n%s", plain)
141	}
142	if !strings.Contains(plain, "bash") {
143		t.Fatalf("collapsed live render lost tool header:\n%s", plain)
144	}
145}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close