turbo-editors/turbo-corepublic Fork 0
28d59854361aeda8541d853093e732126f3d7bff
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.

menu_test.go · 326 lines · 8.6 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 18h ago1package ui
2
3import (
4 "strings"
5 "testing"
6
7 "github.com/gdamore/tcell/v2"
8)
9
10// testMenuBar builds a bar of two menus and returns it with a log the actions
11// append to, so a test can see which item ran.
12func testMenuBar() (*MenuBar, *[]string) {
13 var log []string
14 record := func(name string) func() {
15 return func() { log = append(log, name) }
16 }
17
18 file := &Menu{Label: "~F~ile", Items: []*MenuItem{
19 {Label: "~N~ew", Shortcut: "F4", Action: record("new")},
20 {Label: "~O~pen…", Shortcut: "F3", Action: record("open")},
21 {Separator: true},
22 {Label: "~S~ave", Shortcut: "F2", Action: record("save"),
23 Enabled: func() bool { return false }},
24 {Label: "E~x~it", Shortcut: "Alt-X", Action: record("exit")},
25 }}
26 edit := &Menu{Label: "~E~dit", Items: []*MenuItem{
27 {Label: "~U~ndo", Action: record("undo")},
28 }}
29
30 return NewMenuBar(file, edit), &log
31}
32
33func TestAMenuBarStartsClosed(t *testing.T) {
34 bar, _ := testMenuBar()
35
36 if bar.Open() {
37 t.Error("Open() = true on a new menu bar")
38 }
39 if len(bar.Menus()) != 2 {
40 t.Errorf("Menus() returned %d menus, want 2", len(bar.Menus()))
41 }
42}
43
44func TestF10OpensTheFirstMenu(t *testing.T) {
45 bar, _ := testMenuBar()
46
47 if !bar.HandleKey(keyEvent(tcell.KeyF10, 0, tcell.ModNone)) {
48 t.Fatal("F10 was not handled")
49 }
50 if !bar.Open() {
51 t.Error("F10 did not open a menu")
52 }
53}
54
55func TestAltLetterOpensTheMatchingMenu(t *testing.T) {
56 bar, _ := testMenuBar()
57
58 if !bar.HandleKey(keyEvent(tcell.KeyRune, 'e', tcell.ModAlt)) {
59 t.Fatal("Alt-E was not handled")
60 }
61 if bar.openMenu != 1 {
62 t.Errorf("Alt-E opened menu %d, want the Edit menu", bar.openMenu)
63 }
64}
65
66func TestAltLetterThatMatchesNothingIsLeftAlone(t *testing.T) {
67 bar, _ := testMenuBar()
68
69 if bar.HandleKey(keyEvent(tcell.KeyRune, 'z', tcell.ModAlt)) {
70 t.Error("Alt-Z was claimed although no menu answers to it")
71 }
72 if bar.HandleKey(runeEvent('f')) {
73 t.Error("a plain letter opened a menu; only Alt-letter should")
74 }
75}
76
77func TestOpeningAMenuHighlightsItsFirstUsableItem(t *testing.T) {
78 bar, _ := testMenuBar()
79
80 bar.OpenMenu(0)
81
82 if bar.highlight != 0 {
83 t.Errorf("highlight = %d, want the first item", bar.highlight)
84 }
85}
86
87func TestArrowsWalkPastSeparatorsAndDisabledItems(t *testing.T) {
88 bar, _ := testMenuBar()
89 bar.OpenMenu(0) // New, Open…, ─, Save (disabled), Exit
90
91 bar.HandleKey(keyEvent(tcell.KeyDown, 0, tcell.ModNone))
92 if bar.highlight != 1 {
93 t.Fatalf("highlight = %d, want 1", bar.highlight)
94 }
95
96 bar.HandleKey(keyEvent(tcell.KeyDown, 0, tcell.ModNone))
97 if bar.highlight != 4 {
98 t.Errorf("highlight = %d, want 4 — the separator and the disabled item are skipped", bar.highlight)
99 }
100
101 bar.HandleKey(keyEvent(tcell.KeyDown, 0, tcell.ModNone))
102 if bar.highlight != 0 {
103 t.Errorf("highlight = %d, want it to wrap back to the first item", bar.highlight)
104 }
105
106 bar.HandleKey(keyEvent(tcell.KeyUp, 0, tcell.ModNone))
107 if bar.highlight != 4 {
108 t.Errorf("highlight = %d, want it to wrap backwards to the last usable item", bar.highlight)
109 }
110}
111
112func TestLeftAndRightMoveBetweenMenus(t *testing.T) {
113 bar, _ := testMenuBar()
114 bar.OpenMenu(0)
115
116 bar.HandleKey(keyEvent(tcell.KeyRight, 0, tcell.ModNone))
117 if bar.openMenu != 1 {
118 t.Errorf("openMenu = %d after Right, want 1", bar.openMenu)
119 }
120
121 bar.HandleKey(keyEvent(tcell.KeyRight, 0, tcell.ModNone))
122 if bar.openMenu != 0 {
123 t.Errorf("openMenu = %d, want it to wrap round to the first menu", bar.openMenu)
124 }
125
126 bar.HandleKey(keyEvent(tcell.KeyLeft, 0, tcell.ModNone))
127 if bar.openMenu != 1 {
128 t.Errorf("openMenu = %d after Left from the first menu, want the last one", bar.openMenu)
129 }
130}
131
132func TestEnterRunsTheHighlightedItemAndClosesTheMenu(t *testing.T) {
133 bar, log := testMenuBar()
134 bar.OpenMenu(0)
135
136 bar.HandleKey(keyEvent(tcell.KeyEnter, 0, tcell.ModNone))
137
138 if len(*log) != 1 || (*log)[0] != "new" {
139 t.Errorf("the action log is %v, want [new]", *log)
140 }
141 if bar.Open() {
142 t.Error("the menu stayed open after an item ran")
143 }
144}
145
146func TestTypingAnItemsHotKeyRunsIt(t *testing.T) {
147 bar, log := testMenuBar()
148 bar.OpenMenu(0)
149
150 bar.HandleKey(runeEvent('x'))
151
152 if len(*log) != 1 || (*log)[0] != "exit" {
153 t.Errorf("the action log is %v, want [exit]", *log)
154 }
155}
156
157func TestADisabledItemCannotBeRun(t *testing.T) {
158 bar, log := testMenuBar()
159 bar.OpenMenu(0)
160
161 bar.HandleKey(runeEvent('s')) // Save is disabled
162
163 if len(*log) != 0 {
164 t.Errorf("the action log is %v, want it empty", *log)
165 }
166 if !bar.Open() {
167 t.Error("the menu closed although nothing ran")
168 }
169}
170
171func TestAnOpenMenuSwallowsStrayTyping(t *testing.T) {
172 bar, _ := testMenuBar()
173 bar.OpenMenu(0)
174
175 if !bar.HandleKey(runeEvent('q')) {
176 t.Error("a letter that matches nothing escaped the open menu and would have reached the editor")
177 }
178}
179
180func TestEscapeClosesTheMenu(t *testing.T) {
181 bar, _ := testMenuBar()
182 bar.OpenMenu(0)
183
184 bar.HandleKey(keyEvent(tcell.KeyEscape, 0, tcell.ModNone))
185
186 if bar.Open() {
187 t.Error("Escape did not close the menu")
188 }
189}
190
191func TestAKeyTheOpenMenuHasNoUseForIsPassedOn(t *testing.T) {
192 bar, _ := testMenuBar()
193 bar.OpenMenu(0)
194
195 if bar.HandleKey(keyEvent(tcell.KeyF5, 0, tcell.ModNone)) {
196 t.Error("the open menu claimed a function key it does nothing with")
197 }
198}
199
200func TestOpenMenuIgnoresAnIndexOutOfRange(t *testing.T) {
201 bar, _ := testMenuBar()
202
203 bar.OpenMenu(99)
204
205 if bar.Open() {
206 t.Error("OpenMenu(99) opened something")
207 }
208}
209
210func TestTheMenuBarDrawsItsTitlesAndTheOpenDropdown(t *testing.T) {
211 screen, root := newTestScreen(t, 40, 12)
212 bar, _ := testMenuBar()
213 bar.SetBounds(Rect{X: 0, Y: 0, W: 40, H: 1})
214 bar.OpenMenu(0)
215
216 bar.Draw(root, testTheme(t))
217 screen.Show()
218
219 lines := screenLines(t, screen)
220 if !strings.Contains(lines[0], "File") || !strings.Contains(lines[0], "Edit") {
221 t.Errorf("the bar is %q, want both menu titles", lines[0])
222 }
223 if !strings.Contains(lines[2], "New") {
224 t.Errorf("row 2 is %q, want the first item of the open menu", lines[2])
225 }
226 if !strings.Contains(lines[2], "F4") {
227 t.Errorf("row 2 is %q, want the item's shortcut shown", lines[2])
228 }
229 if !strings.Contains(lines[4], "─") {
230 t.Errorf("row 4 is %q, want the separator drawn as a rule", lines[4])
231 }
232}
233
234func TestClickingATitleOpensAndClosesItsMenu(t *testing.T) {
235 bar, _ := testMenuBar()
236 bar.SetBounds(Rect{X: 0, Y: 0, W: 40, H: 1})
237
238 bar.HandleMouse(clickEvent(2, 0)) // on "File"
239 if bar.openMenu != 0 {
240 t.Fatalf("openMenu = %d, want the File menu open", bar.openMenu)
241 }
242
243 bar.HandleMouse(clickEvent(2, 0)) // the same title again
244 if bar.Open() {
245 t.Error("clicking the open menu's own title did not close it")
246 }
247}
248
249func TestClickingAnItemRunsIt(t *testing.T) {
250 bar, log := testMenuBar()
251 bar.SetBounds(Rect{X: 0, Y: 0, W: 40, H: 1})
252 bar.OpenMenu(0)
253
254 bounds := bar.dropdownBounds()
255 bar.HandleMouse(clickEvent(bounds.X+2, bounds.Y+2)) // the second item, "Open…"
256
257 if len(*log) != 1 || (*log)[0] != "open" {
258 t.Errorf("the action log is %v, want [open]", *log)
259 }
260}
261
262func TestClickingAwayFromAnOpenMenuClosesIt(t *testing.T) {
263 bar, log := testMenuBar()
264 bar.SetBounds(Rect{X: 0, Y: 0, W: 40, H: 1})
265 bar.OpenMenu(0)
266
267 bar.HandleMouse(clickEvent(38, 9))
268
269 if bar.Open() {
270 t.Error("a click elsewhere did not close the menu")
271 }
272 if len(*log) != 0 {
273 t.Errorf("the action log is %v, want it empty", *log)
274 }
275}
276
277func TestMovingOverAnItemHighlightsIt(t *testing.T) {
278 bar, log := testMenuBar()
279 bar.SetBounds(Rect{X: 0, Y: 0, W: 40, H: 1})
280 bar.OpenMenu(0)
281
282 bounds := bar.dropdownBounds()
283 bar.HandleMouse(tcell.NewEventMouse(bounds.X+2, bounds.Y+2, tcell.ButtonNone, tcell.ModNone))
284
285 if bar.highlight != 1 {
286 t.Errorf("highlight = %d, want the item under the pointer", bar.highlight)
287 }
288 if len(*log) != 0 {
289 t.Error("merely moving over an item ran it")
290 }
291}
292
293func TestAClickThatMissesAClosedBarIsNotHandled(t *testing.T) {
294 bar, _ := testMenuBar()
295 bar.SetBounds(Rect{X: 0, Y: 0, W: 40, H: 1})
296
297 if bar.HandleMouse(clickEvent(10, 6)) {
298 t.Error("a closed menu bar claimed a click well below it")
299 }
300}
301
302func TestSetMenusReplacesWhatTheBarShows(t *testing.T) {
303 bar := NewMenuBar(&Menu{Label: "~F~ile", Items: []*MenuItem{{Label: "Open"}}})
304
305 bar.SetMenus([]*Menu{{Label: "~T~ools", Items: []*MenuItem{{Label: "Echo"}}}})
306
307 if got := bar.Menus(); len(got) != 1 || PlainLabel(got[0].Label) != "Tools" {
308 t.Fatalf("Menus() = %v, want just Tools", got)
309 }
310}
311
312func TestSetMenusClosesWhatWasOpen(t *testing.T) {
313 // The open index points into the old slice: keeping it would drop down
314 // whichever menu happened to take that position, or index out of range.
315 bar := NewMenuBar(
316 &Menu{Label: "~F~ile", Items: []*MenuItem{{Label: "Open"}}},
317 &Menu{Label: "~E~dit", Items: []*MenuItem{{Label: "Copy"}}},
318 )
319 bar.OpenMenu(1)
320
321 bar.SetMenus([]*Menu{{Label: "~T~ools", Items: []*MenuItem{{Label: "Echo"}}}})
322
323 if bar.Open() {
324 t.Error("the bar is still open after its menus were replaced")
325 }
326}