turbo-editors/turbo-corepublic Fork 0
d662cebdb65b319885da903daf7eff9ab1bfbb78
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

submenu_test.go · 325 lines · 8.4 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 21h ago1package ui
2
3import (
4 "strings"
5 "testing"
6
7 "github.com/gdamore/tcell/v2"
8)
9
10// submenuBar returns a bar whose second menu holds one plain item and one
11// branch, and records what was run.
12func submenuBar() (*MenuBar, *[]string) {
13 var ran []string
14 record := func(name string) func() { return func() { ran = append(ran, name) } }
15
16 bar := NewMenuBar(
17 &Menu{Label: "~F~ile", Items: []*MenuItem{
18 {Label: "~N~ew", Action: record("new")},
19 }},
20 &Menu{Label: "~S~nippets", Items: []*MenuItem{
21 {Label: "~G~o", Items: []*MenuItem{
22 {Label: "~f~unc", Action: record("go/func")},
23 {Label: "if ~e~rr", Action: record("go/iferr")},
24 }},
25 {Label: "~S~hell", Items: []*MenuItem{
26 {Label: "~l~oop", Action: record("shell/loop")},
27 }},
28 {Separator: true},
29 {Label: "~C~reate file", Action: record("create")},
30 }},
31 )
32 bar.SetBounds(Rect{W: 80, H: 1})
33 return bar, &ran
34}
35
36// key sends a key to the bar.
37func key(bar *MenuBar, k tcell.Key) bool {
38 return bar.HandleKey(tcell.NewEventKey(k, 0, tcell.ModNone))
39}
40
41// typeRune sends a printable key to the bar.
42func typeRune(bar *MenuBar, r rune) bool {
43 return bar.HandleKey(tcell.NewEventKey(tcell.KeyRune, r, tcell.ModNone))
44}
45
46func TestAnItemWithNoActionButASubmenuCanStillBeChosen(t *testing.T) {
47 // Without this an item that only opens a branch counts as disabled, and
48 // the whole submenu is unreachable.
49 branch := &MenuItem{Label: "Go", Items: []*MenuItem{{Label: "f", Action: func() {}}}}
50
51 if !branch.enabled() {
52 t.Error("an item with a submenu and no action reports itself disabled")
53 }
54}
55
56func TestRightOpensTheHighlightedSubmenu(t *testing.T) {
57 bar, _ := submenuBar()
58 bar.OpenMenu(1)
59
60 key(bar, tcell.KeyRight)
61
62 if !bar.submenuOpen() {
63 t.Fatal("right did not open the submenu")
64 }
65 if bar.openItem != 0 {
66 t.Errorf("openItem = %d, want the first branch", bar.openItem)
67 }
68}
69
70func TestRightMovesToTheNextMenuWhenThereIsNoSubmenu(t *testing.T) {
71 bar, _ := submenuBar()
72 bar.OpenMenu(0) // File, whose only item is plain
73
74 key(bar, tcell.KeyRight)
75
76 if bar.submenuOpen() {
77 t.Error("right opened a submenu on an item that has none")
78 }
79 if bar.openMenu != 1 {
80 t.Errorf("openMenu = %d, want the next menu", bar.openMenu)
81 }
82}
83
84func TestEnterOnABranchOpensItRatherThanRunningAnything(t *testing.T) {
85 bar, ran := submenuBar()
86 bar.OpenMenu(1)
87
88 key(bar, tcell.KeyEnter)
89
90 if !bar.submenuOpen() {
91 t.Error("enter on a branch did not open it")
92 }
93 if len(*ran) != 0 {
94 t.Errorf("enter on a branch ran %v", *ran)
95 }
96}
97
98func TestTheArrowsWalkTheSubmenu(t *testing.T) {
99 bar, _ := submenuBar()
100 bar.OpenMenu(1)
101 key(bar, tcell.KeyRight)
102
103 key(bar, tcell.KeyDown)
104 if bar.subHighlight != 1 {
105 t.Errorf("subHighlight = %d, want 1", bar.subHighlight)
106 }
107 key(bar, tcell.KeyUp)
108 if bar.subHighlight != 0 {
109 t.Errorf("subHighlight = %d, want 0", bar.subHighlight)
110 }
111}
112
113func TestEnterInASubmenuRunsTheItemAndClosesEverything(t *testing.T) {
114 bar, ran := submenuBar()
115 bar.OpenMenu(1)
116 key(bar, tcell.KeyRight)
117 key(bar, tcell.KeyDown)
118
119 key(bar, tcell.KeyEnter)
120
121 if len(*ran) != 1 || (*ran)[0] != "go/iferr" {
122 t.Errorf("ran %v, want the second submenu item", *ran)
123 }
124 if bar.Open() || bar.submenuOpen() {
125 t.Error("running a submenu item left a menu showing")
126 }
127}
128
129func TestLeftStepsBackOutOfASubmenu(t *testing.T) {
130 bar, _ := submenuBar()
131 bar.OpenMenu(1)
132 key(bar, tcell.KeyRight)
133
134 key(bar, tcell.KeyLeft)
135
136 if bar.submenuOpen() {
137 t.Error("left did not close the submenu")
138 }
139 if !bar.Open() {
140 t.Error("left closed the whole menu instead of stepping out")
141 }
142}
143
144func TestEscapeFromASubmenuClosesTheWholeMenu(t *testing.T) {
145 // Cancel should mean cancel wherever you are, which is what makes it
146 // different from left.
147 bar, _ := submenuBar()
148 bar.OpenMenu(1)
149 key(bar, tcell.KeyRight)
150
151 key(bar, tcell.KeyEscape)
152
153 if bar.Open() || bar.submenuOpen() {
154 t.Error("escape left a menu showing")
155 }
156}
157
158func TestAHotKeyInsideASubmenuRunsItsItem(t *testing.T) {
159 bar, ran := submenuBar()
160 bar.OpenMenu(1)
161 key(bar, tcell.KeyRight)
162
163 typeRune(bar, 'e') // "if ~e~rr"
164
165 if len(*ran) != 1 || (*ran)[0] != "go/iferr" {
166 t.Errorf("ran %v, want the item whose hot key was typed", *ran)
167 }
168}
169
170func TestAHotKeyOnABranchOpensItRatherThanRunningIt(t *testing.T) {
171 bar, ran := submenuBar()
172 bar.OpenMenu(1)
173
174 typeRune(bar, 's') // "~S~hell"
175
176 if !bar.submenuOpen() || bar.openItem != 1 {
177 t.Errorf("openItem = %d, submenuOpen = %v; want the Shell branch open", bar.openItem, bar.submenuOpen())
178 }
179 if len(*ran) != 0 {
180 t.Errorf("a hot key on a branch ran %v", *ran)
181 }
182}
183
184func TestASubmenuSwallowsStrayTyping(t *testing.T) {
185 bar, ran := submenuBar()
186 bar.OpenMenu(1)
187 key(bar, tcell.KeyRight)
188
189 if !typeRune(bar, 'z') {
190 t.Error("a submenu let a stray key through to whatever is behind it")
191 }
192 if len(*ran) != 0 {
193 t.Errorf("a stray key ran %v", *ran)
194 }
195}
196
197func TestOpeningAnotherMenuClosesTheSubmenu(t *testing.T) {
198 bar, _ := submenuBar()
199 bar.OpenMenu(1)
200 key(bar, tcell.KeyRight)
201
202 bar.OpenMenu(0)
203
204 if bar.submenuOpen() {
205 t.Error("opening another menu left the submenu showing")
206 }
207}
208
209func TestASubmenuIsDrawnBesideItsParentWithAMarkerOnTheBranch(t *testing.T) {
210 screen, root := newTestScreen(t, 80, 12)
211 bar, _ := submenuBar()
212 bar.SetBounds(Rect{W: 80, H: 1})
213 bar.OpenMenu(1)
214 key(bar, tcell.KeyRight)
215
216 bar.Draw(root, testTheme(t))
217 screen.Show()
218 lines := screenLines(t, screen)
219 shown := strings.Join(lines, "\n")
220
221 if !strings.Contains(shown, submenuMarker) {
222 t.Errorf("no branch marker is drawn:\n%s", shown)
223 }
224 for _, want := range []string{"Go", "Shell", "func", "if err"} {
225 if !strings.Contains(shown, want) {
226 t.Errorf("%q is not on the screen:\n%s", want, shown)
227 }
228 }
229
230 // The two panels must not sit on top of each other.
231 parent, sub := bar.dropdownBounds(), bar.submenuBounds()
232 if parent.Intersect(sub).W > 0 && parent.Intersect(sub).H > 0 {
233 t.Errorf("the panels overlap: parent %+v, submenu %+v", parent, sub)
234 }
235}
236
237func TestASubmenuFlipsLeftWhenThereIsNoRoomOnTheRight(t *testing.T) {
238 // A menu drawn past the edge of the screen is a menu nobody can read.
239 bar := NewMenuBar(
240 &Menu{Label: "A", Items: nil},
241 &Menu{Label: "B", Items: nil},
242 &Menu{Label: "~S~nippets", Items: []*MenuItem{
243 {Label: "A branch with a long name", Items: []*MenuItem{
244 {Label: "an item with a very long label indeed", Action: func() {}},
245 }},
246 }},
247 )
248 bar.SetBounds(Rect{W: 40, H: 1})
249 bar.OpenMenu(2)
250 key(bar, tcell.KeyRight)
251
252 sub := bar.submenuBounds()
253 if sub.Right() > bar.Bounds().Right() {
254 t.Errorf("the submenu at %+v runs past the screen %+v", sub, bar.Bounds())
255 }
256 if sub.X < bar.Bounds().X {
257 t.Errorf("the submenu at %+v starts before the screen", sub)
258 }
259}
260
261func TestMovingThePointerOntoABranchOpensItAndOffItClosesIt(t *testing.T) {
262 bar, _ := submenuBar()
263 bar.OpenMenu(1)
264 bounds := bar.dropdownBounds()
265
266 // Hover the first branch.
267 bar.HandleMouse(tcell.NewEventMouse(bounds.X+2, bounds.Y+1, tcell.ButtonNone, tcell.ModNone))
268 if !bar.submenuOpen() {
269 t.Fatal("hovering a branch did not open its submenu")
270 }
271
272 // Hover the plain item further down.
273 bar.HandleMouse(tcell.NewEventMouse(bounds.X+2, bounds.Y+4, tcell.ButtonNone, tcell.ModNone))
274 if bar.submenuOpen() {
275 t.Error("moving off a branch left its submenu showing")
276 }
277}
278
279func TestClickingASubmenuItemRunsIt(t *testing.T) {
280 bar, ran := submenuBar()
281 bar.OpenMenu(1)
282 key(bar, tcell.KeyRight)
283 sub := bar.submenuBounds()
284
285 bar.HandleMouse(tcell.NewEventMouse(sub.X+2, sub.Y+1, tcell.Button1, tcell.ModNone))
286
287 if len(*ran) != 1 || (*ran)[0] != "go/func" {
288 t.Errorf("ran %v, want the clicked submenu item", *ran)
289 }
290}
291
292func TestOnOpenRunsBeforeTheMenuDropsDown(t *testing.T) {
293 // A menu built from a file cannot be decided at start-up; this is the hook
294 // that fills it in at the only moment it is about to be seen.
295 filled := 0
296 menu := &Menu{Label: "~S~nippets"}
297 menu.OnOpen = func() {
298 filled++
299 menu.Items = []*MenuItem{{Label: "from the file", Action: func() {}}}
300 }
301
302 bar := NewMenuBar(menu)
303 bar.SetBounds(Rect{W: 80, H: 1})
304 bar.OpenMenu(0)
305
306 if filled != 1 {
307 t.Errorf("OnOpen ran %d times, want once", filled)
308 }
309 if len(menu.Items) != 1 {
310 t.Fatalf("the menu holds %d items after OnOpen", len(menu.Items))
311 }
312 if bar.highlight != 0 {
313 t.Errorf("highlight = %d; the highlight must be computed after OnOpen filled the menu", bar.highlight)
314 }
315}
316
317func TestAnEmptyMenuOpensWithoutPanicking(t *testing.T) {
318 bar := NewMenuBar(&Menu{Label: "~S~nippets"})
319 bar.SetBounds(Rect{W: 80, H: 1})
320
321 bar.OpenMenu(0)
322 key(bar, tcell.KeyDown)
323 key(bar, tcell.KeyRight)
324 key(bar, tcell.KeyEnter)
325}