ai-agent/internal/tui/planform_test.go
admin 8dc496b626
Some checks failed
CI / test (push) Has been cancelled
Release / release (push) Failing after 4m36s
first commit
2026-03-08 15:40:34 +07:00

265 lines
7.5 KiB
Go

package tui
import (
"strings"
"testing"
)
func TestPlanForm_NewPrefilled(t *testing.T) {
pf := NewPlanFormState("refactor auth module")
if len(pf.Fields) != 3 {
t.Fatalf("expected 3 fields, got %d", len(pf.Fields))
}
// Task field should be pre-filled.
if pf.Fields[0].Input.Value() != "refactor auth module" {
t.Errorf("task field should be pre-filled, got %q", pf.Fields[0].Input.Value())
}
// Scope field should be select with 3 options.
if pf.Fields[1].Kind != "select" {
t.Errorf("scope field should be select, got %q", pf.Fields[1].Kind)
}
if len(pf.Fields[1].Options) != 3 {
t.Errorf("scope should have 3 options, got %d", len(pf.Fields[1].Options))
}
// Focus field should be text.
if pf.Fields[2].Kind != "text" {
t.Errorf("focus field should be text, got %q", pf.Fields[2].Kind)
}
}
func TestPlanForm_AssemblePrompt(t *testing.T) {
pf := NewPlanFormState("build a REST API")
pf.Fields[1].OptionIndex = 1 // "module"
pf.Fields[2].Input.SetValue("keep backward compat")
prompt := pf.AssemblePrompt()
if !strings.Contains(prompt, "build a REST API") {
t.Error("prompt should contain task")
}
if !strings.Contains(prompt, "module") {
t.Error("prompt should contain scope")
}
if !strings.Contains(prompt, "keep backward compat") {
t.Error("prompt should contain focus")
}
if !strings.Contains(prompt, "step-by-step plan") {
t.Error("prompt should contain plan instruction")
}
}
func TestPlanForm_AssemblePrompt_NoFocus(t *testing.T) {
pf := NewPlanFormState("fix the bug")
prompt := pf.AssemblePrompt()
if !strings.Contains(prompt, "fix the bug") {
t.Error("prompt should contain task")
}
if strings.Contains(prompt, "Focus:") {
t.Error("prompt should not contain Focus when empty")
}
}
func TestPlanForm_OpenClose(t *testing.T) {
t.Run("open_sets_overlay", func(t *testing.T) {
m := newTestModel(t)
m.openPlanForm("test task")
if m.overlay != OverlayPlanForm {
t.Errorf("expected OverlayPlanForm, got %d", m.overlay)
}
if m.planFormState == nil {
t.Fatal("planFormState should not be nil")
}
if m.planFormState.Fields[0].Input.Value() != "test task" {
t.Error("task should be pre-filled")
}
})
t.Run("close_resets_state", func(t *testing.T) {
m := newTestModel(t)
m.planFormState = NewPlanFormState("test")
m.overlay = OverlayPlanForm
m.closePlanForm()
if m.planFormState != nil {
t.Error("planFormState should be nil after close")
}
if m.overlay != OverlayNone {
t.Errorf("overlay should be OverlayNone, got %d", m.overlay)
}
})
}
func TestPlanForm_EscCancels(t *testing.T) {
m := newTestModel(t)
m.openPlanForm("some task")
updated, _ := m.Update(escKey())
m = updated.(*Model)
if m.planFormState != nil {
t.Error("ESC should close plan form")
}
if m.overlay != OverlayNone {
t.Errorf("overlay should be OverlayNone, got %d", m.overlay)
}
}
func TestPlanForm_FieldNavigation(t *testing.T) {
m := newTestModel(t)
m.openPlanForm("task")
// Initially on field 0.
if m.planFormState.ActiveField != 0 {
t.Fatalf("expected active field 0, got %d", m.planFormState.ActiveField)
}
// Tab advances to field 1.
updated, _ := m.Update(tabKey())
m = updated.(*Model)
if m.planFormState.ActiveField != 1 {
t.Errorf("expected active field 1, got %d", m.planFormState.ActiveField)
}
// Tab again advances to field 2.
updated, _ = m.Update(tabKey())
m = updated.(*Model)
if m.planFormState.ActiveField != 2 {
t.Errorf("expected active field 2, got %d", m.planFormState.ActiveField)
}
// Tab at last field stays on last field.
updated, _ = m.Update(tabKey())
m = updated.(*Model)
if m.planFormState.ActiveField != 2 {
t.Errorf("expected active field to stay at 2, got %d", m.planFormState.ActiveField)
}
}
func TestPlanForm_SelectFieldLeftRight(t *testing.T) {
m := newTestModel(t)
m.openPlanForm("task")
// Tab to scope field.
updated, _ := m.Update(tabKey())
m = updated.(*Model)
if m.planFormState.ActiveField != 1 {
t.Fatalf("expected field 1, got %d", m.planFormState.ActiveField)
}
if m.planFormState.Fields[1].OptionIndex != 0 {
t.Fatalf("expected option 0, got %d", m.planFormState.Fields[1].OptionIndex)
}
// Right should advance to option 1.
updated, _ = m.Update(rightKey())
m = updated.(*Model)
if m.planFormState.Fields[1].OptionIndex != 1 {
t.Errorf("expected option 1 after right, got %d", m.planFormState.Fields[1].OptionIndex)
}
// Left should go back to option 0.
updated, _ = m.Update(leftKey())
m = updated.(*Model)
if m.planFormState.Fields[1].OptionIndex != 0 {
t.Errorf("expected option 0 after left, got %d", m.planFormState.Fields[1].OptionIndex)
}
}
func TestPlanForm_SelectFieldBounds(t *testing.T) {
m := newTestModel(t)
m.openPlanForm("task")
// Tab to scope field.
updated, _ := m.Update(tabKey())
m = updated.(*Model)
// Up at 0 stays at 0.
updated, _ = m.Update(upKey())
m = updated.(*Model)
if m.planFormState.Fields[1].OptionIndex != 0 {
t.Errorf("expected option 0 after up at boundary, got %d", m.planFormState.Fields[1].OptionIndex)
}
// Left at 0 stays at 0.
updated, _ = m.Update(leftKey())
m = updated.(*Model)
if m.planFormState.Fields[1].OptionIndex != 0 {
t.Errorf("expected option 0 after left at boundary, got %d", m.planFormState.Fields[1].OptionIndex)
}
// Navigate to last option.
updated, _ = m.Update(downKey())
m = updated.(*Model)
updated, _ = m.Update(downKey())
m = updated.(*Model)
if m.planFormState.Fields[1].OptionIndex != 2 {
t.Fatalf("expected option 2, got %d", m.planFormState.Fields[1].OptionIndex)
}
// Down at last stays at last.
updated, _ = m.Update(downKey())
m = updated.(*Model)
if m.planFormState.Fields[1].OptionIndex != 2 {
t.Errorf("expected option 2 after down at boundary, got %d", m.planFormState.Fields[1].OptionIndex)
}
// Right at last stays at last.
updated, _ = m.Update(rightKey())
m = updated.(*Model)
if m.planFormState.Fields[1].OptionIndex != 2 {
t.Errorf("expected option 2 after right at boundary, got %d", m.planFormState.Fields[1].OptionIndex)
}
}
func TestPlanForm_SelectField(t *testing.T) {
m := newTestModel(t)
m.openPlanForm("task")
// Navigate to scope field (index 1).
m.Update(tabKey())
updated, _ := m.Update(tabKey())
m = updated.(*Model)
// Oops, we need to re-get m after first tab. Let me redo:
m2 := newTestModel(t)
m2.openPlanForm("task")
// Tab to scope field.
updated, _ = m2.Update(tabKey())
m2 = updated.(*Model)
if m2.planFormState.ActiveField != 1 {
t.Fatalf("expected field 1, got %d", m2.planFormState.ActiveField)
}
// Down should cycle scope option.
if m2.planFormState.Fields[1].OptionIndex != 0 {
t.Fatalf("expected option 0, got %d", m2.planFormState.Fields[1].OptionIndex)
}
updated, _ = m2.Update(downKey())
m2 = updated.(*Model)
if m2.planFormState.Fields[1].OptionIndex != 1 {
t.Errorf("expected option 1 after down, got %d", m2.planFormState.Fields[1].OptionIndex)
}
}