1package modes
2
3import (
4 "fmt"
5 "reflect"
6 "strings"
7 "testing"
8
9 "github.com/patriceckhart/zot/packages/agent/tools"
10 "github.com/patriceckhart/zot/packages/core"
11 "github.com/patriceckhart/zot/packages/provider"
12 "github.com/patriceckhart/zot/packages/tui"
13)
14
15func TestJumpRendersSelectedTurn(t *testing.T) {
16 for _, tc := range []struct {
17 name string
18 tailLimit, turn int
19 filtered bool
20 }{
21 {"picker", 0, 1, false},
22 {"resumed older turn", 80, 10, false},
23 {"filtered shortcut", 80, 50, true},
24 } {
25 t.Run(tc.name, func(t *testing.T) {
26 term := &cleanupTestTerminal{}
27 agent := core.NewAgent(nil, "test", "", nil)
28 var msgs []provider.Message
29 for n := 1; n <= 100; n++ {
30 msgs = append(msgs, provider.Message{Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: fmt.Sprintf("prompt-number-%03d", n)}}})
31 }
32 agent.SetMessages(msgs)
33 i := NewInteractive(InteractiveConfig{Terminal: term, Agent: agent, Sandbox: tools.NewSandbox(t.TempDir())})
34 i.rend.Resize(80, 24)
35 i.view.TailLimit = tc.tailLimit
36 i.redraw()
37 targetText := fmt.Sprintf("prompt-number-%03d", tc.turn)
38 if tc.filtered {
39 term.Reset()
40 i.openJumpDialog([]string{targetText})
41 } else {
42 i.openJumpDialog(nil)
43 i.redraw()
44 target := i.jumpDialog.Targets()[tc.turn-1]
45 i.jumpDialog.Close()
46 term.Reset()
47 i.applyJumpSelection(target.MessageIdx, target.TurnNo)
48 }
49 i.redraw()
50 output := term.String()
51 if !strings.Contains(output, tui.SeqAltScreenOn) || !strings.Contains(output, targetText) || !strings.Contains(output, fmt.Sprintf("viewing turn %d of 100", tc.turn)) {
52 t.Fatalf("selected turn not rendered in history: %q", output)
53 }
54 if strings.Contains(output, "prompt-number-100") {
55 t.Fatal("history still displays the live tail")
56 }
57 if !reflect.DeepEqual(agent.Messages(), msgs) {
58 t.Fatal("jump modified the transcript")
59 }
60 // Appending a completed turn must not move the history viewport.
61 beforeChat := i.cachedChatLocked(80)
62 top := len(beforeChat) - i.scrollOffset - i.prevChatRows
63 _, anchors := i.view.BuildWithAnchors(80)
64 if top != anchors[tc.turn-1].Row {
65 t.Fatalf("jump top = %d, want target row %d", top, anchors[tc.turn-1].Row)
66 }
67 agent.SetMessages(append(msgs, provider.Message{Role: provider.RoleUser, Content: []provider.Content{provider.TextBlock{Text: "new output"}}}))
68 i.redraw()
69 afterChat := i.cachedChatLocked(80)
70 if got := len(afterChat) - i.scrollOffset - i.prevChatRows; got != top {
71 t.Fatalf("history moved while output arrived: %d to %d", top, got)
72 }
73 term.Reset()
74 for pages := 0; i.scrollOffset > 0 && pages < 100; pages++ {
75 i.scrollBy(-i.chatPage())
76 i.redraw()
77 }
78 if i.scrollOffset != 0 {
79 t.Fatal("page down did not reach the live tail")
80 }
81 if !strings.Contains(term.String(), tui.SeqAltScreenOff) || !strings.Contains(term.String(), "new output") {
82 t.Fatal("return to live tail did not restore main screen with new output")
83 }
84 })
85 }
86}