1package vt
2
3import (
4 "testing"
5)
6
7func TestESCSequences(t *testing.T) {
8 tests := []struct {
9 name string
10 run func(t *testing.T)
11 }{
12 {
13 name: "ESC = (DECKPAM) is ignored, following text is printable",
14 run: func(t *testing.T) {
15 s := NewScreen(10, 2)
16 s.Write([]byte("\x1b=abc"))
17
18 assertCell(t, s, 0, 0, 'a')
19 assertCell(t, s, 1, 0, 'b')
20 assertCell(t, s, 2, 0, 'c')
21 if s.Col != 3 || s.Row != 0 {
22 t.Errorf("cursor at row=%d col=%d, want row=0 col=3", s.Row, s.Col)
23 }
24 },
25 },
26 {
27 name: "ESC 7 / ESC 8 save and restore the cursor",
28 run: func(t *testing.T) {
29 s := NewScreen(10, 3)
30 s.Write([]byte("abc\x1b7\x1b[2;5HZZ\x1b8X"))
31
32 assertCell(t, s, 0, 0, 'a')
33 assertCell(t, s, 1, 0, 'b')
34 assertCell(t, s, 2, 0, 'c')
35 assertCell(t, s, 3, 0, 'X')
36 assertCell(t, s, 4, 1, 'Z')
37 assertCell(t, s, 5, 1, 'Z')
38 if s.Row != 0 || s.Col != 4 {
39 t.Errorf("cursor at row=%d col=%d, want row=0 col=4", s.Row, s.Col)
40 }
41 },
42 },
43 {
44 name: "ESC 7 / ESC 8 restore origin and insert modes",
45 run: func(t *testing.T) {
46 s := NewScreen(6, 5)
47 s.Write([]byte("\x1b[2;4r\x1b[?6h\x1b[4h\x1b7"))
48 s.Write([]byte("\x1b[?6l\x1b[4l\x1b8"))
49
50 if !s.originMode {
51 t.Fatal("origin mode was not restored")
52 }
53 if !s.insertMode {
54 t.Fatal("insert mode was not restored")
55 }
56
57 s.Write([]byte("\x1b[1;1Habcd\x1b[1;3HXY"))
58 if s.Row != 1 || s.Col != 4 {
59 t.Fatalf("cursor after restored origin and insert modes = (%d,%d), want (1,4)", s.Row, s.Col)
60 }
61 if got := lineText(s, 1); got != "abXYcd" {
62 t.Fatalf("line after restored insert mode = %q, want %q", got, "abXYcd")
63 }
64 },
65 },
66 {
67 name: "ESC D (index), ESC E (next line), ESC M (reverse index)",
68 run: func(t *testing.T) {
69 s := NewScreen(5, 3)
70 s.Write([]byte("A\x1bDB\x1bEC"))
71
72 assertCell(t, s, 0, 0, 'A')
73 assertCell(t, s, 1, 1, 'B')
74 assertCell(t, s, 0, 2, 'C')
75
76 s = NewScreen(5, 3)
77 s.Write([]byte("\x1b[2;1Hmid\r\x1bMtop"))
78 assertCell(t, s, 0, 0, 't')
79 assertCell(t, s, 0, 1, 'm')
80 },
81 },
82 }
83
84 for _, tt := range tests {
85 t.Run(tt.name, tt.run)
86 }
87}
88
89func TestScreenReportsSynchronizedUpdateMode(t *testing.T) {
90 s := NewScreen(10, 2)
91 s.Write([]byte("\x1b[?2026h"))
92 if !s.SyncUpdateActive() {
93 t.Fatal("sync update mode should be active")
94 }
95 s.Write([]byte("\x1b[?2026l"))
96 if s.SyncUpdateActive() {
97 t.Fatal("sync update mode should be inactive")
98 }
99}
100
101func TestForceSyncEnd(t *testing.T) {
102 s := NewScreen(10, 2)
103 s.Write([]byte("\x1b[?2026h"))
104 if !s.SyncUpdateActive() {
105 t.Fatal("sync update mode should be active before forcing end")
106 }
107 s.ForceSyncEnd()
108 if s.SyncUpdateActive() {
109 t.Fatal("sync update mode should be inactive after forcing end")
110 }
111}
112
113func TestScreenCursorAndMouseStateAccessors(t *testing.T) {
114 tests := []struct {
115 name string
116 seq string
117 check func(t *testing.T, s *Screen)
118 }{
119 {
120 name: "cursor position and visibility are exposed",
121 seq: "\x1b[2;3H\x1b[?25l",
122 check: func(t *testing.T, s *Screen) {
123 if s.CursorRow() != 1 || s.CursorCol() != 2 {
124 t.Fatalf("cursor = row %d col %d, want row 1 col 2", s.CursorRow(), s.CursorCol())
125 }
126 if s.CursorVisible() {
127 t.Fatal("cursor should be hidden")
128 }
129 s.Write([]byte("\x1b[?25h"))
130 if !s.CursorVisible() {
131 t.Fatal("cursor should be visible")
132 }
133 },
134 },
135 {
136 name: "mouse mode and SGR are exposed",
137 seq: "\x1b[?1002h\x1b[?1006h",
138 check: func(t *testing.T, s *Screen) {
139 mode, sgr := s.MouseMode()
140 if mode != 1002 || !sgr {
141 t.Fatalf("MouseMode() = (%d, %v), want (1002, true)", mode, sgr)
142 }
143 },
144 },
145 {
146 name: "reset restores cursor and mouse defaults",
147 seq: "\x1b[?25l\x1b[?1003h\x1b[?1006h\x1bc",
148 check: func(t *testing.T, s *Screen) {
149 mode, sgr := s.MouseMode()
150 if !s.CursorVisible() || mode != 0 || sgr {
151 t.Fatalf("after reset CursorVisible=%v MouseMode=(%d,%v), want true,(0,false)", s.CursorVisible(), mode, sgr)
152 }
153 },
154 },
155 }
156 for _, tt := range tests {
157 t.Run(tt.name, func(t *testing.T) {
158 s := NewScreen(10, 4)
159 s.Write([]byte(tt.seq))
160 tt.check(t, s)
161 })
162 }
163}
164
165func TestScreenMouseModeDisableInactiveIsNoOp(t *testing.T) {
166 s := NewScreen(10, 2)
167 s.Write([]byte("\x1b[?1002h"))
168 s.Write([]byte("\x1b[?1000l"))
169 mode, _ := s.MouseMode()
170 if mode != 1002 {
171 t.Fatalf("mouse mode after disabling inactive 1000 = %d, want 1002", mode)
172 }
173 s.Write([]byte("\x1b[?1002l"))
174 mode, _ = s.MouseMode()
175 if mode != 0 {
176 t.Fatalf("mouse mode after disabling active 1002 = %d, want 0", mode)
177 }
178}
179
180func TestScreenCursorStyleDECSCUSR(t *testing.T) {
181 tests := []struct {
182 name string
183 seq string
184 wantStyle int
185 wantSet bool
186 }{
187 {name: "explicit style", seq: "\x1b[5 q", wantStyle: 5, wantSet: true},
188 {name: "blank style parameter", seq: "\x1b[ q", wantStyle: 0, wantSet: true},
189 {name: "invalid low style is ignored", seq: "\x1b[-1 q", wantStyle: 0, wantSet: false},
190 {name: "invalid high style is ignored", seq: "\x1b[7 q", wantStyle: 0, wantSet: false},
191 {name: "XTVERSION is ignored", seq: "\x1b[>0q", wantStyle: 0, wantSet: false},
192 }
193 for _, tt := range tests {
194 t.Run(tt.name, func(t *testing.T) {
195 s := NewScreen(10, 2)
196 s.Write([]byte(tt.seq))
197 style, set := s.CursorStyle()
198 if style != tt.wantStyle || set != tt.wantSet {
199 t.Fatalf("CursorStyle() = (%d, %v), want (%d, %v)", style, set, tt.wantStyle, tt.wantSet)
200 }
201 })
202 }
203}
204
205func TestScreenInvalidCursorStyleDoesNotOverwriteCurrentStyle(t *testing.T) {
206 s := NewScreen(10, 2)
207 s.Write([]byte("\x1b[5 q"))
208 s.Write([]byte("\x1b[99 q"))
209 style, set := s.CursorStyle()
210 if style != 5 || !set {
211 t.Fatalf("CursorStyle() = (%d, %v), want (5, true)", style, set)
212 }
213}
214
215func TestScreenAltScreenActiveAccessor(t *testing.T) {
216 s := NewScreen(10, 2)
217 if s.AltScreenActive() {
218 t.Fatal("alt screen should start inactive")
219 }
220 s.Write([]byte("\x1b[?1049h"))
221 if !s.AltScreenActive() {
222 t.Fatal("alt screen should be active")
223 }
224 s.Write([]byte("\x1b[?1049l"))
225 if s.AltScreenActive() {
226 t.Fatal("alt screen should be inactive after exit")
227 }
228}