1package daemon
2
3import (
4 "testing"
5
6 "github.com/stretchr/testify/require"
7)
8
9func TestRouteOverlayBytes(t *testing.T) {
10 t.Parallel()
11
12 tests := []struct {
13 name string
14 chunks [][]byte
15 wantRunes []rune
16 wantPending []byte
17 wantBackspace int
18 wantEnter int
19 wantCancel int
20 wantUp int
21 wantDown int
22 wantTab int
23 callback bool
24 }{
25 {
26 name: "text and split utf8",
27 chunks: [][]byte{{'w', 0xc3}, {0xb8, 'k'}},
28 wantRunes: []rune{'w', 'ø', 'k'},
29 wantPending: nil,
30 },
31 {
32 name: "incomplete utf8 remains pending",
33 chunks: [][]byte{{'w', 0xc3}},
34 wantRunes: []rune{'w'},
35 wantPending: []byte{0xc3},
36 },
37 {
38 name: "incomplete csi resumes as arrow",
39 chunks: [][]byte{[]byte("\x1b["), []byte("B")},
40 wantDown: 1,
41 },
42 {
43 name: "incomplete csi remains pending",
44 chunks: [][]byte{[]byte("\x1b[")},
45 wantPending: []byte("\x1b["),
46 },
47 {
48 name: "incomplete ss3 resumes as arrow",
49 chunks: [][]byte{[]byte("\x1bO"), []byte("A\x1bOB")},
50 wantUp: 1,
51 wantDown: 1,
52 },
53 {
54 name: "incomplete ss3 remains pending",
55 chunks: [][]byte{[]byte("\x1bO")},
56 wantPending: []byte("\x1bO"),
57 },
58 {
59 name: "arrows and ctrl navigation",
60 chunks: [][]byte{[]byte("\x1b[A\x1b[B\x0e\x10")},
61 wantUp: 2,
62 wantDown: 2,
63 },
64 {
65 name: "unhandled escapes are consumed",
66 chunks: [][]byte{[]byte("a\x1b[D\x1b[3~b\x1bOQ")},
67 wantRunes: []rune{'a', 'b'},
68 },
69 {
70 name: "submit cancel and editing",
71 chunks: [][]byte{{0x7f, 0x08, '\r', '\n', 0x1b, 0x03}},
72 wantBackspace: 2,
73 wantEnter: 2,
74 wantCancel: 2,
75 },
76 {
77 name: "controls and invalid utf8 ignored",
78 chunks: [][]byte{{'a', 0x01, 0xff, 'b'}},
79 wantRunes: []rune{'a', 'b'},
80 },
81 {
82 name: "tab routes to callback",
83 chunks: [][]byte{{0x09}},
84 wantTab: 1,
85 callback: true,
86 },
87 {
88 name: "tab without callback is consumed",
89 chunks: [][]byte{{0x09}},
90 },
91 }
92
93 for _, tt := range tests {
94 t.Run(tt.name, func(t *testing.T) {
95 var pending []byte
96 var gotRunes []rune
97 var gotBackspace, gotEnter, gotCancel, gotUp, gotDown, gotTab int
98 ev := overlayEvents{
99 rune: func(r rune) { gotRunes = append(gotRunes, r) },
100 backspace: func() { gotBackspace++ },
101 enter: func() { gotEnter++ },
102 cancel: func() { gotCancel++ },
103 up: func() { gotUp++ },
104 down: func() { gotDown++ },
105 }
106 if tt.callback {
107 ev.tab = func() { gotTab++ }
108 }
109
110 for _, chunk := range tt.chunks {
111 routeOverlayBytes(chunk, &pending, ev)
112 }
113
114 require.Equal(t, tt.wantRunes, gotRunes)
115 require.Equal(t, tt.wantPending, pending)
116 require.Equal(t, tt.wantBackspace, gotBackspace)
117 require.Equal(t, tt.wantEnter, gotEnter)
118 require.Equal(t, tt.wantCancel, gotCancel)
119 require.Equal(t, tt.wantUp, gotUp)
120 require.Equal(t, tt.wantDown, gotDown)
121 require.Equal(t, tt.wantTab, gotTab)
122 })
123 }
124}