ai-agent/internal/tui/history_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

230 lines
6.3 KiB
Go

package tui
import "testing"
func TestPushHistory_Basic(t *testing.T) {
m := newTestModel(t)
m.pushHistory("hello")
m.pushHistory("world")
if len(m.promptHistory) != 2 {
t.Fatalf("expected 2 history entries, got %d", len(m.promptHistory))
}
if m.promptHistory[0] != "hello" || m.promptHistory[1] != "world" {
t.Errorf("unexpected history: %v", m.promptHistory)
}
}
func TestPushHistory_Empty(t *testing.T) {
m := newTestModel(t)
m.pushHistory("")
if len(m.promptHistory) != 0 {
t.Error("empty string should not be added to history")
}
}
func TestPushHistory_DedupConsecutive(t *testing.T) {
m := newTestModel(t)
m.pushHistory("hello")
m.pushHistory("hello")
m.pushHistory("hello")
if len(m.promptHistory) != 1 {
t.Errorf("expected 1 entry after dedup, got %d", len(m.promptHistory))
}
}
func TestPushHistory_DedupNonConsecutive(t *testing.T) {
m := newTestModel(t)
m.pushHistory("hello")
m.pushHistory("world")
m.pushHistory("hello")
if len(m.promptHistory) != 3 {
t.Errorf("non-consecutive duplicates should be kept, got %d", len(m.promptHistory))
}
}
func TestPushHistory_CapAt100(t *testing.T) {
m := newTestModel(t)
for i := 0; i < 110; i++ {
m.pushHistory(string(rune('a' + i%26)) + string(rune('0'+i/26)))
}
if len(m.promptHistory) > 100 {
t.Errorf("history should be capped at 100, got %d", len(m.promptHistory))
}
}
func TestNavigateHistory_EmptyHistory(t *testing.T) {
m := newTestModel(t)
if m.navigateHistory(-1) {
t.Error("up on empty history should return false")
}
if m.navigateHistory(1) {
t.Error("down on empty history should return false")
}
}
func TestNavigateHistory_UpDown(t *testing.T) {
m := newTestModel(t)
m.pushHistory("first")
m.pushHistory("second")
m.pushHistory("third")
// Set current input
m.input.SetValue("current")
// Press up: should go to "third" (most recent)
if !m.navigateHistory(-1) {
t.Fatal("up should succeed")
}
if m.input.Value() != "third" {
t.Errorf("expected 'third', got %q", m.input.Value())
}
if m.historySaved != "current" {
t.Errorf("current input should be saved, got %q", m.historySaved)
}
// Press up again: "second"
if !m.navigateHistory(-1) {
t.Fatal("up should succeed")
}
if m.input.Value() != "second" {
t.Errorf("expected 'second', got %q", m.input.Value())
}
// Press up again: "first"
if !m.navigateHistory(-1) {
t.Fatal("up should succeed")
}
if m.input.Value() != "first" {
t.Errorf("expected 'first', got %q", m.input.Value())
}
// Press up again: at oldest, should fail
if m.navigateHistory(-1) {
t.Error("up at oldest should return false")
}
// Press down: "second"
if !m.navigateHistory(1) {
t.Fatal("down should succeed")
}
if m.input.Value() != "second" {
t.Errorf("expected 'second', got %q", m.input.Value())
}
// Press down: "third"
if !m.navigateHistory(1) {
t.Fatal("down should succeed")
}
if m.input.Value() != "third" {
t.Errorf("expected 'third', got %q", m.input.Value())
}
// Press down past newest: restore saved input
if !m.navigateHistory(1) {
t.Fatal("down past newest should succeed")
}
if m.input.Value() != "current" {
t.Errorf("expected restored 'current', got %q", m.input.Value())
}
if m.historyIndex != -1 {
t.Errorf("historyIndex should be -1 after exiting history, got %d", m.historyIndex)
}
}
func TestNavigateHistory_DownNotBrowsing(t *testing.T) {
m := newTestModel(t)
m.pushHistory("hello")
// Down without first pressing up should return false
if m.navigateHistory(1) {
t.Error("down when not browsing should return false")
}
}
func TestHistoryKey_OnlyWhenIdleAndEmpty(t *testing.T) {
t.Run("idle_empty_with_history", func(t *testing.T) {
m := newTestModel(t)
m.pushHistory("hello")
m.state = StateIdle
m.overlay = OverlayNone
updated, _ := m.Update(upKey())
m = updated.(*Model)
if m.input.Value() != "hello" {
t.Errorf("up key should navigate history, got %q", m.input.Value())
}
})
t.Run("idle_nonempty_no_history", func(t *testing.T) {
m := newTestModel(t)
m.pushHistory("hello")
m.state = StateIdle
m.overlay = OverlayNone
m.input.SetValue("typing something")
updated, _ := m.Update(upKey())
m = updated.(*Model)
// Should NOT navigate history when input has content and not already browsing
if m.historyIndex != -1 {
t.Error("up key should not navigate history when input is non-empty")
}
})
t.Run("waiting_no_history", func(t *testing.T) {
m := newTestModel(t)
m.pushHistory("hello")
m.state = StateWaiting
updated, _ := m.Update(upKey())
m = updated.(*Model)
if m.historyIndex != -1 {
t.Error("up key should not navigate history when not idle")
}
})
t.Run("overlay_no_history", func(t *testing.T) {
m := newTestModel(t)
m.pushHistory("hello")
m.state = StateIdle
m.overlay = OverlayHelp
updated, _ := m.Update(upKey())
m = updated.(*Model)
if m.historyIndex != -1 {
t.Error("up key should not navigate history when overlay is open")
}
})
}
func TestHistoryKey_AlreadyBrowsing(t *testing.T) {
m := newTestModel(t)
m.pushHistory("first")
m.pushHistory("second")
m.state = StateIdle
m.overlay = OverlayNone
// Input must be empty to start browsing
m.input.SetValue("")
// Press up — enters history (input is empty, so allowed)
updated, _ := m.Update(upKey())
m = updated.(*Model)
if m.input.Value() != "second" {
t.Fatalf("expected 'second', got %q", m.input.Value())
}
// Now input is non-empty (from history), up should still work because historyIndex != -1
updated, _ = m.Update(upKey())
m = updated.(*Model)
if m.input.Value() != "first" {
t.Errorf("expected 'first', got %q", m.input.Value())
}
}