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.

🛟 Updated. 28d5985 · on d662cebdb65b319885da903daf7eff9ab1bfbb78 · k33g · 19h ago
menu_test.go · 326 lines · 8.6 KBGo Blame HistoryRaw
  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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
package ui

import (
	"strings"
	"testing"

	"github.com/gdamore/tcell/v2"
)

// testMenuBar builds a bar of two menus and returns it with a log the actions
// append to, so a test can see which item ran.
func testMenuBar() (*MenuBar, *[]string) {
	var log []string
	record := func(name string) func() {
		return func() { log = append(log, name) }
	}

	file := &Menu{Label: "~F~ile", Items: []*MenuItem{
		{Label: "~N~ew", Shortcut: "F4", Action: record("new")},
		{Label: "~O~pen…", Shortcut: "F3", Action: record("open")},
		{Separator: true},
		{Label: "~S~ave", Shortcut: "F2", Action: record("save"),
			Enabled: func() bool { return false }},
		{Label: "E~x~it", Shortcut: "Alt-X", Action: record("exit")},
	}}
	edit := &Menu{Label: "~E~dit", Items: []*MenuItem{
		{Label: "~U~ndo", Action: record("undo")},
	}}

	return NewMenuBar(file, edit), &log
}

func TestAMenuBarStartsClosed(t *testing.T) {
	bar, _ := testMenuBar()

	if bar.Open() {
		t.Error("Open() = true on a new menu bar")
	}
	if len(bar.Menus()) != 2 {
		t.Errorf("Menus() returned %d menus, want 2", len(bar.Menus()))
	}
}

func TestF10OpensTheFirstMenu(t *testing.T) {
	bar, _ := testMenuBar()

	if !bar.HandleKey(keyEvent(tcell.KeyF10, 0, tcell.ModNone)) {
		t.Fatal("F10 was not handled")
	}
	if !bar.Open() {
		t.Error("F10 did not open a menu")
	}
}

func TestAltLetterOpensTheMatchingMenu(t *testing.T) {
	bar, _ := testMenuBar()

	if !bar.HandleKey(keyEvent(tcell.KeyRune, 'e', tcell.ModAlt)) {
		t.Fatal("Alt-E was not handled")
	}
	if bar.openMenu != 1 {
		t.Errorf("Alt-E opened menu %d, want the Edit menu", bar.openMenu)
	}
}

func TestAltLetterThatMatchesNothingIsLeftAlone(t *testing.T) {
	bar, _ := testMenuBar()

	if bar.HandleKey(keyEvent(tcell.KeyRune, 'z', tcell.ModAlt)) {
		t.Error("Alt-Z was claimed although no menu answers to it")
	}
	if bar.HandleKey(runeEvent('f')) {
		t.Error("a plain letter opened a menu; only Alt-letter should")
	}
}

func TestOpeningAMenuHighlightsItsFirstUsableItem(t *testing.T) {
	bar, _ := testMenuBar()

	bar.OpenMenu(0)

	if bar.highlight != 0 {
		t.Errorf("highlight = %d, want the first item", bar.highlight)
	}
}

func TestArrowsWalkPastSeparatorsAndDisabledItems(t *testing.T) {
	bar, _ := testMenuBar()
	bar.OpenMenu(0) // New, Open…, ─, Save (disabled), Exit

	bar.HandleKey(keyEvent(tcell.KeyDown, 0, tcell.ModNone))
	if bar.highlight != 1 {
		t.Fatalf("highlight = %d, want 1", bar.highlight)
	}

	bar.HandleKey(keyEvent(tcell.KeyDown, 0, tcell.ModNone))
	if bar.highlight != 4 {
		t.Errorf("highlight = %d, want 4 — the separator and the disabled item are skipped", bar.highlight)
	}

	bar.HandleKey(keyEvent(tcell.KeyDown, 0, tcell.ModNone))
	if bar.highlight != 0 {
		t.Errorf("highlight = %d, want it to wrap back to the first item", bar.highlight)
	}

	bar.HandleKey(keyEvent(tcell.KeyUp, 0, tcell.ModNone))
	if bar.highlight != 4 {
		t.Errorf("highlight = %d, want it to wrap backwards to the last usable item", bar.highlight)
	}
}

