1package input
2
3import (
4 "testing"
5 "time"
6
7 "github.com/bnema/puregotk/v4/gdk"
8)
9
10func TestGlobalShortcutHandlerSuppressesRepeatedOneShotActions(t *testing.T) {
11 h := &GlobalShortcutHandler{lastDispatchAt: make(map[Action]time.Time)}
12 now := time.Unix(100, 0)
13
14 if h.suppressRepeatedShortcut(ActionZoomReset, now) {
15 t.Fatal("first zoom reset dispatch was suppressed")
16 }
17 if !h.suppressRepeatedShortcut(ActionZoomReset, now.Add(globalShortcutRepeatSuppressWindow/2)) {
18 t.Fatal("repeated zoom reset dispatch inside suppression window was not suppressed")
19 }
20 if h.suppressRepeatedShortcut(ActionZoomReset, now.Add(globalShortcutRepeatSuppressWindow)) {
21 t.Fatal("zoom reset dispatch at suppression boundary was suppressed")
22 }
23}
24
25func TestGlobalShortcutHandlerAllowsRepeatingFocusNavigationActions(t *testing.T) {
26 h := &GlobalShortcutHandler{lastDispatchAt: make(map[Action]time.Time)}
27 now := time.Unix(100, 0)
28
29 if h.suppressRepeatedShortcut(ActionFocusRight, now) {
30 t.Fatal("first focus-right dispatch was suppressed")
31 }
32 if h.suppressRepeatedShortcut(ActionFocusRight, now.Add(time.Millisecond)) {
33 t.Fatal("focus-right repeat was suppressed")
34 }
35}
36
37func TestGlobalShortcutHandlerSuppressesRepeatedBrowserNavigationActions(t *testing.T) {
38 h := &GlobalShortcutHandler{lastDispatchAt: make(map[Action]time.Time)}
39 now := time.Unix(100, 0)
40
41 if h.suppressRepeatedShortcut(ActionGoBack, now) {
42 t.Fatal("first go-back dispatch was suppressed")
43 }
44 if !h.suppressRepeatedShortcut(ActionGoBack, now.Add(time.Millisecond)) {
45 t.Fatal("repeated go-back dispatch was not suppressed")
46 }
47}
48
49func TestGlobalShortcutHandlerSuppressesRepeatedTabIndexSwitchActions(t *testing.T) {
50 actions := []Action{
51 ActionSwitchTabIndex1,
52 ActionSwitchTabIndex2,
53 ActionSwitchTabIndex3,
54 ActionSwitchTabIndex4,
55 ActionSwitchTabIndex5,
56 ActionSwitchTabIndex6,
57 ActionSwitchTabIndex7,
58 ActionSwitchTabIndex8,
59 ActionSwitchTabIndex9,
60 ActionSwitchTabIndex10,
61 }
62
63 for _, action := range actions {
64 t.Run(string(action), func(t *testing.T) {
65 h := &GlobalShortcutHandler{lastDispatchAt: make(map[Action]time.Time)}
66 now := time.Unix(100, 0)
67
68 if h.suppressRepeatedShortcut(action, now) {
69 t.Fatalf("first %s dispatch was suppressed", action)
70 }
71 if !h.suppressRepeatedShortcut(action, now.Add(time.Millisecond)) {
72 t.Fatalf("repeated %s dispatch was not suppressed", action)
73 }
74 })
75 }
76}
77
78func TestGlobalShortcutHandlerSuppressesOneShotActionsAfterDetach(t *testing.T) {
79 h := &GlobalShortcutHandler{
80 registered: make(map[KeyBinding]Action),
81 lastDispatchAt: make(map[Action]time.Time),
82 }
83
84 if h.suppressRepeatedShortcut(ActionZoomReset, time.Unix(100, 0)) {
85 t.Fatal("one-shot shortcut was suppressed before detach")
86 }
87 h.Detach()
88 if !h.suppressRepeatedShortcut(ActionZoomReset, time.Unix(100, 0)) {
89 t.Fatal("one-shot shortcut was not suppressed with detached handler state")
90 }
91}
92
93func TestGlobalShortcutHandlerGenerationMarksOldCallbacksStale(t *testing.T) {
94 h := &GlobalShortcutHandler{}
95 generation := h.generation
96
97 if h.isStaleGeneration(generation) {
98 t.Fatal("current generation was marked stale")
99 }
100 h.generation++
101 if !h.isStaleGeneration(generation) {
102 t.Fatal("old generation was not marked stale")
103 }
104}
105
106func TestGlobalShortcutHandlerReturnsInactiveForNilOrDetached(t *testing.T) {
107 var nilHandler *GlobalShortcutHandler
108 if nilHandler.isActiveWindowShortcutHandler() {
109 t.Fatal("nil handler should return inactive")
110 }
111 if (&GlobalShortcutHandler{}).isActiveWindowShortcutHandler() {
112 t.Fatal("handler with nil window should return inactive")
113 }
114
115 h := &GlobalShortcutHandler{
116 registered: make(map[KeyBinding]Action),
117 lastDispatchAt: make(map[Action]time.Time),
118 }
119 h.Detach()
120 if h.isActiveWindowShortcutHandler() {
121 t.Fatal("detached handler with nil controller should return inactive")
122 }
123}
124
125func TestGlobalShortcutHandlerSuppressesRepeatedAdditionalOneShotUIActions(t *testing.T) {
126 actions := []Action{
127 ActionPrintPage,
128 ActionReload,
129 ActionHardReload,
130 ActionToggleFullscreen,
131 ActionToggleHistorySystemView,
132 ActionOpenOmnibox,
133 ActionOpenFind,
134 ActionFindNext,
135 ActionFindPrev,
136 ActionOpenDevTools,
137 ActionToggleFloatingPane,
138 ActionToggleFavoritesSystemView,
139 ActionToggleConfigSystemView,
140 ActionCopyURL,
141 ActionConsumeOrExpelLeft,
142 ActionConsumeOrExpelRight,
143 ActionConsumeOrExpelUp,
144 ActionConsumeOrExpelDown,
145 }
146
147 for _, action := range actions {
148 t.Run(string(action), func(t *testing.T) {
149 h := &GlobalShortcutHandler{lastDispatchAt: make(map[Action]time.Time)}
150 now := time.Unix(100, 0)
151
152 if h.suppressRepeatedShortcut(action, now) {
153 t.Fatalf("first %s dispatch was suppressed", action)
154 }
155 if !h.suppressRepeatedShortcut(action, now.Add(time.Millisecond)) {
156 t.Fatalf("repeated %s dispatch was not suppressed", action)
157 }
158 })
159 }
160}
161
162func TestGlobalShortcutHandlerSuppressesRepeatedFloatingProfileAction(t *testing.T) {
163 h := &GlobalShortcutHandler{lastDispatchAt: make(map[Action]time.Time)}
164 now := time.Unix(100, 0)
165 action := NewFloatingProfileAction("work", "https://example.com")
166
167 if h.suppressRepeatedShortcut(action, now) {
168 t.Fatal("first floating-profile dispatch was suppressed")
169 }
170 if !h.suppressRepeatedShortcut(action, now.Add(time.Millisecond)) {
171 t.Fatal("repeated floating-profile dispatch was not suppressed")
172 }
173}
174
175func TestGlobalShortcutHandlerSuppressesActionSwitchLastTab(t *testing.T) {
176 if !isRepeatedGlobalShortcutSuppressed(ActionSwitchLastTab) {
177 t.Fatal("ActionSwitchLastTab should be suppressed")
178 }
179
180 h := &GlobalShortcutHandler{lastDispatchAt: make(map[Action]time.Time)}
181 now := time.Unix(100, 0)
182
183 if h.suppressRepeatedShortcut(ActionSwitchLastTab, now) {
184 t.Fatal("first ActionSwitchLastTab dispatch was suppressed")
185 }
186 if !h.suppressRepeatedShortcut(ActionSwitchLastTab, now.Add(time.Millisecond)) {
187 t.Fatal("repeated ActionSwitchLastTab dispatch was not suppressed")
188 }
189}
190
191func TestGlobalShortcutHandlerSuppressesHeldGlobalShortcutUntilRelease(t *testing.T) {
192 h := &GlobalShortcutHandler{heldShortcuts: make(map[globalShortcutHoldKey]struct{})}
193 info := globalShortcutEventInfo{hasCurrentEvent: true, eventType: gdk.KeyPressValue, eventKeyval: uint('f'), eventKeycode: 41}
194
195 if h.suppressHeldShortcut(ActionToggleFloatingPane, info) {
196 t.Fatal("first held global shortcut dispatch was suppressed")
197 }
198 if !h.suppressHeldShortcut(ActionToggleFloatingPane, info) {
199 t.Fatal("held global shortcut repeat after cooldown was not suppressed")
200 }
201
202 h.releaseHeldGlobalShortcuts(uint('x'), 0)
203 if !h.suppressHeldShortcut(ActionToggleFloatingPane, info) {
204 t.Fatal("unrelated key release re-armed held global shortcut")
205 }
206
207 h.releaseHeldGlobalShortcuts(uint('f'), 0)
208 if h.suppressHeldShortcut(ActionToggleFloatingPane, info) {
209 t.Fatal("matching key release did not re-arm held global shortcut")
210 }
211}
212
213func TestGlobalShortcutHandlerSuppressesHeldModeAction(t *testing.T) {
214 h := &GlobalShortcutHandler{heldShortcuts: make(map[globalShortcutHoldKey]struct{})}
215 info := globalShortcutEventInfo{hasCurrentEvent: true, eventType: gdk.KeyPressValue, eventKeyval: uint('p'), eventKeycode: 33}
216
217 if h.suppressHeldShortcut(ActionEnterPaneMode, info) {
218 t.Fatal("first mode shortcut dispatch was suppressed")
219 }
220 if !h.suppressHeldShortcut(ActionEnterPaneMode, info) {
221 t.Fatal("held mode shortcut repeat was not suppressed")
222 }
223}
224
225func TestGlobalShortcutHandlerSuppressesHeldFloatingProfileAction(t *testing.T) {
226 h := &GlobalShortcutHandler{heldShortcuts: make(map[globalShortcutHoldKey]struct{})}
227 info := globalShortcutEventInfo{hasCurrentEvent: true, eventType: gdk.KeyPressValue, eventKeyval: uint('o'), eventKeycode: 32}
228 action := NewFloatingProfileAction("work", "https://example.com")
229
230 if h.suppressHeldShortcut(action, info) {
231 t.Fatal("first floating-profile shortcut dispatch was suppressed")
232 }
233 if !h.suppressHeldShortcut(action, info) {
234 t.Fatal("held floating-profile shortcut repeat was not suppressed")
235 }
236}
237
238func TestShouldDispatchGlobalShortcutEventRequiresCurrentKeyEvent(t *testing.T) {
239 if shouldDispatchGlobalShortcutEvent(globalShortcutEventInfo{}) {
240 t.Fatal("global shortcut without current event should not dispatch")
241 }
242 if shouldDispatchGlobalShortcutEvent(globalShortcutEventInfo{hasCurrentEvent: true, eventType: gdk.KeyReleaseValue}) {
243 t.Fatal("global shortcut from key-release event should not dispatch")
244 }
245 if !shouldDispatchGlobalShortcutEvent(globalShortcutEventInfo{hasCurrentEvent: true, eventType: gdk.KeyPressValue}) {
246 t.Fatal("global shortcut from key-press event should dispatch")
247 }
248}
249
250func TestFormatModifierMask(t *testing.T) {
251 got := formatModifierMask(gdk.ControlMaskValue | gdk.ShiftMaskValue | gdk.ModifierType(0x2000))
252 if got != "ctrl+shift+0x2000" {
253 t.Fatalf("unexpected modifier mask: %q", got)
254 }
255 if formatModifierMask(0) != "none" {
256 t.Fatal("zero modifier mask should render as none")
257 }
258}
259
260func TestFormatEventType(t *testing.T) {
261 if got := formatEventType(gdk.KeyPressValue); got != "key-press" {
262 t.Fatalf("unexpected key press event type: %q", got)
263 }
264 if got := formatEventType(gdk.EventType(99)); got != "event-99" {
265 t.Fatalf("unexpected fallback event type: %q", got)
266 }
267}