1package core
2
3import (
4 "slices"
5 "testing"
6)
7
8func cascadeWorkspace(n int) *Workspace {
9 w := &Workspace{Overflow: OverflowCascade, MaxColumns: 3}
10 w.SetOutput(900, 600)
11 for i := 1; i <= n; i++ {
12 w.AddWindow(WindowID(i))
13 }
14 return w
15}
16
17func TestCascadeBandsAndReveal(t *testing.T) {
18 for _, n := range []int{0, 1, 3, 4, 7} {
19 w := cascadeWorkspace(n)
20 ps := w.Layout()
21 if len(ps) != n {
22 t.Fatalf("n=%d placements=%d", n, len(ps))
23 }
24 for i, p := range ps {
25 want := Rect{X: i % 3 * 300, Y: (i/3)*600 - w.View, W: 300, H: 600}
26 if p.Rect != want {
27 t.Fatalf("n=%d window=%d rect=%v want=%v", n, p.ID, p.Rect, want)
28 }
29 }
30 if n > 0 && w.View != ((n-1)/3)*600 {
31 t.Fatalf("n=%d view=%d", n, w.View)
32 }
33 }
34}
35
36func TestCascadeCompactionAndNavigation(t *testing.T) {
37 w := cascadeWorkspace(7)
38 w.FocusID(2)
39 if !w.FocusWindow(1) {
40 t.Fatal("no lower band")
41 }
42 if id, _ := w.Focused(); id != 5 {
43 t.Fatalf("focus=%d", id)
44 }
45 w.RemoveWindow(1)
46 if w.View != 600 {
47 t.Fatalf("compacted view=%d", w.View)
48 }
49 w.SetMaxColumns(2)
50 if w.View != 600 {
51 t.Fatalf("changed max view=%d", w.View)
52 }
53 w.FocusID(2)
54 w.AddWindow(8)
55 if w.Columns[len(w.Columns)-1].Windows[0] != 8 {
56 t.Fatal("new column did not append")
57 }
58 w.SetUsable(Rect{X: 0, Y: 20, W: 900, H: 580})
59 if w.View != w.band(w.Focus)*580 {
60 t.Fatal("resize did not reveal band")
61 }
62 if !slices.Equal(w.snapPoints(), []float64{0, 580, 1160, 1740}) {
63 t.Fatalf("points=%v", w.snapPoints())
64 }
65}
66
67func TestCascadeCovers(t *testing.T) {
68 w := cascadeWorkspace(4)
69 w.FocusID(2)
70 w.ToggleFullWidth()
71 ps := w.Layout()
72 for _, p := range ps {
73 if p.ID == 2 && p.Rect.W != 900 {
74 t.Fatalf("full width=%v", p.Rect)
75 }
76 if (p.ID == 1 || p.ID == 3) && !p.Hidden {
77 t.Fatalf("sibling %d visible", p.ID)
78 }
79 }
80 w.ToggleFullWidth()
81 w.ToggleFullscreen()
82 for _, p := range w.Layout() {
83 if p.ID == 2 && p.Rect != w.Output {
84 t.Fatalf("fullscreen=%v", p.Rect)
85 }
86 if p.ID != 2 && !p.Hidden {
87 t.Fatalf("fullscreen sibling %d visible", p.ID)
88 }
89 }
90 w.ToggleFullscreen()
91 w.FocusID(4)
92 if w.View != 600 {
93 t.Fatal("fullscreen restore lost band reveal")
94 }
95}
96
97func TestCascadeClearsMaximizationOnBandChanges(t *testing.T) {
98 for _, move := range []struct {
99 name string
100 run func(*Workspace)
101 }{
102 {"focus", func(w *Workspace) { w.FocusWindow(1) }},
103 {"append", func(w *Workspace) { w.AddWindow(5) }},
104 {"insert", func(w *Workspace) { w.insertColumn(4, Column{Windows: []WindowID{5}}) }},
105 } {
106 t.Run(move.name, func(t *testing.T) {
107 w := cascadeWorkspace(4)
108 w.FocusID(1)
109 w.ToggleFullWidth()
110 move.run(w)
111 for _, col := range w.Columns {
112 if col.FullWidth {
113 t.Fatal("stale maximization")
114 }
115 }
116 })
117 }
118}
119
120func TestCascadeReloadAndStashRestoreClearMaximization(t *testing.T) {
121 w := cascadeWorkspace(3)
122 w.FocusID(1)
123 w.ToggleFullWidth()
124 w.restore(4, &origPlace{stacked: []WindowID{2}, col: 1})
125 for _, c := range w.Columns {
126 if c.FullWidth {
127 t.Fatal("restore retained maximization")
128 }
129 }
130 m := NewMonitor()
131 m.SetOutput(900, 600)
132 for i := WindowID(1); i <= 3; i++ {
133 m.AddWindow(i)
134 }
135 m.Current().FocusID(1)
136 m.Current().ToggleFullWidth()
137 m.Current().FocusID(3)
138 m.SetOverflow(OverflowCascade)
139 for _, c := range m.Current().Columns {
140 if c.FullWidth {
141 t.Fatal("reload retained maximization")
142 }
143 }
144}
145
146func TestCascadeGestureValidityDoesNotAllocate(t *testing.T) {
147 m := NewMonitor()
148 m.SetOutput(900, 600)
149 m.SetOverflow(OverflowCascade)
150 for i := WindowID(1); i <= 7; i++ {
151 m.AddWindow(i)
152 }
153 g := &swipeGesture{ws: m.Current(), points: m.Current().snapPoints(), horizontal: false, opens: m.overviewOpens}
154 if n := testing.AllocsPerRun(100, func() {
155 if g.columnsChanged(m) {
156 t.Fatal("unchanged gesture invalidated")
157 }
158 }); n != 0 {
159 t.Fatalf("allocations=%v", n)
160 }
161}
162
163func TestCascadeGeometryReusesBuffer(t *testing.T) {
164 w := cascadeWorkspace(7)
165 buf := w.columnRectsInto(nil, false)
166 if n := testing.AllocsPerRun(100, func() { buf = w.columnRectsInto(buf, false) }); n != 0 {
167 t.Fatalf("allocations=%v", n)
168 }
169}
170
171func TestMixedLayoutSwitchKeepsSourceAxis(t *testing.T) {
172 m := NewMonitor()
173 m.SetOutput(900, 600)
174 m.Current().Overflow = OverflowCascade
175 m.AddWindow(1)
176 m.Focus(1)
177 m.AddWindow(2)
178 m.Current().Overflow = OverflowScroll
179 m.Focus(0)
180 m.switchAxis = m.Current().policy().workspace
181 m.switchView.off = .5
182 ps := m.layoutInto(nil)
183 for _, p := range ps {
184 if p.ID == 1 && (p.Rect.X != -450 || p.Rect.Y != 0) {
185 t.Fatalf("cascade source offset=%v", p.Rect)
186 }
187 }
188 m.stopSwitch()
189 m.Focus(1)
190 m.switchAxis = m.Current().policy().workspace
191 m.switchView.off = -.5
192 ps = m.layoutInto(nil)
193 for _, p := range ps {
194 if p.ID == 2 && (p.Rect.X != 0 || p.Rect.Y != 300) {
195 t.Fatalf("scroll source offset=%v", p.Rect)
196 }
197 }
198}
199
200func TestCascadeViewportSnap(t *testing.T) {
201 w := cascadeWorkspace(7)
202 w.FocusID(3)
203 if w.snapFocus(600, true) != 5 {
204 t.Fatalf("nearest cell=%d", w.snapFocus(600, true))
205 }
206 if w.snapFocus(1200, true) != 6 {
207 t.Fatal("short band neighbour")
208 }
209 w.SetUsable(Rect{})
210 if got := w.snapPoints(); len(got) != 1 || got[0] != 0 {
211 t.Fatalf("zero area=%v", got)
212 }
213}