//go:build unix
// Effort selection crosses the composer, model registry, keyboard, and daemon
// session. Stubbed command replies cannot catch a dropped request or commit.
package e2e
import (
"strings"
"testing"
"albedo/cli/internal/tui"
tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/x/ansi"
)
func TestTUIEffortSelectorCommitsThroughTheDaemon(t *testing.T) {
providerRoute(t, echoReply)
d := newTUIDriver(t)
// Selecting a model goes through the real composer: enter dispatches the
// command, the app asks the daemon, and the answer lands in the model.
d.App.Chat.TextArea.SetValue("/model o3-mini")
dispatched, ok := d.Key(tea.KeyEnter).(tui.ChatExecuteCommandMsg)
if !ok || dispatched.Name != "/model" || dispatched.Args != "o3-mini" {
t.Fatalf("enter did not dispatch /model: %#v", dispatched)
}
d.Dispatch(dispatched)
if d.App.ActiveSession == nil || d.App.ActiveSession.Model != "o3-mini" || d.App.ActiveSession.Effort != "medium" {
t.Fatalf("the daemon's model switch did not land: %+v", d.App.ActiveSession)
}
// /effort arrives as the message the command menu submits for it; the
// daemon, not a stub, decides which tiers the model offers and which one
// is current.
d.Dispatch(tui.ChatExecuteCommandMsg{Name: "/effort"})
opened := d.View()
for _, tier := range []string{"low", "medium", "high"} {
if !strings.Contains(opened, tier) {
t.Fatalf("the selector is missing tier %q:\n%s", tier, opened)
}
}
if !strings.Contains(opened, "Reasoning effort") || !strings.Contains(opened, "[medium]") {
t.Fatalf("the daemon's current tier is not selected:\n%s", opened)
}
if strings.Contains(opened, "› ") {
t.Fatal("the composer must be hidden while choosing effort")
}
// Even a narrow selector stays inside the terminal and Escape restores the
// composer without changing the daemon.
d.Update(tea.WindowSizeMsg{Width: 28, Height: 14})
for line := range strings.SplitSeq(d.View(), "\n") {
if width := ansi.StringWidth(line); width > 28 {
t.Fatalf("effort selector rendered a %d-cell line at width 28: %q", width, line)
}
}
d.Key(tea.KeyEscape)
if view := d.View(); strings.Contains(view, "Reasoning effort") || !strings.Contains(view, "› ") {
t.Fatalf("Escape did not restore the composer:\n%s", view)
}
d.Update(tea.WindowSizeMsg{Width: 80, Height: 22})
d.Dispatch(tui.ChatExecuteCommandMsg{Name: "/effort"})
// The keyboard alone moves the selection and commits it.
d.Key(tea.KeyRight)
if !strings.Contains(d.View(), "[high]") {
t.Fatalf("right did not select high:\n%s", d.View())
}
committed, ok := d.Key(tea.KeyEnter).(tui.ChatExecuteCommandMsg)
if !ok || committed.Name != "/effort" || committed.Args != "high" {
t.Fatalf("enter did not commit the selected tier: %#v", committed)
}
d.Dispatch(committed)
settled := d.View()
if strings.Contains(settled, "Reasoning effort") {
t.Fatalf("the selector stayed open after committing:\n%s", settled)
}
if !strings.Contains(settled, "reasoning effort set to high") || !strings.Contains(settled, "› ") {
t.Fatalf("the daemon's answer did not reach the chat:\n%s", settled)
}
if d.App.ActiveSession.Effort != "high" {
t.Fatalf("the chat kept effort %q", d.App.ActiveSession.Effort)
}
// The truth at the end is the daemon's session record.
session := daemonSession(t, d.App.ActiveSession.ID)
if session.Model != "o3-mini" || session.Effort != "high" {
t.Fatalf("session %s kept model %q effort %q, want o3-mini at high", session.ID, session.Model, session.Effort)
}
}