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

desktop_test.go · 413 lines · 11.0 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// addWindows puts n windows on a desktop and returns them.
11func addWindows(d *Desktop, n int) []*Window {
12 windows := make([]*Window, n)
13 for i := range n {
14 windows[i] = NewWindow("w", &stubContent{consume: true})
15 windows[i].SetBounds(Rect{X: i * 3, Y: i * 2, W: 20, H: 8})
16 d.Add(windows[i])
17 }
18 return windows
19}
20
21func TestAddMakesTheNewWindowActiveAndNumbersThemAll(t *testing.T) {
22 d := NewDesktop()
23
24 windows := addWindows(d, 3)
25
26 if d.Active() != windows[2] {
27 t.Error("the last window added is not the active one")
28 }
29 for i, w := range windows {
30 if got := w.Number(); got != i+1 {
31 t.Errorf("window %d has number %d, want %d", i, got, i+1)
32 }
33 }
34 if windows[0].Active() || windows[1].Active() {
35 t.Error("more than one window is marked active")
36 }
37}
38
39func TestRemoveRenumbersTheRest(t *testing.T) {
40 d := NewDesktop()
41 windows := addWindows(d, 3)
42
43 if !d.Remove(windows[0]) {
44 t.Fatal("Remove() = false for a window that was on the desktop")
45 }
46
47 if d.Count() != 2 {
48 t.Errorf("Count() = %d, want 2", d.Count())
49 }
50 if got := windows[1].Number(); got != 1 {
51 t.Errorf("the remaining windows were not renumbered: got %d, want 1", got)
52 }
53 if d.Remove(NewWindow("stranger", nil)) {
54 t.Error("Remove() = true for a window that was never added")
55 }
56}
57
58func TestRemovingTheActiveWindowActivatesTheOneBehind(t *testing.T) {
59 d := NewDesktop()
60 windows := addWindows(d, 2)
61
62 d.Remove(windows[1])
63
64 if d.Active() != windows[0] || !windows[0].Active() {
65 t.Error("the window behind did not become active")
66 }
67}
68
69func TestFocusBringsAWindowToTheFront(t *testing.T) {
70 d := NewDesktop()
71 windows := addWindows(d, 3)
72
73 if !d.Focus(windows[0]) {
74 t.Fatal("Focus() = false for a window on the desktop")
75 }
76
77 if d.Active() != windows[0] {
78 t.Error("the focused window is not at the front")
79 }
80 if got := windows[0].Number(); got != 3 {
81 t.Errorf("the raised window has number %d, want 3 — it is now last", got)
82 }
83}
84
85func TestFocusNumber(t *testing.T) {
86 d := NewDesktop()
87 windows := addWindows(d, 3)
88
89 if !d.FocusNumber(2) {
90 t.Fatal("FocusNumber(2) = false")
91 }
92 if d.Active() != windows[1] {
93 t.Error("FocusNumber raised the wrong window")
94 }
95
96 for _, n := range []int{0, -1, 4, 99} {
97 if d.FocusNumber(n) {
98 t.Errorf("FocusNumber(%d) = true, want false", n)
99 }
100 }
101}
102
103func TestNextCyclesThroughTheWindows(t *testing.T) {
104 d := NewDesktop()
105 windows := addWindows(d, 3) // front to back: 2, 1, 0
106
107 d.Next()
108 if d.Active() != windows[1] {
109 t.Error("Next() did not bring the window behind forward")
110 }
111 d.Next()
112 if d.Active() != windows[0] {
113 t.Error("a second Next() did not carry on round")
114 }
115 d.Next()
116 if d.Active() != windows[2] {
117 t.Error("Next() did not wrap back to the start")
118 }
119}
120
121func TestNextWithFewerThanTwoWindowsDoesNothing(t *testing.T) {
122 d := NewDesktop()
123 d.Next() // must not panic on an empty desktop
124
125 windows := addWindows(d, 1)
126 d.Next()
127
128 if d.Active() != windows[0] {
129 t.Error("Next() disturbed a desktop holding one window")
130 }
131}
132
133func TestDrawPaintsTheBackdropThenTheWindowsBackToFront(t *testing.T) {
134 screen, root := newTestScreen(t, 40, 12)
135 d := NewDesktop()
136 d.SetBounds(Rect{X: 0, Y: 0, W: 40, H: 12})
137
138 back := NewWindow("back", nil)
139 back.SetBounds(Rect{X: 0, Y: 0, W: 20, H: 6})
140 front := NewWindow("front", nil)
141 front.SetBounds(Rect{X: 5, Y: 0, W: 20, H: 6})
142 d.Add(back)
143 d.Add(front)
144
145 d.Draw(root, testTheme(t))
146 screen.Show()
147
148 lines := screenLines(t, screen)
149 if !strings.Contains(lines[11], string(desktopPattern)) {
150 t.Errorf("the bottom row is %q, want the desktop pattern", lines[11])
151 }
152 if !strings.Contains(lines[0], "front") {
153 t.Errorf("the top row is %q, want the front window's title visible", lines[0])
154 }
155 if strings.Contains(lines[0], "back") {
156 t.Errorf("the top row is %q, want the back window covered", lines[0])
157 }
158}
159
160func TestAClickRaisesTheWindowItLandsOn(t *testing.T) {
161 d := NewDesktop()
162 d.SetBounds(Rect{X: 0, Y: 0, W: 40, H: 12})
163 back := NewWindow("back", &stubContent{consume: true})
164 back.SetBounds(Rect{X: 0, Y: 0, W: 10, H: 6})
165 front := NewWindow("front", &stubContent{consume: true})
166 front.SetBounds(Rect{X: 20, Y: 0, W: 10, H: 6})
167 d.Add(back)
168 d.Add(front)
169
170 if !d.HandleMouse(clickEvent(3, 3)) {
171 t.Fatal("the click was not handled")
172 }
173 if d.Active() != back {
174 t.Error("clicking a window did not bring it to the front")
175 }
176}
177
178func TestAClickOnTheBareDesktopIsNotHandled(t *testing.T) {
179 d := NewDesktop()
180 d.SetBounds(Rect{X: 0, Y: 0, W: 40, H: 12})
181 addWindows(d, 1)
182
183 if d.HandleMouse(clickEvent(38, 11)) {
184 t.Error("a click on the backdrop was reported as handled")
185 }
186}
187
188func TestADraggedWindowKeepsReceivingEventsThatLeaveIt(t *testing.T) {
189 d := NewDesktop()
190 d.SetBounds(Rect{X: 0, Y: 0, W: 60, H: 20})
191 w := NewWindow("t", nil)
192 w.SetBounds(Rect{X: 0, Y: 0, W: 10, H: 5})
193 d.Add(w)
194
195 d.HandleMouse(clickEvent(5, 0)) // grab the title bar
196 // The pointer jumps far outside the window, as a fast drag does.
197 d.HandleMouse(tcell.NewEventMouse(40, 12, tcell.Button1, tcell.ModNone))
198
199 if got := w.Bounds(); got.X != 35 || got.Y != 12 {
200 t.Errorf("Bounds() = %+v, want the window to have followed the pointer", got)
201 }
202}
203
204func TestKeysGoToTheActiveWindow(t *testing.T) {
205 d := NewDesktop()
206 back := &stubContent{consume: true}
207 front := &stubContent{consume: true}
208 d.Add(NewWindow("back", back))
209 d.Add(NewWindow("front", front))
210
211 d.HandleKey(runeEvent('x'))
212
213 if front.keys != 1 || back.keys != 0 {
214 t.Errorf("the active window saw %d keys and the other %d, want 1 and 0", front.keys, back.keys)
215 }
216}
217
218func TestKeysOnAnEmptyDesktopAreNotHandled(t *testing.T) {
219 if NewDesktop().HandleKey(runeEvent('x')) {
220 t.Error("an empty desktop claimed a key")
221 }
222}
223
224func TestTilePutsEveryWindowInItsOwnCell(t *testing.T) {
225 d := NewDesktop()
226 d.SetBounds(Rect{X: 0, Y: 1, W: 80, H: 22})
227 windows := addWindows(d, 4)
228
229 d.Tile()
230
231 for i, a := range windows {
232 for _, b := range windows[i+1:] {
233 if !a.Bounds().Intersect(b.Bounds()).IsEmpty() {
234 t.Errorf("tiled windows %+v and %+v overlap", a.Bounds(), b.Bounds())
235 }
236 }
237 }
238}
239
240func TestTileOnAnEmptyDesktopDoesNothing(t *testing.T) {
241 d := NewDesktop()
242 d.SetBounds(Rect{X: 0, Y: 0, W: 20, H: 10})
243 d.Tile() // must not panic
244}
245
246func TestCascadeOffsetsEachWindowAndKeepsItsTitleVisible(t *testing.T) {
247 d := NewDesktop()
248 d.SetBounds(Rect{X: 0, Y: 1, W: 80, H: 22})
249 windows := addWindows(d, 3)
250
251 d.Cascade()
252
253 for i := 1; i < len(windows); i++ {
254 previous, current := windows[i-1].Bounds(), windows[i].Bounds()
255 if current.X <= previous.X || current.Y <= previous.Y {
256 t.Errorf("window %d at %+v is not offset from %+v", i, current, previous)
257 }
258 }
259}
260
261func TestMaximizeFillsTheDesktop(t *testing.T) {
262 d := NewDesktop()
263 area := Rect{X: 0, Y: 1, W: 80, H: 22}
264 d.SetBounds(area)
265 w := addWindows(d, 1)[0]
266
267 d.ToggleMaximize(w)
268
269 if got := w.Bounds(); got != area {
270 t.Errorf("Bounds() = %+v, want the whole desktop %+v", got, area)
271 }
272}
273
274func TestAWindowFollowsTheDesktopWhenTheTerminalGrows(t *testing.T) {
275 d := NewDesktop()
276 d.SetBounds(Rect{X: 0, Y: 1, W: 80, H: 23})
277 w := NewWindow("t", nil)
278 w.SetBounds(Rect{X: 0, Y: 1, W: 78, H: 23}) // filling it, less the shadow
279 d.Add(w)
280
281 d.SetBounds(Rect{X: 0, Y: 1, W: 100, H: 30})
282
283 if got := w.Bounds(); got != (Rect{X: 0, Y: 1, W: 98, H: 30}) {
284 t.Errorf("Bounds() = %+v, want the window grown by the same amount as the desktop", got)
285 }
286}
287
288func TestAWindowFollowsTheDesktopWhenTheTerminalShrinks(t *testing.T) {
289 d := NewDesktop()
290 d.SetBounds(Rect{X: 0, Y: 1, W: 80, H: 23})
291 w := NewWindow("t", nil)
292 w.SetBounds(Rect{X: 0, Y: 1, W: 78, H: 23})
293 d.Add(w)
294
295 d.SetBounds(Rect{X: 0, Y: 1, W: 60, H: 15})
296
297 if got := w.Bounds(); got != (Rect{X: 0, Y: 1, W: 58, H: 15}) {
298 t.Errorf("Bounds() = %+v, want the window shrunk with the desktop", got)
299 }
300}
301
302func TestACascadedWindowKeepsItsOffsetWhileFollowingTheFarCorner(t *testing.T) {
303 d := NewDesktop()
304 d.SetBounds(Rect{X: 0, Y: 1, W: 80, H: 23})
305 w := NewWindow("t", nil)
306 w.SetBounds(Rect{X: 4, Y: 5, W: 74, H: 19}) // offset from the top left
307 d.Add(w)
308
309 d.SetBounds(Rect{X: 0, Y: 1, W: 100, H: 30})
310
311 got := w.Bounds()
312 if got.X != 4 || got.Y != 5 {
313 t.Errorf("Bounds() = %+v, want the top-left corner left alone", got)
314 }
315 if got.Right() != 98 || got.Bottom() != 31 {
316 t.Errorf("Bounds() = %+v, want the far corner to have kept its distance", got)
317 }
318}
319
320func TestAWindowThatDoesNotGrowIsOnlyMoved(t *testing.T) {
321 d := NewDesktop()
322 d.SetBounds(Rect{X: 0, Y: 1, W: 80, H: 23})
323 w := NewWindow("t", nil)
324 w.SetGrow(GrowNone)
325 w.SetBounds(Rect{X: 60, Y: 15, W: 20, H: 8})
326 d.Add(w)
327
328 d.SetBounds(Rect{X: 0, Y: 1, W: 40, H: 12})
329
330 got := w.Bounds()
331 if got.W != 20 {
332 t.Errorf("Bounds() = %+v, want its width kept", got)
333 }
334 if got.Right() > 40 || got.Bottom() > 13 {
335 t.Errorf("Bounds() = %+v, want it pulled back inside the smaller desktop", got)
336 }
337}
338
339func TestNoWindowEndsUpLargerThanTheDesktop(t *testing.T) {
340 d := NewDesktop()
341 d.SetBounds(Rect{X: 0, Y: 1, W: 80, H: 23})
342 w := NewWindow("t", nil)
343 w.SetGrow(GrowNone) // it does not follow, so only the clamp can save it
344 w.SetBounds(Rect{X: 0, Y: 1, W: 78, H: 23})
345 d.Add(w)
346
347 d.SetBounds(Rect{X: 0, Y: 1, W: 30, H: 10})
348
349 got := w.Bounds()
350 if got.W > 30 || got.H > 10 {
351 t.Errorf("Bounds() = %+v, want it held to the desktop's own size", got)
352 }
353}
354
355func TestAWindowIsNotShrunkBelowItsMinimumWhileTheDesktopCanHoldIt(t *testing.T) {
356 d := NewDesktop()
357 d.SetBounds(Rect{X: 0, Y: 1, W: 80, H: 23})
358 w := NewWindow("t", nil)
359 w.SetBounds(Rect{X: 0, Y: 1, W: 20, H: 6})
360 d.Add(w)
361
362 // Shrinking by more than the window is wide would leave it at nothing.
363 d.SetBounds(Rect{X: 0, Y: 1, W: 40, H: 20})
364
365 got := w.Bounds()
366 if got.W < MinWindowWidth || got.H < MinWindowHeight {
367 t.Errorf("Bounds() = %+v, want at least the minimum window size", got)
368 }
369}
370
371func TestOnATerminalSmallerThanTheMinimumTheDesktopWins(t *testing.T) {
372 // A window may be no larger than the desktop even when that means going
373 // below the size it could be dragged down to: overflowing a terminal this
374 // small helps nobody, and the painter clips the rest away anyway.
375 d := NewDesktop()
376 d.SetBounds(Rect{X: 0, Y: 1, W: 80, H: 23})
377 w := NewWindow("t", nil)
378 w.SetBounds(Rect{X: 0, Y: 1, W: 78, H: 23})
379 d.Add(w)
380
381 tiny := Rect{X: 0, Y: 1, W: 4, H: 2}
382 d.SetBounds(tiny)
383
384 if got := w.Bounds(); got.W > tiny.W || got.H > tiny.H {
385 t.Errorf("Bounds() = %+v, want it to fit inside %+v", got, tiny)
386 }
387}
388
389func TestALayoutThatChangesNothingLeavesTheWindowsAlone(t *testing.T) {
390 // SetBounds runs on every turn of the event loop, so it must be inert when
391 // the desktop has not moved. A window the user resized by hand would
392 // otherwise creep.
393 d := NewDesktop()
394 area := Rect{X: 0, Y: 1, W: 80, H: 23}
395 d.SetBounds(area)
396 w := NewWindow("t", nil)
397 w.SetBounds(Rect{X: 5, Y: 5, W: 30, H: 10})
398 d.Add(w)
399
400 for range 10 {
401 d.SetBounds(area)
402 }
403
404 if got := w.Bounds(); got != (Rect{X: 5, Y: 5, W: 30, H: 10}) {
405 t.Errorf("Bounds() = %+v, want it untouched", got)
406 }
407}
408
409func TestADocumentWindowFollowsBothEdgesByDefault(t *testing.T) {
410 if got := NewWindow("t", nil).Grow(); got != GrowBoth {
411 t.Errorf("Grow() = %v, want GrowBoth", got)
412 }
413}