func TestLeftAndRightMoveBetweenMenus(t *testing.T) {
	bar, _ := testMenuBar()
	bar.OpenMenu(0)

	bar.HandleKey(keyEvent(tcell.KeyRight, 0, tcell.ModNone))
	if bar.openMenu != 1 {
		t.Errorf("openMenu = %d after Right, want 1", bar.openMenu)
	}

	bar.HandleKey(keyEvent(tcell.KeyRight, 0, tcell.ModNone))
	if bar.openMenu != 0 {
		t.Errorf("openMenu = %d, want it to wrap round to the first menu", bar.openMenu)
	}

	bar.HandleKey(keyEvent(tcell.KeyLeft, 0, tcell.ModNone))
	if bar.openMenu != 1 {
		t.Errorf("openMenu = %d after Left from the first menu, want the last one", bar.openMenu)
	}
}

func TestEnterRunsTheHighlightedItemAndClosesTheMenu(t *testing.T) {
	bar, log := testMenuBar()
	bar.OpenMenu(0)

	bar.HandleKey(keyEvent(tcell.KeyEnter, 0, tcell.ModNone))

	if len(*log) != 1 || (*log)[0] != "new" {
		t.Errorf("the action log is %v, want [new]", *log)
	}
	if bar.Open() {
		t.Error("the menu stayed open after an item ran")
	}
}

func TestTypingAnItemsHotKeyRunsIt(t *testing.T) {
	bar, log := testMenuBar()
	bar.OpenMenu(0)

	bar.HandleKey(runeEvent('x'))

	if len(*log) != 1 || (*log)[0] != "exit" {
		t.Errorf("the action log is %v, want [exit]", *log)
	}
}

func TestADisabledItemCannotBeRun(t *testing.T) {
	bar, log := testMenuBar()
	bar.OpenMenu(0)

	bar.HandleKey(runeEvent('s')) // Save is disabled

	if len(*log) != 0 {
		t.Errorf("the action log is %v, want it empty", *log)
	}
	if !bar.Open() {
		t.Error("the menu closed although nothing ran")
	}
}

func TestAnOpenMenuSwallowsStrayTyping(t *testing.T) {
	bar, _ := testMenuBar()
	bar.OpenMenu(0)

	if !bar.HandleKey(runeEvent('q')) {
		t.Error("a letter that matches nothing escaped the open menu and would have reached the editor")
	}
}

func TestEscapeClosesTheMenu(t *testing.T) {
	bar, _ := testMenuBar()
	bar.OpenMenu(0)

	bar.HandleKey(keyEvent(tcell.KeyEscape, 0, tcell.ModNone))

	if bar.Open() {
		t.Error("Escape did not close the menu")
	}
}

func TestAKeyTheOpenMenuHasNoUseForIsPassedOn(t *testing.T) {
	bar, _ := testMenuBar()
	bar.OpenMenu(0)

	if bar.HandleKey(keyEvent(tcell.KeyF5, 0, tcell.ModNone)) {
		t.Error("the open menu claimed a function key it does nothing with")
	}
}

func TestOpenMenuIgnoresAnIndexOutOfRange(t *testing.T) {
	bar, _ := testMenuBar()

	bar.OpenMenu(99)

	if bar.Open() {
		t.Error("OpenMenu(99) opened something")
	}
}

func TestTheMenuBarDrawsItsTitlesAndTheOpenDropdown(t *testing.T) {
	screen, root := newTestScreen(t, 40, 12)
	bar, _ := testMenuBar()
	bar.SetBounds(Rect{X: 0, Y: 0, W: 40, H: 1})
	bar.OpenMenu(0)

	bar.Draw(root, testTheme(t))
	screen.Show()

	lines := screenLines(t, screen)
	if !strings.Contains(lines[0], "File") || !strings.Contains(lines[0], "Edit") {
		t.Errorf("the bar is %q, want both menu titles", lines[0])
	}
	if !strings.Contains(lines[2], "New") {
		t.Errorf("row 2 is %q, want the first item of the open menu", lines[2])
	}
	if !strings.Contains(lines[2], "F4") {
		t.Errorf("row 2 is %q, want the item's shortcut shown", lines[2])
	}
	if !strings.Contains(lines[4], "─") {
		t.Errorf("row 4 is %q, want the separator drawn as a rule", lines[4])
	}
}

