1package modes
2
3import (
4 "context"
5 "strings"
6 "testing"
7
8 "github.com/bnema/zut/packages/provider"
9 "github.com/bnema/zut/packages/tui"
10)
11
12func TestModelDialogReasoningNavigation(t *testing.T) {
13 d := &modelDialog{
14 active: true,
15 view: []provider.Model{{Provider: "openai-codex", ID: "gpt-5.6-luna", Reasoning: true}},
16 reasoning: "high",
17 showReasoning: true,
18 }
19
20 act := d.HandleKey(tui.Key{Kind: tui.KeyRight})
21 if !act.ReasoningChanged || act.Reasoning != "xhigh" || d.reasoning != "xhigh" {
22 t.Fatalf("right action = %+v, dialog reasoning = %q; want xhigh", act, d.reasoning)
23 }
24 act = d.HandleKey(tui.Key{Kind: tui.KeyRune, Rune: 'L'})
25 if !act.ReasoningChanged || act.Reasoning != "max" || d.reasoning != "max" {
26 t.Fatalf("L action = %+v, dialog reasoning = %q; want max", act, d.reasoning)
27 }
28 act = d.HandleKey(tui.Key{Kind: tui.KeyLeft})
29 if !act.ReasoningChanged || act.Reasoning != "xhigh" {
30 t.Fatalf("left action = %+v; want xhigh", act)
31 }
32 act = d.HandleKey(tui.Key{Kind: tui.KeyRune, Rune: 'H'})
33 if !act.ReasoningChanged || act.Reasoning != "high" {
34 t.Fatalf("H action = %+v; want high", act)
35 }
36}
37
38func TestModelDialogReasoningAllowsLowercaseFilter(t *testing.T) {
39 d := &modelDialog{active: true, showReasoning: true}
40 for _, r := range "claude" {
41 d.HandleKey(tui.Key{Kind: tui.KeyRune, Rune: r})
42 }
43 if d.query != "claude" {
44 t.Fatalf("query = %q, want claude", d.query)
45 }
46}
47
48func TestModelDialogWithoutReasoningTreatsHAsFilter(t *testing.T) {
49 d := &modelDialog{active: true}
50 d.HandleKey(tui.Key{Kind: tui.KeyRune, Rune: 'H'})
51 if d.query != "H" {
52 t.Fatalf("embedded picker query = %q, want H", d.query)
53 }
54}
55
56func TestModelDialogReasoningNavigationStopsAtBounds(t *testing.T) {
57 d := &modelDialog{
58 active: true,
59 showReasoning: true,
60 view: []provider.Model{{Provider: "openai-codex", ID: "gpt-5.6-luna", Reasoning: true}},
61 reasoning: "",
62 }
63
64 if act := d.HandleKey(tui.Key{Kind: tui.KeyLeft}); act.ReasoningChanged {
65 t.Fatalf("left at off changed reasoning: %+v", act)
66 }
67 d.reasoning = "max"
68 if act := d.HandleKey(tui.Key{Kind: tui.KeyRight}); act.ReasoningChanged {
69 t.Fatalf("right at max changed reasoning: %+v", act)
70 }
71}
72
73func TestModelDialogReasoningNavigationAppliesToInteractive(t *testing.T) {
74 d := &modelDialog{
75 active: true,
76 showReasoning: true,
77 view: []provider.Model{{Provider: "openai-codex", ID: "gpt-5.6-luna", Reasoning: true}},
78 reasoning: "high",
79 }
80 var changed string
81 i := &Interactive{
82 cfg: InteractiveConfig{
83 Reasoning: "high",
84 OnReasoningChanged: func(level string) { changed = level },
85 },
86 modelDialog: d,
87 }
88
89 i.handleKey(context.Background(), tui.Key{Kind: tui.KeyRight})
90 if i.cfg.Reasoning != "xhigh" || changed != "xhigh" {
91 t.Fatalf("reasoning = %q, callback = %q; want xhigh", i.cfg.Reasoning, changed)
92 }
93}
94
95func TestModelDialogWithoutReasoningIgnoresArrows(t *testing.T) {
96 d := &modelDialog{
97 active: true,
98 view: []provider.Model{{Provider: "openai-codex", ID: "gpt-5.6-luna", Reasoning: true}},
99 reasoning: "high",
100 }
101 if act := d.HandleKey(tui.Key{Kind: tui.KeyRight}); act.ReasoningChanged || d.reasoning != "high" {
102 t.Fatalf("hidden reasoning changed: action=%+v reasoning=%q", act, d.reasoning)
103 }
104}
105
106func TestModelDialogReasoningReflectsHighlightedModel(t *testing.T) {
107 d := &modelDialog{
108 active: true,
109 showReasoning: true,
110 reasoning: "high",
111 view: []provider.Model{{Provider: "google", ID: "gemini-2.0-flash"}},
112 }
113 text := stripANSIBytes(strings.Join(d.Render(tui.Dark, 100), "\n"))
114 if !strings.Contains(text, "reasoning: off") {
115 t.Fatalf("effective reasoning level missing from %q", text)
116 }
117 if !strings.Contains(text, "unavailable for highlighted model") {
118 t.Fatalf("unsupported-model hint missing from %q", text)
119 }
120 if strings.Contains(text, "H/L to change") {
121 t.Fatalf("unsupported model advertises reasoning controls: %q", text)
122 }
123}
124
125func TestModelDialogReasoningUsesLevelColor(t *testing.T) {
126 tests := []struct {
127 level string
128 color tui.TerminalColor
129 }{
130 {"off", tui.Dark.Muted},
131 {"low", tui.Dark.Accent},
132 {"high", tui.Dark.Warning},
133 {"max", tui.Dark.ThinkingMax},
134 }
135 for _, tt := range tests {
136 if got := reasoningLevelColor(tui.Dark, tt.level); got != tt.color {
137 t.Errorf("reasoningLevelColor(%q) = %#v, want %#v", tt.level, got, tt.color)
138 }
139 }
140
141 d := &modelDialog{
142 active: true,
143 showReasoning: true,
144 reasoning: "max",
145 view: []provider.Model{{Provider: "openai-codex", ID: "gpt-5.6-luna", Reasoning: true}},
146 }
147 text := strings.Join(d.Render(tui.Dark, 100), "\n")
148 if !strings.Contains(text, tui.Dark.FGColor(tui.Dark.ThinkingMax, "max")) {
149 t.Fatalf("max reasoning color missing from %q", text)
150 }
151 if !strings.Contains(text, "←/→ or H/L to change") {
152 t.Fatalf("reasoning navigation hint missing from %q", text)
153 }
154}