1package modes
2
3import (
4 "testing"
5
6 "github.com/bnema/zut/packages/core"
7 "github.com/bnema/zut/packages/provider"
8 "github.com/bnema/zut/packages/tui"
9)
10
11func TestInputHistoryUsesUpDown(t *testing.T) {
12 ag := core.NewAgent(nil, "", "", nil)
13 ag.SetMessages([]provider.Message{
14 {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "first"}}},
15 {Role: provider.RoleAssistant, Content: []provider.Content{provider.TextBlock{Text: "reply"}}},
16 {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "second"}}},
17 })
18 i := &Interactive{
19 agent: ag,
20 ed: tui.NewEditor(""),
21 inputHistoryIndex: -1,
22 }
23
24 if !i.handleInputHistoryKey(tui.Key{Kind: tui.KeyUp}) {
25 t.Fatal("expected Up to enter history")
26 }
27 if got := i.ed.Value(); got != "second" {
28 t.Fatalf("Up loaded %q, want newest history item", got)
29 }
30
31 if !i.handleInputHistoryKey(tui.Key{Kind: tui.KeyUp}) {
32 t.Fatal("expected repeated Up to stay in history")
33 }
34 if got := i.ed.Value(); got != "first" {
35 t.Fatalf("second Up loaded %q, want older history item", got)
36 }
37
38 if !i.handleInputHistoryKey(tui.Key{Kind: tui.KeyDown}) {
39 t.Fatal("expected Down to move forward in history")
40 }
41 if got := i.ed.Value(); got != "second" {
42 t.Fatalf("Down loaded %q, want newer history item", got)
43 }
44
45 if !i.handleInputHistoryKey(tui.Key{Kind: tui.KeyDown}) {
46 t.Fatal("expected Down at newest item to clear editor")
47 }
48 if got := i.ed.Value(); got != "" {
49 t.Fatalf("Down past newest loaded %q, want empty editor", got)
50 }
51}
52
53func TestInputHistorySkipsShellEscapeContext(t *testing.T) {
54 ag := core.NewAgent(nil, "", "", nil)
55 ag.SetMessages([]provider.Message{
56 {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "prompt"}}},
57 {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "$ pwd\n\n/tmp\n\n[exit 0]"}}, Meta: map[string]string{shellEscapeMetaKey: "true"}},
58 })
59 i := &Interactive{agent: ag}
60
61 history := i.inputHistory()
62 if len(history) != 1 || history[0] != "prompt" {
63 t.Fatalf("inputHistory() = %q, want [prompt]", history)
64 }
65}
66
67func TestInputHistorySkipsCompactionContext(t *testing.T) {
68 ag := core.NewAgent(nil, "", "", nil)
69 ag.SetMessages([]provider.Message{
70 {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "prompt"}}},
71 {Role: provider.RoleUser, Meta: map[string]string{"compaction": "true"}, Content: []provider.Content{provider.TextBlock{Text: "## Context Summary (compacted)"}}},
72 {Role: provider.RoleUser, Meta: map[string]string{autoCompactContinueMetaKey: "true"}, Content: []provider.Content{provider.TextBlock{Text: "resume internally"}}},
73 {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "new prompt"}}},
74 })
75 i := &Interactive{agent: ag}
76
77 history := i.inputHistory()
78 if len(history) != 2 || history[0] != "prompt" || history[1] != "new prompt" {
79 t.Fatalf("inputHistory() = %q, want [prompt new prompt]", history)
80 }
81}
82
83func TestJumpTargetsSkipInternalUserContext(t *testing.T) {
84 msgs := []provider.Message{
85 {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "prompt"}}},
86 {Role: provider.RoleAssistant, Content: []provider.Content{provider.ToolCallBlock{ID: "call-1", Name: "read"}}},
87 {Role: provider.RoleTool, Content: []provider.Content{provider.ToolResultBlock{CallID: "call-1"}}},
88 {Role: provider.RoleUser, Meta: map[string]string{"compaction": "true"}, Content: []provider.Content{provider.TextBlock{Text: "## Context Summary (compacted)"}}},
89 {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "new prompt"}}},
90 }
91
92 targets := buildJumpTargets(msgs)
93 if len(targets) != 2 {
94 t.Fatalf("jump target count = %d, want 2", len(targets))
95 }
96 if targets[0].TurnNo != 1 || targets[0].ToolCount != 1 || targets[1].TurnNo != 2 {
97 t.Fatalf("jump targets = %+v, want real turns and tool count", targets)
98 }
99}
100
101func TestInputHistoryNoLongerUsesLeftRight(t *testing.T) {
102 ag := core.NewAgent(nil, "", "", nil)
103 ag.SetMessages([]provider.Message{
104 {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "prompt"}}},
105 })
106 i := &Interactive{
107 agent: ag,
108 ed: tui.NewEditor(""),
109 inputHistoryIndex: -1,
110 }
111
112 if i.handleInputHistoryKey(tui.Key{Kind: tui.KeyLeft}) {
113 t.Fatal("Left should not browse input history")
114 }
115 if i.handleInputHistoryKey(tui.Key{Kind: tui.KeyRight}) {
116 t.Fatal("Right should not browse input history")
117 }
118 if got := i.ed.Value(); got != "" {
119 t.Fatalf("left/right changed editor to %q", got)
120 }
121}
122
123func TestInputHistoryDoesNotStealNonEmptyEditor(t *testing.T) {
124 ag := core.NewAgent(nil, "", "", nil)
125 ag.SetMessages([]provider.Message{
126 {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "prompt"}}},
127 })
128 i := &Interactive{
129 agent: ag,
130 ed: tui.NewEditor(""),
131 inputHistoryIndex: -1,
132 }
133 i.ed.SetValue("draft")
134
135 if i.handleInputHistoryKey(tui.Key{Kind: tui.KeyUp}) {
136 t.Fatal("Up should not browse history while editing a draft")
137 }
138 if got := i.ed.Value(); got != "draft" {
139 t.Fatalf("editor changed to %q, want draft", got)
140 }
141}