func TestClickingATitleOpensAndClosesItsMenu(t *testing.T) {
	bar, _ := testMenuBar()
	bar.SetBounds(Rect{X: 0, Y: 0, W: 40, H: 1})

	bar.HandleMouse(clickEvent(2, 0)) // on "File"
	if bar.openMenu != 0 {
		t.Fatalf("openMenu = %d, want the File menu open", bar.openMenu)
	}

	bar.HandleMouse(clickEvent(2, 0)) // the same title again
	if bar.Open() {
		t.Error("clicking the open menu's own title did not close it")
	}
}

func TestClickingAnItemRunsIt(t *testing.T) {
	bar, log := testMenuBar()
	bar.SetBounds(Rect{X: 0, Y: 0, W: 40, H: 1})
	bar.OpenMenu(0)

	bounds := bar.dropdownBounds()
	bar.HandleMouse(clickEvent(bounds.X+2, bounds.Y+2)) // the second item, "Open…"

	if len(*log) != 1 || (*log)[0] != "open" {
		t.Errorf("the action log is %v, want [open]", *log)
	}
}

func TestClickingAwayFromAnOpenMenuClosesIt(t *testing.T) {
	bar, log := testMenuBar()
	bar.SetBounds(Rect{X: 0, Y: 0, W: 40, H: 1})
	bar.OpenMenu(0)

	bar.HandleMouse(clickEvent(38, 9))

	if bar.Open() {
		t.Error("a click elsewhere did not close the menu")
	}
	if len(*log) != 0 {
		t.Errorf("the action log is %v, want it empty", *log)
	}
}

func TestMovingOverAnItemHighlightsIt(t *testing.T) {
	bar, log := testMenuBar()
	bar.SetBounds(Rect{X: 0, Y: 0, W: 40, H: 1})
	bar.OpenMenu(0)

	bounds := bar.dropdownBounds()
	bar.HandleMouse(tcell.NewEventMouse(bounds.X+2, bounds.Y+2, tcell.ButtonNone, tcell.ModNone))

	if bar.highlight != 1 {
		t.Errorf("highlight = %d, want the item under the pointer", bar.highlight)
	}
	if len(*log) != 0 {
		t.Error("merely moving over an item ran it")
	}
}

func TestAClickThatMissesAClosedBarIsNotHandled(t *testing.T) {
	bar, _ := testMenuBar()
	bar.SetBounds(Rect{X: 0, Y: 0, W: 40, H: 1})

	if bar.HandleMouse(clickEvent(10, 6)) {
		t.Error("a closed menu bar claimed a click well below it")
	}
}

func TestSetMenusReplacesWhatTheBarShows(t *testing.T) {
	bar := NewMenuBar(&Menu{Label: "~F~ile", Items: []*MenuItem{{Label: "Open"}}})

	bar.SetMenus([]*Menu{{Label: "~T~ools", Items: []*MenuItem{{Label: "Echo"}}}})

	if got := bar.Menus(); len(got) != 1 || PlainLabel(got[0].Label) != "Tools" {
		t.Fatalf("Menus() = %v, want just Tools", got)
	}
}

func TestSetMenusClosesWhatWasOpen(t *testing.T) {
	// The open index points into the old slice: keeping it would drop down
	// whichever menu happened to take that position, or index out of range.
	bar := NewMenuBar(
		&Menu{Label: "~F~ile", Items: []*MenuItem{{Label: "Open"}}},
		&Menu{Label: "~E~dit", Items: []*MenuItem{{Label: "Copy"}}},
	)
	bar.OpenMenu(1)

	bar.SetMenus([]*Menu{{Label: "~T~ools", Items: []*MenuItem{{Label: "Echo"}}}})

	if bar.Open() {
		t.Error("the bar is still open after its menus were replaced")
	}
}