1package core
2
3import (
4 "slices"
5 "testing"
6)
7
8func stackMonitor() *Monitor {
9 m := overviewMonitor()
10 m.SetBorder(2)
11 m.AddFloating(9, 300, 200)
12 return m
13}
14
15func TestOverviewCoveringStackNavigation(t *testing.T) {
16 for _, columns := range []bool{false, true} {
17 m := stackMonitor()
18 w := m.Current()
19 if columns {
20 w.FocusID(3)
21 }
22 before := slices.Clone(order(w))
23 m.ToggleOverview()
24 ps := m.Layout()
25 f, c := previewOf(t, ps, 9), previewOf(t, ps, 3)
26 if f.Peek != columns || c.Peek == columns {
27 t.Fatalf("front float %+v columns %+v", f, c)
28 }
29 if !slices.Equal(order(w), before) {
30 t.Fatal("opening changed float order")
31 }
32 if columns {
33 m.OverviewMove(0, -1)
34 } else {
35 m.OverviewMove(-1, 0)
36 }
37 if columns {
38 if p := previewOf(t, m.Layout(), 9); p.Peek || !p.Focused {
39 t.Fatalf("float %+v", p)
40 }
41 } else {
42 if p := previewOf(t, m.Layout(), 3); p.Peek || !p.Focused {
43 t.Fatalf("column %+v", p)
44 }
45 }
46 m.ToggleOverview()
47 if columns {
48 wantOrder(t, w, 1, 2, 3, 9)
49 } else {
50 wantOrder(t, w, 9, 1, 2, 3)
51 }
52 }
53}
54
55func TestOverviewStackLeftWithoutStashRotatesBackward(t *testing.T) {
56 m := stackMonitor()
57 w := m.Current()
58 m.ToggleOverview()
59 m.OverviewMove(-1, 0)
60 if p := previewOf(t, m.Layout(), 3); p.Peek || !p.Focused {
61 t.Fatalf("left did not rotate to columns: %+v", p)
62 }
63 m.CancelOverview()
64 if id, _ := w.Focused(); id != 9 {
65 t.Fatalf("cancel focus %d", id)
66 }
67}
68
69func TestOverviewStackEscapePreservesFocusAndOrder(t *testing.T) {
70 for _, columns := range []bool{false, true} {
71 m := stackMonitor()
72 w := m.Current()
73 if columns {
74 w.FocusID(3)
75 }
76 before := slices.Clone(order(w))
77 focused, _ := w.Focused()
78 m.ToggleOverview()
79 m.OverviewMove(1, 0)
80 m.CancelOverview()
81 if id, _ := w.Focused(); id != focused || !slices.Equal(order(w), before) {
82 t.Fatalf("columns %v: focus %d want %d, order %v want %v", columns, id, focused, order(w), before)
83 }
84 }
85 // When the focused float is not last (a dialog sits over it), Escape
86 // must restore its position too; FocusID alone would raise it.
87 m := stackMonitor()
88 w := m.Current()
89 w.AddFloating(10, 20, 20)
90 w.FocusID(9)
91 before := slices.Clone(order(w))
92 m.ToggleOverview()
93 m.OverviewMove(1, 0)
94 m.CancelOverview()
95 if id, _ := w.Focused(); id != 9 || !slices.Equal(order(w), before) {
96 t.Fatalf("float focus %d order %v want %v", id, order(w), before)
97 }
98}
99
100func TestOverviewStackClickAndHitPriority(t *testing.T) {
101 for _, frontColumn := range []bool{false, true} {
102 m := stackMonitor()
103 w := m.Current()
104 if frontColumn {
105 w.FocusID(3)
106 }
107 m.ToggleOverview()
108 ps := m.Layout()
109 front, back := previewOf(t, ps, 9), previewOf(t, ps, 3)
110 if frontColumn {
111 front, back = back, front
112 }
113 // The overlap with the peeking column tile must hit the front.
114 x, y := back.Rect.X+back.Rect.W/2, max(front.Rect.Y, back.Rect.Y)+back.Rect.H/2
115 if frontColumn {
116 x = front.Rect.X + front.Rect.W/2
117 }
118 if got := overviewIn(m.Layout(), float64(x), float64(y)); got != front.ID {
119 t.Fatalf("overlap hit %d want %d (front %+v back %+v)", got, front.ID, front.Rect, back.Rect)
120 }
121 // The up/right exposed corner belongs to the peeking card.
122 x, y = back.Rect.X+back.Rect.W-1, back.Rect.Y+1
123 if got := overviewIn(m.Layout(), float64(x), float64(y)); got != back.ID {
124 t.Fatalf("peek hit %d want %d", got, back.ID)
125 }
126 m.OverviewPick(back.ID)
127 if id, _ := w.Focused(); id != back.ID || m.ov.open {
128 t.Fatalf("picked %d, focus %d", back.ID, id)
129 }
130 }
131}
132
133func TestOverviewStackStashDialogNeighbor(t *testing.T) {
134 m := stackMonitor()
135 w := m.Current()
136 w.AddFloating(10, 20, 20)
137 w.FocusID(9)
138 w.AddWindow(11)
139 w.FocusID(11)
140 w.ToggleWindowStash()
141 w.FocusID(9)
142 m.ToggleOverview()
143 if p := previewOf(t, m.Layout(), 10); !p.Hidden {
144 t.Fatalf("dialog %+v", p)
145 }
146 m.OverviewMove(-1, 0) // single-window float sends behind before stash
147 if m.stackFront(w).kind != stackColumns || m.cardAt(w) >= 0 {
148 t.Fatal("float did not send behind")
149 }
150 m.OverviewMove(-1, 0)
151 m.OverviewMove(-1, 0)
152 m.OverviewMove(-1, 0)
153 if m.cardAt(w) < 0 {
154 t.Fatal("left from first column did not enter stash")
155 }
156 m.OverviewMove(1, 0)
157 if m.cardAt(w) >= 0 {
158 t.Fatal("stash exit did not return to card")
159 }
160 m.CancelOverview()
161 m = stackMonitor()
162 m.Focus(1)
163 m.AddFloating(12, 300, 200)
164 m.Focus(0)
165 m.ToggleOverview()
166 if p, q := previewOf(t, m.Layout(), 12), previewOf(t, m.Layout(), 4); p.Hidden || q.Hidden || !p.Peek || !q.Peek || p.Focused || q.Focused {
167 t.Fatalf("neighbor %+v %+v", p, q)
168 }
169}
170
171func TestOverviewMultipleCoveringCards(t *testing.T) {
172 m := stackMonitor()
173 w := m.Current()
174 w.AddFloating(8, 300, 200)
175 w.AddFloating(7, 300, 200)
176 w.AddFloating(6, 300, 200)
177 m.ToggleOverview()
178 if p := previewOf(t, m.Layout(), 6); p.Peek {
179 t.Fatalf("front %+v", p)
180 }
181 if p := previewOf(t, m.Layout(), 7); !p.Peek {
182 t.Fatalf("peek %+v", p)
183 }
184 if p := previewOf(t, m.Layout(), 9); !p.Hidden {
185 t.Fatalf("far card %+v", p)
186 }
187 m.OverviewMove(0, -1)
188 if p := previewOf(t, m.Layout(), 7); p.Peek || !p.Focused {
189 t.Fatalf("up %+v", p)
190 }
191 if p := previewOf(t, m.Layout(), 6); !p.Peek || p.Rect.Y <= previewOf(t, m.Layout(), 7).Rect.Y {
192 t.Fatalf("passed card %+v", p)
193 }
194 m.OverviewMove(0, 1)
195 if p := previewOf(t, m.Layout(), 6); p.Peek {
196 t.Fatal("down did not return to screen front")
197 }
198 m.CancelOverview()
199}
200
201func TestOverviewFloatWithoutColumns(t *testing.T) {
202 m := monitor()
203 m.SetOutput(300, 200)
204 m.SetBorder(2)
205 w := m.Current()
206 w.AddFloating(9, 300, 200)
207 w.AddFloating(8, 300, 200)
208 m.ToggleOverview()
209 if p := previewOf(t, m.Layout(), 8); p.Peek || p.Hidden {
210 t.Fatalf("front float %+v", p)
211 }
212 m.OverviewMove(-1, 0)
213 if p := previewOf(t, m.Layout(), 9); p.Peek || p.Hidden {
214 t.Fatalf("back float %+v", p)
215 }
216 m.ToggleOverview()
217 if id, _ := w.Focused(); id != 9 {
218 t.Fatalf("focus %d", id)
219 }
220}
221
222func TestOverviewStackRowChangeResetsRotation(t *testing.T) {
223 m := stackMonitor()
224 m.ToggleOverview()
225 m.OverviewMove(0, -1)
226 if m.stackFront(m.Current()).kind != stackColumns {
227 t.Fatal("stack did not move to columns")
228 }
229 m.OverviewMove(0, -1) // at stack end: workspace above, if present
230 m.OverviewMove(0, 1)
231 if m.stackFront(m.Current()) != (stackItem{stackFloat, 9}) {
232 t.Fatal("row change retained cursor")
233 }
234 m.CancelOverview()
235}
236
237func TestOverviewStackInvalidatedOnRemoval(t *testing.T) {
238 m := stackMonitor()
239 w := m.Current()
240 m.ToggleOverview()
241 m.OverviewMove(1, 0)
242 w.RemoveWindow(9)
243 m.ToggleOverview()
244 if id, _ := w.Focused(); id != 3 {
245 t.Fatalf("removed selection focused %d", id)
246 }
247}
248
249// Stash navigation returns to the current front card without changing its cursor.
250func TestOverviewStashRightBringsColumnsFront(t *testing.T) {
251 m := stackMonitor()
252 w := m.Current()
253 w.FocusID(1)
254 w.ToggleWindowStash()
255 w.FocusID(9)
256 m.ToggleOverview()
257 m.OverviewMove(-1, 0) // float to columns
258 m.OverviewMove(-1, 0) // first column to stash
259 if m.cardAt(w) < 0 {
260 t.Fatal("not in stash")
261 }
262 m.OverviewMove(1, 0)
263 if m.cardAt(w) >= 0 || m.stackFront(w).kind != stackColumns {
264 t.Fatal("not returned to front column card")
265 }
266 m.ToggleOverview()
267 if id, _ := w.Focused(); id != 2 {
268 t.Fatalf("focus %d", id)
269 }
270}
271
272// Stack previews are not floats: the overview draws no float veil.
273func TestOverviewStackNoFloatVeil(t *testing.T) {
274 m := stackMonitor()
275 m.ToggleOverview()
276 if d := floatDim(m.Layout(), Rect{W: 300, H: 200}, 0.3); d != 0 {
277 t.Fatalf("veil %v in the overview", d)
278 }
279}
280
281// l past the last stash card returns to the front card, even with no columns.
282func TestOverviewPileRightToFloatCard(t *testing.T) {
283 m := monitor()
284 m.SetOutput(300, 200)
285 m.SetBorder(2)
286 w := m.Current()
287 m.AddWindow(1)
288 w.ToggleWindowStash()
289 w.ToggleStashVisible()
290 w.AddFloating(9, 300, 200)
291 m.ToggleOverview()
292 m.OverviewMove(-1, 0)
293 if m.card() != 1 {
294 t.Fatalf("h did not enter the pile: card %d", m.card())
295 }
296 m.OverviewMove(1, 0)
297 if m.card() != 0 || m.overviewTarget() != 9 {
298 t.Fatalf("l stayed in the pile: card %d target %d", m.card(), m.overviewTarget())
299 }
300}