-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscroll_test.go
More file actions
221 lines (188 loc) · 5.92 KB
/
Copy pathscroll_test.go
File metadata and controls
221 lines (188 loc) · 5.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
package termview
import (
"fmt"
"strings"
"testing"
tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/x/vt"
)
func newTestModel(w, h int) Model {
return Model{emu: vt.NewSafeEmulator(w, h), width: w, height: h}
}
func TestScrollRendering(t *testing.T) {
const w, h = 20, 5
m := newTestModel(w, h)
for i := 1; i <= 12; i++ {
fmt.Fprintf(m.emu, "line%d\r\n", i)
}
// Live screen: last 4 lines plus the empty row the cursor sits on.
live := m.View()
t.Logf("live view:\n%s", live)
if !strings.Contains(live, "line12") {
t.Errorf("live view missing newest line:\n%s", live)
}
if strings.Contains(live, "line1\n") {
t.Errorf("live view should not contain oldest line:\n%s", live)
}
// Scroll up three lines.
m.scrollUp(3)
if m.scrollOffset != 3 {
t.Fatalf("offset = %d, want 3", m.scrollOffset)
}
scrolled := m.View()
t.Logf("scrolled(3) view:\n%s", scrolled)
if got := len(strings.Split(scrolled, "\n")); got != h {
t.Errorf("scrolled view has %d lines, want %d", got, h)
}
// Scrollback holds line1..line8, the screen line9..line12, so an offset of
// three starts the viewport at line6 and mixes in the first screen rows.
if want := "line6\nline7\nline8\nline9\nline10"; scrolled != want {
t.Errorf("scrolled view = %q, want %q", scrolled, want)
}
if strings.Contains(scrolled, "line12") {
t.Errorf("scrolled view should have pushed line12 out:\n%s", scrolled)
}
// Scrolling to the top clamps at the oldest scrollback line.
m.scrollTo(m.maxScrollOffset())
top := m.View()
t.Logf("top view (offset %d):\n%s", m.scrollOffset, top)
if m.scrollOffset != m.emu.ScrollbackLen() {
t.Errorf("top offset = %d, want %d", m.scrollOffset, m.emu.ScrollbackLen())
}
if !strings.Contains(top, "line1\n") {
t.Errorf("top view missing oldest line:\n%s", top)
}
if got := len(strings.Split(top, "\n")); got != h {
t.Errorf("top view has %d lines, want %d", got, h)
}
// Over-scrolling down clamps back to the live screen.
m.scrollDown(1000)
if m.scrollOffset != 0 {
t.Errorf("offset = %d, want 0 after scrolling down past the end", m.scrollOffset)
}
if m.View() != live {
t.Errorf("returning to the bottom did not restore the live view:\n%s", m.View())
}
}
func TestScrollAnchorsOnNewOutput(t *testing.T) {
m := newTestModel(20, 5)
for i := 1; i <= 12; i++ {
fmt.Fprintf(m.emu, "line%d\r\n", i)
}
m.scrollUp(3)
m.lastScrollbackLen = m.emu.ScrollbackLen()
before := m.View()
// Simulate the PTY goroutine pushing two more lines into the scrollback.
fmt.Fprint(m.emu, "new1\r\nnew2\r\n")
growth := m.emu.ScrollbackLen() - m.lastScrollbackLen
m, _ = m.Update(OutputMsg{ID: m.id})
if got := m.View(); got != before {
t.Errorf("view drifted while scrolled up.\nbefore:\n%s\nafter:\n%s", before, got)
}
if m.scrollOffset != 3+growth {
t.Errorf("offset = %d, want %d", m.scrollOffset, 3+growth)
}
}
func TestKeyPressReturnsToBottom(t *testing.T) {
m := newTestModel(20, 5)
for i := 1; i <= 12; i++ {
fmt.Fprintf(m.emu, "line%d\r\n", i)
}
m = m.Focus()
m.scrollUp(3)
// SendKey/SendText write into the emulator's response pipe, which blocks
// without a reader. In the real model terminalViewToPty drains it into the PTY.
emu := m.emu
go func() {
buf := make([]byte, 64)
for {
if _, err := emu.Read(buf); err != nil {
return
}
}
}()
m, _ = m.Update(tea.KeyPressMsg{Code: 'x', Text: "x"})
if m.scrollOffset != 0 {
t.Errorf("offset = %d, want 0 after a key press", m.scrollOffset)
}
}
func TestShiftedPrintableKeyIsSent(t *testing.T) {
m := newTestModel(20, 5).Focus()
emu := m.emu
got := make(chan string, 1)
go func() {
buf := make([]byte, 64)
n, err := emu.Read(buf)
if err != nil {
got <- ""
return
}
got <- string(buf[:n])
}()
// Kitty/enhanced keyboard protocol reports Shift+a as Code 'a' + Text "A".
m, _ = m.Update(tea.KeyPressMsg{Code: 'a', Text: "A", Mod: tea.ModShift})
if out := <-got; out != "A" {
t.Fatalf("pty input = %q, want %q", out, "A")
}
}
func TestPageKeysScroll(t *testing.T) {
m := newTestModel(20, 5)
for i := 1; i <= 12; i++ {
fmt.Fprintf(m.emu, "line%d\r\n", i)
}
m = m.Focus()
m, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyPgUp})
if m.scrollOffset != m.Height() {
t.Fatalf("offset = %d after pgup, want %d", m.scrollOffset, m.Height())
}
m, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyPgDown})
if m.scrollOffset != 0 {
t.Errorf("offset = %d after pgdown, want 0", m.scrollOffset)
}
m = m.Blur()
m, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyPgUp})
if m.scrollOffset != 0 {
t.Errorf("blurred terminal scrolled to %d", m.scrollOffset)
}
}
func TestMouseWheelScrolls(t *testing.T) {
m := newTestModel(20, 5)
for i := 1; i <= 12; i++ {
fmt.Fprintf(m.emu, "line%d\r\n", i)
}
m = m.Focus()
m, _ = m.Update(tea.MouseWheelMsg{Button: tea.MouseWheelUp})
if m.scrollOffset != mouseScrollStep {
t.Fatalf("offset = %d after one wheel up, want %d", m.scrollOffset, mouseScrollStep)
}
m, _ = m.Update(tea.MouseWheelMsg{Button: tea.MouseWheelDown})
if m.scrollOffset != 0 {
t.Errorf("offset = %d after wheeling back down, want 0", m.scrollOffset)
}
m, _ = m.Update(tea.MouseWheelMsg{Button: tea.MouseWheelUp})
if m.scrollOffset != mouseScrollStep {
t.Fatalf("offset = %d after wheel up, want %d", m.scrollOffset, mouseScrollStep)
}
// An unfocused terminal ignores the wheel.
m = m.Blur()
m, _ = m.Update(tea.MouseWheelMsg{Button: tea.MouseWheelUp})
if m.scrollOffset != mouseScrollStep {
t.Errorf("blurred terminal scrolled to %d", m.scrollOffset)
}
}
func TestStyledScrollbackLineKeepsColors(t *testing.T) {
m := newTestModel(20, 5)
fmt.Fprint(m.emu, "\x1b[31mred line\x1b[0m\r\n")
for i := range 12 {
fmt.Fprintf(m.emu, "line%d\r\n", i)
}
m.scrollTo(m.maxScrollOffset())
top := m.View()
if !strings.Contains(top, "red line") {
t.Fatalf("top view missing the styled line:\n%q", top)
}
if !strings.Contains(top, "\x1b[") {
t.Errorf("styled scrollback line lost its attributes:\n%q", top)
}
t.Logf("top view: %q", top)
}