1package vt
2
3import (
4 "bytes"
5 "testing"
6
7 renderer "github.com/bnema/vev-vt/core"
8)
9
10func TestScreenAutoWrapModeRightMargin(t *testing.T) {
11 tests := []struct {
12 name string
13 width int
14 height int
15 input string
16 wantRows []string
17 wantRow int
18 wantCol int
19 wantHistory int
20 }{
21 {
22 name: "successive writes overwrite right margin",
23 width: 4, height: 2, input: "\x1b[?7labcde",
24 wantRows: []string{"abce", " "}, wantRow: 0, wantCol: 3,
25 },
26 {
27 name: "bottom row does not scroll",
28 width: 3, height: 2, input: "top\r\n\x1b[?7labcd",
29 wantRows: []string{"top", "abd"}, wantRow: 1, wantCol: 2,
30 },
31 {
32 name: "width one overwrites",
33 width: 1, height: 2, input: "\x1b[?7lab",
34 wantRows: []string{"b", " "}, wantRow: 0, wantCol: 0,
35 },
36 {
37 name: "vev raw-mode initialization disables wrapping",
38 width: 4, height: 2, input: "\x1b[?7lstatus",
39 wantRows: []string{"stas", " "}, wantRow: 0, wantCol: 3,
40 },
41 }
42
43 for _, test := range tests {
44 t.Run(test.name, func(t *testing.T) {
45 s := NewScreenWithHistory(test.width, test.height, HistoryConfig{MaxRows: 10})
46 s.Write([]byte(test.input))
47 for row, want := range test.wantRows {
48 if got := screenRowText(s, row); got != want {
49 t.Fatalf("row %d = %q, want %q", row, got, want)
50 }
51 }
52 if s.Row != test.wantRow || s.Col != test.wantCol {
53 t.Fatalf("cursor = (%d,%d), want (%d,%d)", s.Row, s.Col, test.wantRow, test.wantCol)
54 }
55 if got := s.History().Len(); got != test.wantHistory {
56 t.Fatalf("history length = %d, want %d", got, test.wantHistory)
57 }
58 if s.buffer.boundaries[0].Soft {
59 t.Fatal("disabled autowrap created a soft wrap")
60 }
61 })
62 }
63}
64
65func TestScreenAutoWrapWideRuneAtRightMargin(t *testing.T) {
66 s := NewScreen(4, 2)
67 s.Write([]byte("abc\x1b[?7l界"))
68
69 if got := s.Cell(3, 0); got.Rune != '\uFFFD' || got.Continuation {
70 t.Fatalf("right-margin cell = %#v, want narrow replacement", got)
71 }
72 for x := 0; x < s.Columns(); x++ {
73 if s.Cell(x, 0).Continuation && (x == 0 || renderer.RuneWidth(s.Cell(x-1, 0).Rune) != 2) {
74 t.Fatalf("orphaned continuation at column %d", x)
75 }
76 }
77 if s.Row != 0 || s.Col != 3 {
78 t.Fatalf("cursor = (%d,%d), want (0,3)", s.Row, s.Col)
79 }
80}
81
82func TestScreenAutoWrapModeTransitionsAndResize(t *testing.T) {
83 s := NewScreen(4, 2)
84 s.Write([]byte("abcd")) // Leave a deferred wrap pending.
85 s.Write([]byte("\x1b[?7le"))
86 if got := screenRowText(s, 0); got != "abce" {
87 t.Fatalf("row after disabling pending wrap = %q, want %q", got, "abce")
88 }
89
90 s.Resize(3, 2)
91 if s.Row != 0 || s.Col != 2 {
92 t.Fatalf("cursor after resize = (%d,%d), want (0,2)", s.Row, s.Col)
93 }
94 s.Write([]byte("f\x1b[?7hgh"))
95 if s.Row != 1 || s.Col != 1 {
96 t.Fatalf("cursor after re-enabling wrap = (%d,%d), want (1,1)", s.Row, s.Col)
97 }
98 if got := screenRowText(s, 1); got != "h " {
99 t.Fatalf("wrapped row = %q, want %q", got, "h ")
100 }
101}
102
103func TestScreenAutoWrapSavedAndReset(t *testing.T) {
104 s := NewScreen(4, 2)
105 s.Write([]byte("\x1b[?7l\x1b7\x1b[?7h\x1b8"))
106 if s.AutoWrapMode() {
107 t.Fatal("ESC 8 did not restore disabled autowrap mode")
108 }
109
110 s.Write([]byte("\x1bc"))
111 if !s.AutoWrapMode() {
112 t.Fatal("RIS did not restore enabled autowrap default")
113 }
114}
115
116func TestScreenApplicationCursorMode(t *testing.T) {
117 s := NewScreen(4, 2)
118 if s.ApplicationCursorMode() {
119 t.Fatal("application cursor mode enabled by default")
120 }
121 s.Write([]byte("\x1b[?"))
122 s.Write([]byte("1h"))
123 if !s.ApplicationCursorMode() || !s.Snapshot().ApplicationCursorMode() {
124 t.Fatal("split DECCKM enable was not tracked in screen and snapshot")
125 }
126 s.Write([]byte("\x1b[?1l"))
127 if s.ApplicationCursorMode() {
128 t.Fatal("DECCKM disable was not tracked")
129 }
130 s.Write([]byte("\x1b[?1h\x1bc"))
131 if s.ApplicationCursorMode() {
132 t.Fatal("RIS did not reset DECCKM")
133 }
134}
135
136func TestScreenCursorAndAutoWrapModeQueries(t *testing.T) {
137 s := NewScreen(4, 2)
138 var got bytes.Buffer
139 s.OnResponse = func(response []byte) { got.Write(response) }
140 s.Write([]byte("\x1b[?1$p\x1b[?7$p\x1b[?1h\x1b[?7l\x1b[?1$p\x1b[?7$p"))
141 want := "\x1b[?1;2$y\x1b[?7;1$y\x1b[?1;1$y\x1b[?7;2$y"
142 if got.String() != want {
143 t.Fatalf("mode reports = %q, want %q", got.String(), want)
144 }
145}
146
147func screenRowText(s *Screen, row int) string {
148 runes := make([]rune, s.Columns())
149 for col := range s.Columns() {
150 r := s.Cell(col, row).Rune
151 if r == 0 {
152 r = ' '
153 }
154 runes[col] = r
155 }
156 return string(runes)
157}