1package modes
2
3import (
4 "testing"
5
6 "github.com/patriceckhart/zot/packages/core"
7 "github.com/patriceckhart/zot/packages/provider"
8 "github.com/patriceckhart/zot/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 TestInputHistoryRecallsShellCommandWithBang(t *testing.T) {
68 for _, cmd := range []string{"pwd", "printf 'first\\n'\nprintf 'second\\n'"} {
69 t.Run(cmd, func(t *testing.T) {
70 ag := core.NewAgent(nil, "", "", nil)
71 ag.SetMessages([]provider.Message{
72 {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "prompt"}}},
73 {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "$ " + cmd + "\n\noutput\n\n[exit 0]"}}, Meta: map[string]string{"shell_escape": "true", "shell_escape_command": cmd}},
74 })
75 i := &Interactive{agent: ag, ed: tui.NewEditor(""), inputHistoryIndex: -1}
76 for _, step := range []struct {
77 key tui.KeyKind
78 want string
79 }{
80 {tui.KeyUp, "!" + cmd},
81 {tui.KeyUp, "prompt"},
82 {tui.KeyDown, "!" + cmd},
83 {tui.KeyDown, ""},
84 } {
85 if !i.handleInputHistoryKey(tui.Key{Kind: step.key}) {
86 t.Fatal("expected history navigation")
87 }
88 if got := i.ed.Value(); got != step.want {
89 t.Fatalf("editor = %q, want %q", got, step.want)
90 }
91 }
92 })
93 }
94}
95
96func TestInputHistoryNoLongerUsesLeftRight(t *testing.T) {
97 ag := core.NewAgent(nil, "", "", nil)
98 ag.SetMessages([]provider.Message{
99 {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "prompt"}}},
100 })
101 i := &Interactive{
102 agent: ag,
103 ed: tui.NewEditor(""),
104 inputHistoryIndex: -1,
105 }
106
107 if i.handleInputHistoryKey(tui.Key{Kind: tui.KeyLeft}) {
108 t.Fatal("Left should not browse input history")
109 }
110 if i.handleInputHistoryKey(tui.Key{Kind: tui.KeyRight}) {
111 t.Fatal("Right should not browse input history")
112 }
113 if got := i.ed.Value(); got != "" {
114 t.Fatalf("left/right changed editor to %q", got)
115 }
116}
117
118func TestInputHistoryDoesNotStealNonEmptyEditor(t *testing.T) {
119 ag := core.NewAgent(nil, "", "", nil)
120 ag.SetMessages([]provider.Message{
121 {Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "prompt"}}},
122 })
123 i := &Interactive{
124 agent: ag,
125 ed: tui.NewEditor(""),
126 inputHistoryIndex: -1,
127 }
128 i.ed.SetValue("draft")
129
130 if i.handleInputHistoryKey(tui.Key{Kind: tui.KeyUp}) {
131 t.Fatal("Up should not browse history while editing a draft")
132 }
133 if got := i.ed.Value(); got != "draft" {
134 t.Fatalf("editor changed to %q, want draft", got)
135 }
136}