1package webterm
2
3import (
4 "context"
5 "io"
6 "testing"
7 "time"
8
9 "github.com/bnema/vev-vt/html/browser"
10 "github.com/bnema/vev/internal/domain"
11 "github.com/stretchr/testify/require"
12)
13
14func TestTerminalFlushResizeAndClose(t *testing.T) {
15 terminal, err := New(t.Context(), domain.Geometry{Size: domain.Size{Cols: 10, Rows: 4}})
16 require.NoError(t, err)
17 defer terminal.Close()
18 _, err = terminal.Write([]byte("hello"))
19 require.NoError(t, err)
20 require.Equal(t, rune(' '), terminal.Snapshot().Cell(0, 0).Rune)
21 require.NoError(t, terminal.Flush())
22 require.Equal(t, rune('h'), terminal.Snapshot().Cell(0, 0).Rune)
23 old := terminal.Snapshot()
24 geometry := domain.Geometry{Size: domain.Size{Cols: 20, Rows: 8}, PixelWidth: 200, PixelHeight: 160}
25 require.NoError(t, terminal.Resize(geometry))
26 require.Equal(t, geometry, <-terminal.ResizeEvents())
27 require.NoError(t, terminal.Flush())
28 require.Equal(t, 20, terminal.Snapshot().Columns())
29 require.Equal(t, 10, old.Columns())
30 require.Error(t, terminal.Resize(domain.Geometry{Size: domain.Size{Cols: MaxColumns + 1, Rows: 2}}))
31 terminal.Close()
32 _, err = terminal.Write([]byte("x"))
33 require.ErrorIs(t, err, io.ErrClosedPipe)
34}
35
36func TestTerminalDefaultColorQueries(t *testing.T) {
37 terminal, err := New(t.Context(), domain.Geometry{Size: domain.Size{Cols: 10, Rows: 4}})
38 require.NoError(t, err)
39 defer terminal.Close()
40 for _, tt := range []struct{ query, want string }{
41 {"\x1b]10;?\x1b\\", "\x1b]10;rgb:d8d8/d8d8/d8d8\x1b\\"},
42 {"\x1b]11;?\x1b\\", "\x1b]11;rgb:1010/1010/1010\x1b\\"},
43 } {
44 _, err := terminal.Write([]byte(tt.query))
45 require.NoError(t, err)
46 got := make([]byte, len(tt.want))
47 _, err = io.ReadFull(terminal.In(), got)
48 require.NoError(t, err)
49 require.Equal(t, tt.want, string(got))
50 }
51}
52
53func TestTerminalInput(t *testing.T) {
54 tests := []struct {
55 name string
56 event browser.Event
57 want string
58 }{
59 {"text", browser.Event{Kind: browser.EventText, Text: &browser.TextEvent{Text: "helloλ"}}, "helloλ"},
60 {"paste sanitizes framing", browser.Event{Kind: browser.EventPaste, Paste: &browser.PasteEvent{Text: "a\x1b[201~\x03\nb"}}, "\x1b[200~a[201~\nb\x1b[201~"},
61 {"palette", browser.Event{Kind: browser.EventKey, Key: &browser.KeyEvent{Key: " ", Modifiers: browser.Modifiers{Alt: true}}}, "\x1b "},
62 {"mouse", browser.Event{Kind: browser.EventPointer, Pointer: &browser.PointerEvent{Action: "down", Button: 0, Row: 1, Column: 2}}, "\x1b[<0;3;2M"},
63 }
64 for _, tt := range tests {
65 t.Run(tt.name, func(t *testing.T) {
66 ctx, cancel := context.WithTimeout(t.Context(), time.Second)
67 defer cancel()
68 terminal, err := New(ctx, domain.Geometry{Size: domain.Size{Cols: 10, Rows: 4}})
69 require.NoError(t, err)
70 defer terminal.Close()
71 _, err = terminal.EnterRaw()
72 require.NoError(t, err)
73 require.NoError(t, terminal.Handle(ctx, tt.event))
74 data := make([]byte, len(tt.want))
75 _, err = io.ReadFull(terminal.In(), data)
76 require.NoError(t, err)
77 require.Equal(t, tt.want, string(data))
78 })
79 }
80}
81
82func TestEncodeKey(t *testing.T) {
83 tests := []struct {
84 name, key string
85 modifiers browser.Modifiers
86 app bool
87 want string
88 }{
89 {"cursor", "ArrowUp", browser.Modifiers{}, false, "\x1b[A"},
90 {"application cursor", "ArrowUp", browser.Modifiers{}, true, "\x1bOA"},
91 {"modified cursor", "ArrowLeft", browser.Modifiers{Ctrl: true}, false, "\x1b[1;5D"},
92 {"interrupt", "c", browser.Modifiers{Ctrl: true}, false, "\x03"},
93 {"alt letter", "h", browser.Modifiers{Alt: true}, false, "\x1bh"},
94 {"shift tab", "Tab", browser.Modifiers{Shift: true}, false, "\x1b[Z"},
95 {"alt shift tab", "Tab", browser.Modifiers{Shift: true, Alt: true}, false, "\x1b\x1b[Z"},
96 {"delete", "Delete", browser.Modifiers{}, false, "\x1b[3~"},
97 {"function", "F12", browser.Modifiers{}, false, "\x1b[24~"},
98 {"meta reserved", "c", browser.Modifiers{Meta: true}, false, ""},
99 {"unknown", "Unidentified", browser.Modifiers{}, false, ""},
100 }
101 for _, tt := range tests {
102 t.Run(tt.name, func(t *testing.T) {
103 require.Equal(t, tt.want, encodeKey(browser.KeyEvent{Key: tt.key, Modifiers: tt.modifiers}, tt.app))
104 })
105 }
106}