1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
package tui
import "testing"
func TestDispatchTable_FocusRequired(t *testing.T) {
type tc struct {
focusedID string
pressRune rune
expectAppQuit bool
expectInserted bool
}
tests := map[string]tc{
"unfocused input: app handler fires, input skipped": {
focusedID: "",
pressRune: 'q',
expectAppQuit: true,
expectInserted: false,
},
"focused input: focus-gated handler takes priority over broadcast": {
focusedID: "input",
pressRune: 'q',
expectAppQuit: false,
expectInserted: true,
},
"focused input: non-matching app key still works": {
focusedID: "input",
pressRune: 0,
expectAppQuit: true,
expectInserted: false,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
appQuit := false
charInserted := false
fm := newFocusManager()
inputMock := newMockFocusable("input", true)
fm.Register(inputMock)
if tt.focusedID == "input" {
fm.SetFocus(inputMock)
}
table := &dispatchTable{}
table.entries = append(table.entries, dispatchEntry{
pattern: KeyPattern{Rune: 'q'},
handler: func(ke KeyEvent) { appQuit = true },
stop: false,
position: 0,
})
table.entries = append(table.entries, dispatchEntry{
pattern: KeyPattern{Key: KeyEscape, ExcludeMods: ModCtrl | ModAlt | ModShift},
handler: func(ke KeyEvent) { appQuit = true },
stop: false,
position: 0,
})
table.entries = append(table.entries, dispatchEntry{
pattern: KeyPattern{AnyRune: true, FocusRequired: true},
handler: func(ke KeyEvent) { charInserted = true },
stop: true,
position: 1,
focusCheck: inputMock.IsFocused,
})
var ke KeyEvent
if tt.pressRune != 0 {
ke = KeyEvent{Key: KeyRune, Rune: tt.pressRune}
} else {
ke = KeyEvent{Key: KeyEscape}
}
table.dispatch(ke)
if appQuit != tt.expectAppQuit {
t.Errorf("appQuit = %v, want %v", appQuit, tt.expectAppQuit)
}
if charInserted != tt.expectInserted {
t.Errorf("charInserted = %v, want %v", charInserted, tt.expectInserted)
}
})
}
}
func TestDispatchTable_ValidateFocusRequired(t *testing.T) {
type tc struct {
entries []dispatchEntry
expectErr bool
}
tests := map[string]tc{
"two focus-gated stop handlers do not conflict": {
entries: []dispatchEntry{
{pattern: KeyPattern{AnyRune: true, FocusRequired: true}, stop: true, position: 0, focusCheck: newMockFocusable("a", true).IsFocused},
{pattern: KeyPattern{AnyRune: true, FocusRequired: true}, stop: true, position: 1, focusCheck: newMockFocusable("b", true).IsFocused},
},
expectErr: false,
},
"focus-gated and broadcast stop handlers do not conflict": {
entries: []dispatchEntry{
{pattern: KeyPattern{AnyRune: true, FocusRequired: true}, stop: true, position: 0, focusCheck: newMockFocusable("a", true).IsFocused},
{pattern: KeyPattern{AnyRune: true}, stop: true, position: 1},
},
expectErr: false,
},
"two broadcast stop handlers still conflict": {
entries: []dispatchEntry{
{pattern: KeyPattern{AnyRune: true}, stop: true, position: 0},
{pattern: KeyPattern{AnyRune: true}, stop: true, position: 1},
},
expectErr: true,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
table := &dispatchTable{entries: tt.entries}
err := table.validate()
if tt.expectErr && err == nil {
t.Error("expected validation error, got nil")
}
if !tt.expectErr && err != nil {
t.Errorf("unexpected validation error: %v", err)
}
})
}
}
|