1package modes
2
3import (
4 "context"
5 "encoding/json"
6 "runtime"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/patriceckhart/zot/packages/core"
12 "github.com/patriceckhart/zot/packages/tui"
13)
14
15func TestConfirmToolCallAttachesDiffBeforeDecision(t *testing.T) {
16 dialog := newConfirmDialog()
17 i := &Interactive{
18 toolCalls: map[string]*tui.ToolCallView{
19 "call-1": {ID: "call-1", Name: "edit"},
20 },
21 confirmDialog: dialog,
22 dirty: make(chan struct{}, 1),
23 }
24 decision := make(chan core.ConfirmDecision, 1)
25 go func() {
26 decision <- i.ConfirmToolCall(core.ToolCallConfirmation{
27 ID: "call-1",
28 Name: "edit",
29 Summary: "sample.go",
30 Content: "-old\n+new\n",
31 })
32 }()
33
34 deadline := time.Now().Add(time.Second)
35 for !dialog.Active() && time.Now().Before(deadline) {
36 runtime.Gosched()
37 }
38 if !dialog.Active() {
39 t.Fatal("confirmation dialog did not open")
40 }
41 i.mu.Lock()
42 preview := i.toolCalls["call-1"].Preview
43 i.mu.Unlock()
44 if preview != "-old\n+new\n" {
45 t.Fatalf("tool preview = %q", preview)
46 }
47
48 dialog.HandleKey(tui.Key{Kind: tui.KeyEnter})
49 select {
50 case got := <-decision:
51 if !got.Allow {
52 t.Fatalf("confirmation decision = %+v", got)
53 }
54 case <-time.After(time.Second):
55 t.Fatal("confirmation did not return")
56 }
57}
58
59func TestConfirmDialogAllowsToolExpansion(t *testing.T) {
60 resp := make(chan core.ConfirmDecision, 1)
61 dialog := newConfirmDialog()
62 dialog.Enqueue(&confirmRequest{
63 toolName: "edit",
64 preview: "large edit",
65 resp: resp,
66 })
67 args, err := json.Marshal(map[string]any{
68 "path": "sample.ts",
69 "edits": []map[string]string{{
70 "oldText": "old",
71 "newText": strings.Repeat("const value = 1\n", 20),
72 }},
73 })
74 if err != nil {
75 t.Fatal(err)
76 }
77 view := &tui.View{
78 ToolCalls: []tui.ToolCallView{{
79 ID: "call-1",
80 Name: "edit",
81 RawJSONBuf: string(args),
82 LivePath: "sample.ts",
83 }},
84 }
85 i := &Interactive{
86 view: view,
87 confirmDialog: dialog,
88 dirty: make(chan struct{}, 1),
89 }
90 if rendered := strings.Join(view.Build(80), "\n"); !strings.Contains(rendered, "ctrl+o to expand") {
91 t.Fatalf("test preview was not initially collapsed:\n%s", rendered)
92 }
93
94 i.handleKey(context.Background(), tui.Key{Kind: tui.KeyCtrlO})
95
96 if !i.view.ExpandAll {
97 t.Fatal("ctrl+o did not expand tool previews while confirmation was active")
98 }
99 if rendered := strings.Join(view.Build(80), "\n"); strings.Contains(rendered, "ctrl+o to expand") {
100 t.Fatalf("live edit preview remained collapsed after ctrl+o:\n%s", rendered)
101 }
102 if !dialog.Active() {
103 t.Fatal("ctrl+o closed the confirmation dialog")
104 }
105 select {
106 case decision := <-resp:
107 t.Fatalf("ctrl+o unexpectedly answered confirmation: %+v", decision)
108 default:
109 }
110
111 i.handleKey(context.Background(), tui.Key{Kind: tui.KeyCtrlO})
112 if i.view.ExpandAll {
113 t.Fatal("second ctrl+o did not collapse tool previews during confirmation")
114 }
115 if rendered := strings.Join(view.Build(80), "\n"); !strings.Contains(rendered, "ctrl+o to expand") {
116 t.Fatalf("live edit preview remained expanded after second ctrl+o:\n%s", rendered)
117 }
118 if !dialog.Active() {
119 t.Fatal("second ctrl+o closed the confirmation dialog")
120 }
121}