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 TestStatusBarUsesThinkingMaxThemeColor(t *testing.T) {
89 th := Dark
90 th.ThinkingMax = 201
91 lines := StatusBar(StatusBarParams{
92 Theme: th, Provider: "openai", Model: "gpt-5.6-sol", Reasoning: "max", Cols: 200,
93 })
94 if len(lines) != 1 {
95 t.Fatalf("lines = %d, want 1", len(lines))
96 }
97 if !strings.Contains(lines[0], "\x1b[38;5;201m") || !strings.Contains(stripANSI(lines[0]), "thinking: max") {
98 t.Fatalf("max thinking style missing: %q", lines[0])
99 }
100}
101
102func TestStatusBarNarrowKeepsModelAndThinkingTogetherWhenTheyFit(t *testing.T) {
103 lines := StatusBar(StatusBarParams{
104 Theme: Dark,
105 Provider: "openai-codex",
106 Model: "gpt-5.5",
107 Reasoning: "xhigh",
108 CWD: "/tmp/x",
109 Usage: provider.Usage{
110 CostUSD: 0,
111 },
112 Subscription: true,
113 ContextUsed: 100,
114 ContextMax: 1_000_000,
115 Cols: 64,
116 })
117 if len(lines) != 3 {
118 t.Fatalf("narrow status with model+thinking fit: want 3 lines, got %d: %q", len(lines), lines)
119 }
120 plain := make([]string, len(lines))
121 for i, line := range lines {
122 plain[i] = stripANSI(line)
123 }
124 if !strings.Contains(plain[0], "(openai-codex) gpt-5.5 thinking: xhigh") {
125 t.Fatalf("line 1 should contain model and thinking level, got %q", plain[0])
126 }
127 if !strings.Contains(plain[1], "$0.000 (sub)") || strings.Contains(plain[1], "thinking level") {
128 t.Fatalf("line 2 should contain only stats, got %q", plain[1])
129 }
130 if !strings.Contains(plain[2], "/tmp/x") {
131 t.Fatalf("line 3 should contain cwd, got %q", plain[2])
132 }
133}
134
135func TestStatusBarNarrowSplitsAfterThinkingLevel(t *testing.T) {
136 lines := StatusBar(StatusBarParams{
137 Theme: Dark,
138 Provider: "openai-codex",
139 Model: "gpt-5.5",
140 Reasoning: "minimum",
141 CWD: "/tmp/x",
142 Usage: provider.Usage{
143 InputTokens: 4_300_000,
144 OutputTokens: 2,
145 },
146 Cols: 40,
147 })
148 if len(lines) != 4 {
149 t.Fatalf("narrow status with thinking: want 4 lines, got %d: %q", len(lines), lines)
150 }
151 plain := make([]string, len(lines))
152 for i, line := range lines {
153 plain[i] = stripANSI(line)
154 }
155 if !strings.Contains(plain[0], "(openai-codex) gpt-5.5") {
156 t.Fatalf("line 1 should contain model info, got %q", plain[0])
157 }
158 if !strings.Contains(plain[1], "thinking: minimal") || strings.Contains(plain[1], "↑4.3M") {
159 t.Fatalf("line 2 should contain only thinking level, got %q", plain[1])
160 }
161 if !strings.Contains(plain[2], "↑4.3M ↓2") {
162 t.Fatalf("line 3 should contain stats, got %q", plain[2])
163 }
164 if !strings.Contains(plain[3], "/tmp/x") {
165 t.Fatalf("line 4 should contain cwd, got %q", plain[3])
166 }
167}
168
169func TestStatusBarVeryNarrowSplitsAfterThinkingLevel(t *testing.T) {
170 lines := StatusBar(StatusBarParams{
171 Theme: Dark,
172 Provider: "openai-codex",
173 Model: "gpt-5.5",
174 Reasoning: "minimum",
175 CWD: "/tmp/x",
176 Usage: provider.Usage{
177 InputTokens: 4_300_000,
178 OutputTokens: 2,
179 },
180 Cols: 32,
181 })
182 if len(lines) != 4 {
183 t.Fatalf("narrow status with thinking: want 4 lines, got %d: %q", len(lines), lines)
184 }
185 plain := make([]string, len(lines))
186 for i, line := range lines {
187 plain[i] = stripANSI(line)
188 }
189 if !strings.Contains(plain[0], "(openai-codex) gpt-5.5") {
190 t.Fatalf("line 1 should contain model info, got %q", plain[0])
191 }
192 if !strings.Contains(plain[1], "thinking: minimal") {
193 t.Fatalf("line 2 should contain thinking level, got %q", plain[1])
194 }
195 if !strings.Contains(plain[2], "↑4.3M ↓2") {
196 t.Fatalf("line 3 should contain stats, got %q", plain[2])
197 }
198 if !strings.Contains(plain[3], "/tmp/x") {
199 t.Fatalf("line 4 should contain cwd, got %q", plain[3])
200 }
201}
202
203func TestStatusBarNoYoloTagPrecedesCWD(t *testing.T) {
204 lines := StatusBar(StatusBarParams{
205 Theme: Dark,
206 Provider: "openai",
207 Model: "gpt-5.5",
208 CWD: "/tmp/x",
209 NoYolo: true,
210 Cols: 200,
211 })
212 if len(lines) != 2 {
213 t.Fatalf("want 2 lines, got %d: %q", len(lines), lines)
214 }
215 plain := stripANSI(lines[1])
216 if !strings.Contains(plain, "yolo mode disabled - /tmp/x") {
217 t.Fatalf("cwd line should include no-yolo tag before cwd, got %q", plain)
218 }
219}