1package tui
2
3import (
4 "strings"
5 "testing"
6
7 "github.com/patriceckhart/zot/packages/provider"
8)
9
10// TestStatusBarAlwaysTwoLines verifies the status bar always emits
11// two lines when a cwd is present, regardless of terminal width, and
12// that the cwd is indented with the 2-space pad so it lines up under
13// the "(provider)" column on line 1.
14func TestStatusBarAlwaysTwoLines(t *testing.T) {
15 // Wide terminal that would previously combine into one line.
16 lines := StatusBar(StatusBarParams{
17 Theme: Dark,
18 Provider: "anthropic",
19 Model: "claude-opus-4-7",
20 CWD: "/tmp/x",
21 Usage: provider.Usage{
22 InputTokens: 476_000,
23 OutputTokens: 3_400,
24 CostUSD: 1.242,
25 },
26 Subscription: true,
27 ContextUsed: 55_000,
28 ContextMax: 1_000_000,
29 Cols: 500, // very wide
30 })
31 if len(lines) != 2 {
32 t.Fatalf("want 2 lines, got %d: %q", len(lines), lines)
33 }
34 if !strings.Contains(lines[0], "claude-opus-4-7") {
35 t.Errorf("line 1 should contain model, got %q", lines[0])
36 }
37 // Line 2 must start with 2-space indent.
38 if !strings.HasPrefix(lines[1], " ") {
39 t.Errorf("line 2 should start with 2-space indent, got %q", lines[1])
40 }
41 if !strings.Contains(lines[1], "/tmp/x") {
42 t.Errorf("line 2 should contain cwd, got %q", lines[1])
43 }
44}
45
46// TestStatusBarNoCWD verifies an empty cwd stays single-line.
47func TestStatusBarNoCWD(t *testing.T) {
48 lines := StatusBar(StatusBarParams{
49 Theme: Dark,
50 Provider: "openai",
51 Model: "gpt-5.4",
52 CWD: "",
53 Cols: 200,
54 })
55 if len(lines) != 1 {
56 t.Fatalf("empty cwd: want 1 line, got %d: %q", len(lines), lines)
57 }
58}
59
60func TestStatusBarThinkingLevelBetweenModelAndStats(t *testing.T) {
61 lines := StatusBar(StatusBarParams{
62 Theme: Dark,
63 Provider: "openai-codex",
64 Model: "gpt-5.5",
65 Reasoning: "minimum",
66 CWD: "/tmp/x",
67 Usage: provider.Usage{
68 InputTokens: 4_300_000,
69 OutputTokens: 2,
70 },
71 Cols: 500,
72 })
73 if len(lines) != 2 {
74 t.Fatalf("want 2 lines, got %d: %q", len(lines), lines)
75 }
76 plain := stripANSI(lines[0])
77 modelIdx := strings.Index(plain, "(openai-codex) gpt-5.5")
78 thinkingIdx := strings.Index(plain, "thinking: minimal")
79 statsIdx := strings.Index(plain, "↑4.3M")
80 if modelIdx < 0 || thinkingIdx < 0 || statsIdx < 0 {
81 t.Fatalf("line should contain model, thinking level, and stats, got %q", plain)
82 }
83 if !(modelIdx < thinkingIdx && thinkingIdx < statsIdx) {
84 t.Fatalf("thinking level should sit between model and stats, got %q", plain)
85 }
86}
87
88func TestStatusBarNarrowKeepsModelAndThinkingTogetherWhenTheyFit(t *testing.T) {
89 lines := StatusBar(StatusBarParams{
90 Theme: Dark,
91 Provider: "openai-codex",
92 Model: "gpt-5.5",
93 Reasoning: "maximum",
94 CWD: "/tmp/x",
95 Usage: provider.Usage{
96 CostUSD: 0,
97 },
98 Subscription: true,
99 ContextUsed: 100,
100 ContextMax: 1_000_000,
101 Cols: 64,
102 })
103 if len(lines) != 3 {
104 t.Fatalf("narrow status with model+thinking fit: want 3 lines, got %d: %q", len(lines), lines)
105 }
106 plain := make([]string, len(lines))
107 for i, line := range lines {
108 plain[i] = stripANSI(line)
109 }
110 if !strings.Contains(plain[0], "(openai-codex) gpt-5.5 thinking: maximum") {
111 t.Fatalf("line 1 should contain model and thinking level, got %q", plain[0])
112 }
113 if !strings.Contains(plain[1], "$0.000 (sub)") || strings.Contains(plain[1], "thinking level") {
114 t.Fatalf("line 2 should contain only stats, got %q", plain[1])
115 }
116 if !strings.Contains(plain[2], "/tmp/x") {
117 t.Fatalf("line 3 should contain cwd, got %q", plain[2])
118 }
119}
120
121func TestStatusBarNarrowSplitsAfterThinkingLevel(t *testing.T) {
122 lines := StatusBar(StatusBarParams{
123 Theme: Dark,
124 Provider: "openai-codex",
125 Model: "gpt-5.5",
126 Reasoning: "minimum",
127 CWD: "/tmp/x",
128 Usage: provider.Usage{
129 InputTokens: 4_300_000,
130 OutputTokens: 2,
131 },
132 Cols: 40,
133 })
134 if len(lines) != 4 {
135 t.Fatalf("narrow status with thinking: want 4 lines, got %d: %q", len(lines), lines)
136 }
137 plain := make([]string, len(lines))
138 for i, line := range lines {
139 plain[i] = stripANSI(line)
140 }
141 if !strings.Contains(plain[0], "(openai-codex) gpt-5.5") {
142 t.Fatalf("line 1 should contain model info, got %q", plain[0])
143 }
144 if !strings.Contains(plain[1], "thinking: minimal") || strings.Contains(plain[1], "↑4.3M") {
145 t.Fatalf("line 2 should contain only thinking level, got %q", plain[1])
146 }
147 if !strings.Contains(plain[2], "↑4.3M ↓2") {
148 t.Fatalf("line 3 should contain stats, got %q", plain[2])
149 }
150 if !strings.Contains(plain[3], "/tmp/x") {
151 t.Fatalf("line 4 should contain cwd, got %q", plain[3])
152 }
153}
154
155func TestStatusBarVeryNarrowSplitsAfterThinkingLevel(t *testing.T) {
156 lines := StatusBar(StatusBarParams{
157 Theme: Dark,
158 Provider: "openai-codex",
159 Model: "gpt-5.5",
160 Reasoning: "minimum",
161 CWD: "/tmp/x",
162 Usage: provider.Usage{
163 InputTokens: 4_300_000,
164 OutputTokens: 2,
165 },
166 Cols: 32,
167 })
168 if len(lines) != 4 {
169 t.Fatalf("narrow status with thinking: want 4 lines, got %d: %q", len(lines), lines)
170 }
171 plain := make([]string, len(lines))
172 for i, line := range lines {
173 plain[i] = stripANSI(line)
174 }
175 if !strings.Contains(plain[0], "(openai-codex) gpt-5.5") {
176 t.Fatalf("line 1 should contain model info, got %q", plain[0])
177 }
178 if !strings.Contains(plain[1], "thinking: minimal") {
179 t.Fatalf("line 2 should contain thinking level, got %q", plain[1])
180 }
181 if !strings.Contains(plain[2], "↑4.3M ↓2") {
182 t.Fatalf("line 3 should contain stats, got %q", plain[2])
183 }
184 if !strings.Contains(plain[3], "/tmp/x") {
185 t.Fatalf("line 4 should contain cwd, got %q", plain[3])
186 }
187}
188
189func TestStatusBarNoYoloTagPrecedesCWD(t *testing.T) {
190 lines := StatusBar(StatusBarParams{
191 Theme: Dark,
192 Provider: "openai",
193 Model: "gpt-5.5",
194 CWD: "/tmp/x",
195 NoYolo: true,
196 Cols: 200,
197 })
198 if len(lines) != 2 {
199 t.Fatalf("want 2 lines, got %d: %q", len(lines), lines)
200 }
201 plain := stripANSI(lines[1])
202 if !strings.Contains(plain, "yolo mode disabled - /tmp/x") {
203 t.Fatalf("cwd line should include no-yolo tag before cwd, got %q", plain)
204 }
205}