1package input
2
3import (
4 "testing"
5
6 "github.com/jwijenbergh/puregotk/v4/gdk"
7)
8
9func TestParseKeyString_SingleKeys(t *testing.T) {
10 tests := []struct {
11 name string
12 input string
13 want KeyBinding
14 wantOk bool
15 }{
16 {
17 name: "escape",
18 input: "escape",
19 want: KeyBinding{Keyval: uint(gdk.KEY_Escape), Modifiers: ModNone},
20 wantOk: true,
21 },
22 {
23 name: "esc alias",
24 input: "esc",
25 want: KeyBinding{Keyval: uint(gdk.KEY_Escape), Modifiers: ModNone},
26 wantOk: true,
27 },
28 {
29 name: "return",
30 input: "return",
31 want: KeyBinding{Keyval: uint(gdk.KEY_Return), Modifiers: ModNone},
32 wantOk: true,
33 },
34 {
35 name: "enter alias",
36 input: "enter",
37 want: KeyBinding{Keyval: uint(gdk.KEY_Return), Modifiers: ModNone},
38 wantOk: true,
39 },
40 {
41 name: "tab",
42 input: "tab",
43 want: KeyBinding{Keyval: uint(gdk.KEY_Tab), Modifiers: ModNone},
44 wantOk: true,
45 },
46 {
47 name: "space",
48 input: "space",
49 want: KeyBinding{Keyval: uint(gdk.KEY_space), Modifiers: ModNone},
50 wantOk: true,
51 },
52 {
53 name: "f5",
54 input: "f5",
55 want: KeyBinding{Keyval: uint(gdk.KEY_F5), Modifiers: ModNone},
56 wantOk: true,
57 },
58 {
59 name: "f12",
60 input: "f12",
61 want: KeyBinding{Keyval: uint(gdk.KEY_F12), Modifiers: ModNone},
62 wantOk: true,
63 },
64 {
65 name: "single letter",
66 input: "t",
67 want: KeyBinding{Keyval: uint('t'), Modifiers: ModNone},
68 wantOk: true,
69 },
70 {
71 name: "single number",
72 input: "0",
73 want: KeyBinding{Keyval: uint(gdk.KEY_0), Modifiers: ModNone},
74 wantOk: true,
75 },
76 {
77 name: "arrow left",
78 input: "left",
79 want: KeyBinding{Keyval: uint(gdk.KEY_Left), Modifiers: ModNone},
80 wantOk: true,
81 },
82 {
83 name: "arrow right",
84 input: "right",
85 want: KeyBinding{Keyval: uint(gdk.KEY_Right), Modifiers: ModNone},
86 wantOk: true,
87 },
88 }
89
90 for _, tt := range tests {
91 t.Run(tt.name, func(t *testing.T) {
92 got, ok := ParseKeyString(tt.input)
93 if ok != tt.wantOk {
94 t.Errorf("ParseKeyString(%q) ok = %v, want %v", tt.input, ok, tt.wantOk)
95 return
96 }
97 if !tt.wantOk {
98 return
99 }
100 if got.Keyval != tt.want.Keyval {
101 t.Errorf("ParseKeyString(%q) keyval = %d, want %d", tt.input, got.Keyval, tt.want.Keyval)
102 }
103 if got.Modifiers != tt.want.Modifiers {
104 t.Errorf("ParseKeyString(%q) modifiers = %d, want %d", tt.input, got.Modifiers, tt.want.Modifiers)
105 }
106 })
107 }
108}
109
110func TestParseKeyString_WithModifiers(t *testing.T) {
111 tests := []struct {
112 name string
113 input string
114 want KeyBinding
115 wantOk bool
116 }{
117 {
118 name: "ctrl+t",
119 input: "ctrl+t",
120 want: KeyBinding{Keyval: uint('t'), Modifiers: ModCtrl},
121 wantOk: true,
122 },
123 {
124 name: "control+t",
125 input: "control+t",
126 want: KeyBinding{Keyval: uint('t'), Modifiers: ModCtrl},
127 wantOk: true,
128 },
129 {
130 name: "shift+tab",
131 input: "shift+tab",
132 want: KeyBinding{Keyval: uint(gdk.KEY_Tab), Modifiers: ModShift},
133 wantOk: true,
134 },
135 {
136 name: "alt+left",
137 input: "alt+left",
138 want: KeyBinding{Keyval: uint(gdk.KEY_Left), Modifiers: ModAlt},
139 wantOk: true,
140 },
141 {
142 name: "ctrl+shift+t",
143 input: "ctrl+shift+t",
144 want: KeyBinding{Keyval: uint('t'), Modifiers: ModCtrl | ModShift},
145 wantOk: true,
146 },
147 {
148 name: "ctrl+r",
149 input: "ctrl+r",
150 want: KeyBinding{Keyval: uint('r'), Modifiers: ModCtrl},
151 wantOk: true,
152 },
153 }
154
155 for _, tt := range tests {
156 t.Run(tt.name, func(t *testing.T) {
157 got, ok := ParseKeyString(tt.input)
158 if ok != tt.wantOk {
159 t.Errorf("ParseKeyString(%q) ok = %v, want %v", tt.input, ok, tt.wantOk)
160 return
161 }
162 if !tt.wantOk {
163 return
164 }
165 if got.Keyval != tt.want.Keyval {
166 t.Errorf("ParseKeyString(%q) keyval = %d, want %d", tt.input, got.Keyval, tt.want.Keyval)
167 }
168 if got.Modifiers != tt.want.Modifiers {
169 t.Errorf("ParseKeyString(%q) modifiers = %d, want %d", tt.input, got.Modifiers, tt.want.Modifiers)
170 }
171 })
172 }
173}
174
175func TestParseKeyString_Invalid(t *testing.T) {
176 tests := []string{
177 "",
178 "ctrl+",
179 "unknownkey",
180 "ctrl+unknownkey",
181 }
182
183 for _, input := range tests {
184 t.Run(input, func(t *testing.T) {
185 _, ok := ParseKeyString(input)
186 if ok {
187 t.Errorf("ParseKeyString(%q) should have failed", input)
188 }
189 })
190 }
191}
192
193func TestParseKeyString_CaseInsensitive(t *testing.T) {
194 tests := []struct {
195 input1 string
196 input2 string
197 }{
198 {"ctrl+t", "CTRL+T"},
199 {"Escape", "escape"},
200 {"Tab", "TAB"},
201 {"Ctrl+Shift+T", "ctrl+shift+t"},
202 }
203
204 for _, tt := range tests {
205 t.Run(tt.input1+" vs "+tt.input2, func(t *testing.T) {
206 got1, ok1 := ParseKeyString(tt.input1)
207 got2, ok2 := ParseKeyString(tt.input2)
208
209 if ok1 != ok2 {
210 t.Errorf("ParseKeyString case sensitivity: %q=%v, %q=%v", tt.input1, ok1, tt.input2, ok2)
211 return
212 }
213
214 if got1 != got2 {
215 t.Errorf("ParseKeyString case sensitivity: %q=%v, %q=%v", tt.input1, got1, tt.input2, got2)
216 }
217 })
218 }
219}
220
221func TestShortcutSet_Lookup(t *testing.T) {
222 set := &ShortcutSet{
223 Global: ShortcutTable{
224 {uint(gdk.KEY_q), ModCtrl}: ActionQuit,
225 {uint(gdk.KEY_l), ModCtrl}: ActionOpenOmnibox,
226 },
227 TabMode: ShortcutTable{
228 {uint('n'), ModNone}: ActionNewTab,
229 {uint(gdk.KEY_Escape), ModNone}: ActionExitMode,
230 },
231 PaneMode: ShortcutTable{
232 {uint('x'), ModNone}: ActionClosePane,
233 {uint(gdk.KEY_Escape), ModNone}: ActionExitMode,
234 },
235 }
236
237 tests := []struct {
238 name string
239 binding KeyBinding
240 mode Mode
241 want Action
242 wantOk bool
243 }{
244 {
245 name: "global in normal mode",
246 binding: KeyBinding{uint(gdk.KEY_q), ModCtrl},
247 mode: ModeNormal,
248 want: ActionQuit,
249 wantOk: true,
250 },
251 {
252 name: "global in tab mode",
253 binding: KeyBinding{uint(gdk.KEY_l), ModCtrl},
254 mode: ModeTab,
255 want: ActionOpenOmnibox,
256 wantOk: true,
257 },
258 {
259 name: "tab mode action",
260 binding: KeyBinding{uint('n'), ModNone},
261 mode: ModeTab,
262 want: ActionNewTab,
263 wantOk: true,
264 },
265 {
266 name: "pane mode action",
267 binding: KeyBinding{uint('x'), ModNone},
268 mode: ModePane,
269 want: ActionClosePane,
270 wantOk: true,
271 },
272 {
273 name: "escape exits tab mode",
274 binding: KeyBinding{uint(gdk.KEY_Escape), ModNone},
275 mode: ModeTab,
276 want: ActionExitMode,
277 wantOk: true,
278 },
279 {
280 name: "escape exits pane mode",
281 binding: KeyBinding{uint(gdk.KEY_Escape), ModNone},
282 mode: ModePane,
283 want: ActionExitMode,
284 wantOk: true,
285 },
286 {
287 name: "unknown key in normal mode",
288 binding: KeyBinding{uint('z'), ModNone},
289 mode: ModeNormal,
290 want: "",
291 wantOk: false,
292 },
293 {
294 name: "tab action not in normal mode",
295 binding: KeyBinding{uint('n'), ModNone},
296 mode: ModeNormal,
297 want: "",
298 wantOk: false,
299 },
300 }
301
302 for _, tt := range tests {
303 t.Run(tt.name, func(t *testing.T) {
304 got, ok := set.Lookup(tt.binding, tt.mode)
305 if ok != tt.wantOk {
306 t.Errorf("Lookup() ok = %v, want %v", ok, tt.wantOk)
307 return
308 }
309 if got != tt.want {
310 t.Errorf("Lookup() = %v, want %v", got, tt.want)
311 }
312 })
313 }
314}
315
316func TestShouldAutoExitMode(t *testing.T) {
317 exitActions := []Action{
318 ActionNewTab,
319 ActionCloseTab,
320 ActionRenameTab,
321 ActionSplitRight,
322 ActionSplitLeft,
323 ActionSplitUp,
324 ActionSplitDown,
325 ActionClosePane,
326 ActionStackPane,
327 }
328
329 stayActions := []Action{
330 ActionNextTab,
331 ActionPreviousTab,
332 ActionFocusRight,
333 ActionFocusLeft,
334 ActionGoBack,
335 ActionZoomIn,
336 ActionOpenOmnibox,
337 }
338
339 for _, action := range exitActions {
340 if !ShouldAutoExitMode(action) {
341 t.Errorf("ShouldAutoExitMode(%s) = false, want true", action)
342 }
343 }
344
345 for _, action := range stayActions {
346 if ShouldAutoExitMode(action) {
347 t.Errorf("ShouldAutoExitMode(%s) = true, want false", action)
348 }
349 }
350}