a431d1ec95
Add OpenAI-compatible providers, TUI, slash commands, and model switching. Add local file tools plus optional yolo shell execution. Document config, usage, and development workflow.
164 lines
4.3 KiB
Go
164 lines
4.3 KiB
Go
package tui
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
func TestModelRendersChatShell(t *testing.T) {
|
|
m := newModel(context.Background(), nil, Options{ModelName: "test-model", Yolo: true})
|
|
next, _ := m.Update(tea.WindowSizeMsg{Width: 100, Height: 30})
|
|
rendered := next.(model).View()
|
|
|
|
for _, want := range []string{"agentu", "test-model", "yolo tools", "light", "ctrl+j", "alt+enter"} {
|
|
if !strings.Contains(rendered, want) {
|
|
t.Fatalf("rendered view missing %q:\n%s", want, rendered)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCtrlJInsertsInputNewline(t *testing.T) {
|
|
m := newModel(context.Background(), nil, Options{ModelName: "test-model"})
|
|
m.input.SetValue("hello")
|
|
|
|
next, _ := m.Update(tea.KeyMsg{Type: tea.KeyCtrlJ})
|
|
got := next.(model).input.Value()
|
|
if got != "hello\n" {
|
|
t.Fatalf("input value = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestInputStartsAtOneLineAndGrows(t *testing.T) {
|
|
m := newModel(context.Background(), nil, Options{ModelName: "test-model"})
|
|
if got := m.input.Height(); got != 1 {
|
|
t.Fatalf("initial input height = %d", got)
|
|
}
|
|
|
|
m.input.SetValue("one\ntwo\nthree")
|
|
m.afterInputChanged()
|
|
if got := m.input.Height(); got != 3 {
|
|
t.Fatalf("multiline input height = %d", got)
|
|
}
|
|
|
|
m.input.SetValue(strings.Repeat("line\n", 20))
|
|
m.afterInputChanged()
|
|
if got := m.input.Height(); got != maxInputLines {
|
|
t.Fatalf("clamped input height = %d", got)
|
|
}
|
|
}
|
|
|
|
func TestSlashCommandSuggestions(t *testing.T) {
|
|
m := newModel(context.Background(), nil, Options{ModelName: "test-model", Yolo: true})
|
|
next, _ := m.Update(tea.WindowSizeMsg{Width: 100, Height: 30})
|
|
m = next.(model)
|
|
m.input.SetValue("/")
|
|
m.afterInputChanged()
|
|
|
|
rendered := m.View()
|
|
for _, want := range []string{"/clear reset context", "/exit quit", "/model model/provider/thinking"} {
|
|
if !strings.Contains(rendered, want) {
|
|
t.Fatalf("rendered suggestions missing %q:\n%s", want, rendered)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestModelCommand(t *testing.T) {
|
|
m := newModel(context.Background(), nil, Options{ModelName: "test-model", Yolo: true})
|
|
m.input.SetValue("/model")
|
|
|
|
next, _ := m.submit()
|
|
rendered := next.(model).renderMessages()
|
|
for _, want := range []string{"model: test-model", "mode: yolo tools", "theme: light"} {
|
|
if !strings.Contains(rendered, want) {
|
|
t.Fatalf("model command output missing %q:\n%s", want, rendered)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestModelThinkingCommand(t *testing.T) {
|
|
manager := &fakeModelManager{model: "test-model", thinking: "none"}
|
|
m := newModel(context.Background(), nil, Options{ModelName: "test-model", ModelManager: manager})
|
|
m.input.SetValue("/model thinking high")
|
|
|
|
next, _ := m.submit()
|
|
rendered := next.(model).renderMessages()
|
|
if manager.thinking != "high" {
|
|
t.Fatalf("thinking = %q", manager.thinking)
|
|
}
|
|
if !strings.Contains(rendered, "thinking: high") {
|
|
t.Fatalf("model command output missing thinking:\n%s", rendered)
|
|
}
|
|
}
|
|
|
|
type fakeModelManager struct {
|
|
provider string
|
|
model string
|
|
thinking string
|
|
}
|
|
|
|
func (f *fakeModelManager) CurrentProvider() string {
|
|
if f.provider == "" {
|
|
return "default"
|
|
}
|
|
return f.provider
|
|
}
|
|
|
|
func (f *fakeModelManager) CurrentModel() string {
|
|
return f.model
|
|
}
|
|
|
|
func (f *fakeModelManager) CurrentThinking() string {
|
|
return f.thinking
|
|
}
|
|
|
|
func (f *fakeModelManager) Info() string {
|
|
return "provider: " + f.CurrentProvider() + "\nmodel: " + f.model + "\nthinking: " + f.thinking
|
|
}
|
|
|
|
func (f *fakeModelManager) SetProvider(name string) (string, error) {
|
|
f.provider = name
|
|
return f.Info(), nil
|
|
}
|
|
|
|
func (f *fakeModelManager) SetModel(model string) (string, error) {
|
|
f.model = model
|
|
return f.Info(), nil
|
|
}
|
|
|
|
func (f *fakeModelManager) SetThinking(level string) (string, error) {
|
|
f.thinking = level
|
|
return f.Info(), nil
|
|
}
|
|
|
|
func TestParseThemeMode(t *testing.T) {
|
|
for input, want := range map[string]ThemeMode{
|
|
"": ThemeLight,
|
|
"light": ThemeLight,
|
|
"dark": ThemeDark,
|
|
"DARK": ThemeDark,
|
|
} {
|
|
got, err := ParseThemeMode(input)
|
|
if err != nil {
|
|
t.Fatalf("ParseThemeMode(%q): %v", input, err)
|
|
}
|
|
if got != want {
|
|
t.Fatalf("ParseThemeMode(%q) = %q, want %q", input, got, want)
|
|
}
|
|
}
|
|
|
|
if _, err := ParseThemeMode("sepia"); err == nil {
|
|
t.Fatal("expected invalid theme error")
|
|
}
|
|
}
|
|
|
|
func TestCleanToolLog(t *testing.T) {
|
|
got := cleanToolLog("\n[tool] shell_run {\"command\":\"pwd\"}\n")
|
|
want := `shell_run {"command":"pwd"}`
|
|
if got != want {
|
|
t.Fatalf("got %q want %q", got, want)
|
|
}
|
|
}
|