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