turbo-editors/turbo-corepublic Fork 0
v1.0.1
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.

events_test.go · 471 lines · 13.4 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 19h ago1package editor
2
3import (
4 "strings"
5 "testing"
6
7 "github.com/gdamore/tcell/v2"
8
📦 Turbo Core f3ade8d k33g 11h ago9 "rickub.com/turbo-editors/turbo-core/buffer"
🛟 Updated. 28d5985 k33g 19h ago10)
11
12func TestArrowKeysMoveTheCursor(t *testing.T) {
13 view, _, _ := newTestView(t, "abc\ndef", 40, 6)
14
15 view.HandleKey(key(tcell.KeyRight, tcell.ModNone))
16 view.HandleKey(key(tcell.KeyDown, tcell.ModNone))
17
18 if got := view.Buffer().Cursor(); got != (buffer.Position{Line: 1, Col: 1}) {
19 t.Errorf("Cursor() = %+v, want {Line:1 Col:1}", got)
20 }
21}
22
23func TestCtrlArrowsMoveByWord(t *testing.T) {
24 view, _, _ := newTestView(t, "alpha beta gamma", 40, 6)
25
26 view.HandleKey(key(tcell.KeyRight, tcell.ModCtrl))
27
28 if got := view.Buffer().Cursor().Col; got != 6 {
29 t.Errorf("Cursor().Col = %d, want the start of the second word", got)
30 }
31}
32
33func TestHomeAndEnd(t *testing.T) {
34 view, _, _ := newTestView(t, "abc\ndefgh", 40, 6)
35 view.Buffer().SetCursor(buffer.Position{Line: 1, Col: 2})
36
37 view.HandleKey(key(tcell.KeyEnd, tcell.ModNone))
38 if got := view.Buffer().Cursor().Col; got != 5 {
39 t.Errorf("End left the cursor at column %d, want 5", got)
40 }
41
42 view.HandleKey(key(tcell.KeyHome, tcell.ModNone))
43 if got := view.Buffer().Cursor().Col; got != 0 {
44 t.Errorf("Home left the cursor at column %d, want 0", got)
45 }
46
47 view.HandleKey(key(tcell.KeyHome, tcell.ModCtrl))
48 if got := view.Buffer().Cursor(); got != (buffer.Position{}) {
49 t.Errorf("Ctrl-Home left the cursor at %+v, want the start of the buffer", got)
50 }
51
52 view.HandleKey(key(tcell.KeyEnd, tcell.ModCtrl))
53 if got := view.Buffer().Cursor(); got != (buffer.Position{Line: 1, Col: 5}) {
54 t.Errorf("Ctrl-End left the cursor at %+v, want the end of the buffer", got)
55 }
56}
57
58func TestPageKeysMoveByAScreenful(t *testing.T) {
59 view, _, _ := newTestView(t, strings.Repeat("line\n", 100), 40, 12)
60 page := view.VisibleLines()
61
62 view.HandleKey(key(tcell.KeyPgDn, tcell.ModNone))
63
64 if got := view.Buffer().Cursor().Line; got != page {
65 t.Errorf("Cursor().Line = %d, want %d", got, page)
66 }
67
68 view.HandleKey(key(tcell.KeyPgUp, tcell.ModNone))
69 if got := view.Buffer().Cursor().Line; got != 0 {
70 t.Errorf("Cursor().Line = %d, want 0", got)
71 }
72}
73
74func TestShiftArrowsExtendTheSelection(t *testing.T) {
75 view, _, _ := newTestView(t, "hello world", 40, 6)
76
77 for range 5 {
78 view.HandleKey(key(tcell.KeyRight, tcell.ModShift))
79 }
80
81 if got := view.SelectedText(); got != "hello" {
82 t.Errorf("SelectedText() = %q, want %q", got, "hello")
83 }
84}
85
86func TestAPlainArrowDropsTheSelection(t *testing.T) {
87 view, _, _ := newTestView(t, "hello world", 40, 6)
88 view.HandleKey(key(tcell.KeyRight, tcell.ModShift))
89
90 view.HandleKey(key(tcell.KeyRight, tcell.ModNone))
91
92 if got := view.SelectedText(); got != "" {
93 t.Errorf("SelectedText() = %q, want the selection dropped", got)
94 }
95}
96
97func TestTypingInsertsCharacters(t *testing.T) {
98 view, _, _ := newTestView(t, "", 40, 6)
99 changes := 0
100 view.OnChange = func() { changes++ }
101
102 for _, r := range "func" {
103 view.HandleKey(typed(r))
104 }
105
106 if got := view.Buffer().Text(); got != "func" {
107 t.Errorf("Text() = %q, want %q", got, "func")
108 }
109 if changes != 4 {
110 t.Errorf("OnChange was called %d times, want 4", changes)
111 }
112}
113
114func TestCtrlAndAltLettersAreNotTyped(t *testing.T) {
115 view, _, _ := newTestView(t, "", 40, 6)
116
117 view.HandleKey(tcell.NewEventKey(tcell.KeyRune, 'x', tcell.ModAlt))
118
119 if got := view.Buffer().Text(); got != "" {
120 t.Errorf("Text() = %q, want a modified key not to have been typed", got)
121 }
122}
123
124func TestEnterInsertsALineAndKeepsTheIndent(t *testing.T) {
125 view, _, _ := newTestView(t, "\tfoo()", 40, 6)
126 view.Buffer().MoveLineEnd()
127
128 view.HandleKey(key(tcell.KeyEnter, tcell.ModNone))
129
130 if got := view.Buffer().Line(1); got != "\t" {
131 t.Errorf("Line(1) = %q, want the indent carried over", got)
132 }
133}
134
135func TestBackspaceAndDelete(t *testing.T) {
136 view, _, _ := newTestView(t, "abcd", 40, 6)
137 view.Buffer().SetCursor(buffer.Position{Line: 0, Col: 2})
138
139 view.HandleKey(key(tcell.KeyBackspace2, tcell.ModNone))
140 view.HandleKey(key(tcell.KeyDelete, tcell.ModNone))
141
142 if got := view.Buffer().Text(); got != "ad" {
143 t.Errorf("Text() = %q, want %q", got, "ad")
144 }
145}
146
147func TestTabInsertsATabWhenNothingIsSelected(t *testing.T) {
148 view, _, _ := newTestView(t, "x", 40, 6)
149
150 view.HandleKey(key(tcell.KeyTab, tcell.ModNone))
151
152 if got := view.Buffer().Text(); got != "\tx" {
153 t.Errorf("Text() = %q, want a tab inserted", got)
154 }
155}
156
157func TestTabIndentsEveryLineOfTheSelection(t *testing.T) {
158 view, _, _ := newTestView(t, "one\ntwo\nthree", 40, 6)
159 view.Buffer().SetCursor(buffer.Position{Line: 0, Col: 1})
160 view.Buffer().StartSelection()
161 view.Buffer().SetCursorKeepingSelection(buffer.Position{Line: 1, Col: 1})
162
163 view.HandleKey(key(tcell.KeyTab, tcell.ModNone))
164
165 if got := view.Buffer().Text(); got != "\tone\n\ttwo\nthree" {
166 t.Errorf("Text() = %q, want the first two lines indented", got)
167 }
168}
169
170func TestShiftTabRemovesOneIndentLevel(t *testing.T) {
171 tests := []struct {
172 name string
173 give string
174 want string
175 }{
176 {"a tab", "\tx", "x"},
177 {"eight spaces", " x", "x"},
178 {"three spaces", " x", "x"},
179 {"nothing to remove", "x", "x"},
180 }
181
182 for _, tc := range tests {
183 t.Run(tc.name, func(t *testing.T) {
184 view, _, _ := newTestView(t, tc.give, 40, 6)
185
186 view.HandleKey(key(tcell.KeyBacktab, tcell.ModNone))
187
188 if got := view.Buffer().Text(); got != tc.want {
189 t.Errorf("Text() = %q, want %q", got, tc.want)
190 }
191 })
192 }
193}
194
195func TestCutCopyAndPaste(t *testing.T) {
196 clipboard := &Clipboard{}
197 view, _, _ := newTestView(t, "hello world", 40, 6)
198 view.clipboard = clipboard
199
200 view.Buffer().SetCursor(buffer.Position{Line: 0, Col: 0})
201 view.Buffer().StartSelection()
202 view.Buffer().SetCursorKeepingSelection(buffer.Position{Line: 0, Col: 6})
203
204 view.HandleKey(key(tcell.KeyCtrlX, tcell.ModNone))
205 if got := view.Buffer().Text(); got != "world" {
206 t.Fatalf("Text() = %q after a cut, want %q", got, "world")
207 }
208 if got := clipboard.Text(); got != "hello " {
209 t.Errorf("the clipboard holds %q, want %q", got, "hello ")
210 }
211
212 view.Buffer().MoveBufferEnd()
213 view.HandleKey(key(tcell.KeyCtrlV, tcell.ModNone))
214 if got := view.Buffer().Text(); got != "worldhello " {
215 t.Errorf("Text() = %q after a paste, want %q", got, "worldhello ")
216 }
217}
218
219func TestTheTurboCClipboardKeysWorkToo(t *testing.T) {
220 clipboard := &Clipboard{}
221 view, _, _ := newTestView(t, "abc", 40, 6)
222 view.clipboard = clipboard
223 view.SelectAll()
224
225 view.HandleKey(key(tcell.KeyInsert, tcell.ModCtrl)) // Ctrl-Ins copies
226 if got := clipboard.Text(); got != "abc" {
227 t.Errorf("the clipboard holds %q, want %q", got, "abc")
228 }
229
230 view.Buffer().MoveBufferEnd()
231 view.HandleKey(key(tcell.KeyInsert, tcell.ModShift)) // Shift-Ins pastes
232 if got := view.Buffer().Text(); got != "abcabc" {
233 t.Errorf("Text() = %q, want %q", got, "abcabc")
234 }
235
236 view.SelectAll()
237 view.HandleKey(key(tcell.KeyDelete, tcell.ModShift)) // Shift-Del cuts
238 if got := view.Buffer().Text(); got != "" {
239 t.Errorf("Text() = %q, want it emptied by the cut", got)
240 }
241}
242
243func TestCopyingNothingDoesNotTouchTheClipboard(t *testing.T) {
244 clipboard := &Clipboard{}
245 clipboard.SetText("kept")
246 view, _, _ := newTestView(t, "abc", 40, 6)
247 view.clipboard = clipboard
248
249 if view.Copy() || view.Cut() {
250 t.Error("Copy or Cut reported success with nothing selected")
251 }
252 if got := clipboard.Text(); got != "kept" {
253 t.Errorf("the clipboard holds %q, want it untouched", got)
254 }
255}
256
257func TestPastingAnEmptyClipboardDoesNothing(t *testing.T) {
258 view, _, _ := newTestView(t, "abc", 40, 6)
259
260 if view.Paste() {
261 t.Error("Paste reported success with an empty clipboard")
262 }
263 if got := view.Buffer().Text(); got != "abc" {
264 t.Errorf("Text() = %q, want it unchanged", got)
265 }
266}
267
268func TestUndoAndRedoKeys(t *testing.T) {
269 view, _, _ := newTestView(t, "abc", 40, 6)
270 view.Buffer().MoveBufferEnd()
271 view.HandleKey(typed('d'))
272
273 view.HandleKey(key(tcell.KeyCtrlZ, tcell.ModNone))
274 if got := view.Buffer().Text(); got != "abc" {
275 t.Errorf("Text() = %q after undo, want %q", got, "abc")
276 }
277
278 // Ctrl-R, not Ctrl-Y: Ctrl-Y is Turbo C's delete-line, and that claim won.
279 view.HandleKey(key(tcell.KeyCtrlR, tcell.ModNone))
280 if got := view.Buffer().Text(); got != "abcd" {
281 t.Errorf("Text() = %q after redo, want %q", got, "abcd")
282 }
283}
284
285func TestCtrlYNoLongerRedoes(t *testing.T) {
286 // The half of the change that a user notices: the old key must not
287 // quietly keep working, or nobody learns the new one until it bites.
288 view, _, _ := newTestView(t, "abc", 40, 6)
289 view.Buffer().MoveBufferEnd()
290 view.HandleKey(typed('d'))
291 view.HandleKey(key(tcell.KeyCtrlZ, tcell.ModNone))
292
293 view.HandleKey(key(tcell.KeyCtrlY, tcell.ModNone))
294
295 if got := view.Buffer().Text(); got == "abcd" {
296 t.Error("Ctrl-Y redid the change; it deletes a line now")
297 }
298}
299
300func TestCtrlNInsertsALineAndCtrlYDeletesOne(t *testing.T) {
301 view, _, _ := newTestView(t, "one\ntwo\nthree\n", 40, 6)
302 view.Buffer().SetCursor(buffer.Position{Line: 1, Col: 1})
303
304 view.HandleKey(key(tcell.KeyCtrlN, tcell.ModNone))
305 if got := view.Buffer().Text(); got != "one\n\ntwo\nthree\n" {
306 t.Fatalf("after Ctrl-N Text() = %q", got)
307 }
308
309 view.HandleKey(key(tcell.KeyCtrlY, tcell.ModNone))
310 if got := view.Buffer().Text(); got != "one\n\nthree\n" {
311 t.Errorf("after Ctrl-Y Text() = %q, want the line the cursor was on gone", got)
312 }
313}
314
315func TestCtrlASelectsEverything(t *testing.T) {
316 view, _, _ := newTestView(t, "one\ntwo", 40, 6)
317
318 view.HandleKey(key(tcell.KeyCtrlA, tcell.ModNone))
319
320 if got := view.SelectedText(); got != "one\ntwo" {
321 t.Errorf("SelectedText() = %q, want the whole buffer", got)
322 }
323}
324
325func TestCtrlSpaceAsksForCompletion(t *testing.T) {
326 view, _, _ := newTestView(t, "fmt.", 40, 6)
327 asked := 0
328 view.OnCompletionRequest = func() { asked++ }
329
330 view.HandleKey(key(tcell.KeyCtrlSpace, tcell.ModNone))
331
332 if asked != 1 {
333 t.Errorf("completion was asked for %d times, want 1", asked)
334 }
335}
336
337func TestTypingADotAsksForCompletion(t *testing.T) {
338 view, _, _ := newTestView(t, "fmt", 40, 6)
339 view.Buffer().MoveBufferEnd()
340 asked := 0
341 view.OnCompletionRequest = func() { asked++ }
342
343 view.HandleKey(typed('.'))
344 view.HandleKey(typed('P'))
345
346 if asked != 1 {
347 t.Errorf("completion was asked for %d times, want 1 — only the dot triggers it", asked)
348 }
349}
350
351func TestAKeyTheViewHasNoUseForIsPassedOn(t *testing.T) {
352 view, _, _ := newTestView(t, "abc", 40, 6)
353
354 if view.HandleKey(key(tcell.KeyF9, tcell.ModNone)) {
355 t.Error("the view claimed a function key it does nothing with")
356 }
357}
358
359func TestWordBeforeCursor(t *testing.T) {
360 tests := []struct {
361 name string
362 text string
363 col int
364 want string
365 }{
366 {"mid-identifier", "fmt.Print", 9, "Print"},
367 {"just after a dot", "fmt.", 4, ""},
368 {"at the start of a line", "abc", 0, ""},
369 {"a whole word", "hello", 5, "hello"},
370 {"with digits and underscores", "a_1b", 4, "a_1b"},
371 }
372
373 for _, tc := range tests {
374 t.Run(tc.name, func(t *testing.T) {
375 view, _, _ := newTestView(t, tc.text, 40, 6)
376 view.Buffer().SetCursor(buffer.Position{Line: 0, Col: tc.col})
377
378 if got := view.WordBeforeCursor(); got != tc.want {
379 t.Errorf("WordBeforeCursor() = %q, want %q", got, tc.want)
380 }
381 })
382 }
383}
384
385func TestReplaceWordBeforeCursorAcceptsACompletion(t *testing.T) {
386 view, _, _ := newTestView(t, "fmt.Prin", 40, 6)
387 view.Buffer().MoveBufferEnd()
388
389 view.ReplaceWordBeforeCursor("Println")
390
391 if got := view.Buffer().Text(); got != "fmt.Println" {
392 t.Errorf("Text() = %q, want %q", got, "fmt.Println")
393 }
394}
395
396func TestGoToLine(t *testing.T) {
397 view, _, _ := newTestView(t, strings.Repeat("x\n", 50), 40, 8)
398
399 view.GoToLine(30)
400
401 if got := view.Buffer().Cursor().Line; got != 29 {
402 t.Errorf("Cursor().Line = %d, want 29 — GoToLine counts from one", got)
403 }
404 if top := view.TopLine(); 29 < top || 29 >= top+view.VisibleLines() {
405 t.Error("the view did not scroll to the requested line")
406 }
407}
408
409func TestClickingPlacesTheCursor(t *testing.T) {
410 view, _, _ := newTestView(t, "package main\nfunc main() {}", 40, 8)
411 gutter := view.gutterWidth()
412
413 view.HandleMouse(tcell.NewEventMouse(gutter+4, 1, tcell.Button1, tcell.ModNone))
414
415 if got := view.Buffer().Cursor(); got != (buffer.Position{Line: 1, Col: 4}) {
416 t.Errorf("Cursor() = %+v, want {Line:1 Col:4}", got)
417 }
418}
419
420func TestDraggingSelects(t *testing.T) {
421 view, _, _ := newTestView(t, "hello world", 40, 8)
422 gutter := view.gutterWidth()
423
424 view.HandleMouse(tcell.NewEventMouse(gutter, 0, tcell.Button1, tcell.ModNone))
425 view.HandleMouse(tcell.NewEventMouse(gutter+5, 0, tcell.Button1, tcell.ModNone))
426
427 if got := view.SelectedText(); got != "hello" {
428 t.Errorf("SelectedText() = %q, want %q", got, "hello")
429 }
430
431 view.HandleMouse(tcell.NewEventMouse(gutter+5, 0, tcell.ButtonNone, tcell.ModNone))
432 if view.selecting {
433 t.Error("the drag did not end when the button came back up")
434 }
435}
436
437func TestTheWheelScrolls(t *testing.T) {
438 view, _, _ := newTestView(t, strings.Repeat("line\n", 100), 40, 10)
439
440 view.HandleMouse(tcell.NewEventMouse(5, 5, tcell.WheelDown, tcell.ModNone))
441
442 if got := view.TopLine(); got != wheelStep {
443 t.Errorf("TopLine() = %d, want %d", got, wheelStep)
444 }
445
446 view.HandleMouse(tcell.NewEventMouse(5, 5, tcell.WheelUp, tcell.ModNone))
447 if got := view.TopLine(); got != 0 {
448 t.Errorf("TopLine() = %d, want 0", got)
449 }
450}
451
452func TestAMouseEventTheViewHasNoUseForIsPassedOn(t *testing.T) {
453 view, _, _ := newTestView(t, "abc", 40, 6)
454
455 if view.HandleMouse(tcell.NewEventMouse(1, 1, tcell.Button3, tcell.ModNone)) {
456 t.Error("the view claimed a middle-button event it does nothing with")
457 }
458}
459
460func TestTheCursorIsReportedWhenItMoves(t *testing.T) {
461 view, _, _ := newTestView(t, "abc", 40, 6)
462 moves := 0
463 view.OnCursorMove = func() { moves++ }
464
465 view.HandleKey(key(tcell.KeyRight, tcell.ModNone))
466 view.HandleKey(typed('x')) // an edit moves the cursor too
467
468 if moves != 2 {
469 t.Errorf("OnCursorMove was called %d times, want 2", moves)
470 }
471}