1package core
2
3import (
4 "slices"
5 "testing"
6)
7
8// Move-to-workspace binds move the selected preview, not the focus the
9// overview opened on, and keep the overview open.
10func TestOverviewMove(t *testing.T) {
11 tests := []struct {
12 name string
13 action Action
14 follow bool
15 // row is the workspace index current after the move.
16 row int
17 }{
18 {"column next", ActionMoveColumnToWorkspaceNext, false, 0},
19 {"window next", ActionMoveWindowToWorkspaceNext, false, 0},
20 {"column numbered", "move-column-to-workspace 2", false, 0},
21 {"window numbered", "move-window-to-workspace 2", false, 0},
22 {"follows", ActionMoveColumnToWorkspaceNext, true, 1},
23 }
24 for _, tt := range tests {
25 t.Run(tt.name, func(t *testing.T) {
26 m := overviewMonitor()
27 m.SetFollowMove(tt.follow)
28 m.Workspaces[0].FocusID(3)
29 m.ToggleOverview()
30 m.OverviewMove(-1, 0)
31 if m.overviewTarget() != 2 {
32 t.Fatalf("selection %d, want 2", m.overviewTarget())
33 }
34 m.Apply(tt.action)
35 if !slices.Equal(m.Workspaces[0].windows(), []WindowID{1, 3}) || !slices.Contains(m.Workspaces[1].windows(), 2) {
36 t.Fatalf("window 2 not moved: %v", windows(m))
37 }
38 if !m.ov.open || m.Current() != m.Workspaces[tt.row] {
39 t.Fatalf("open %v row %d", m.ov.open, m.Active)
40 }
41 if tt.follow && m.overviewTarget() != 2 {
42 t.Fatalf("selection %d did not follow", m.overviewTarget())
43 }
44 m.CancelOverview()
45 if !slices.Contains(m.Workspaces[1].windows(), 2) {
46 t.Fatal("escape undid the move")
47 }
48 if id, _ := m.Workspaces[0].Focused(); m.Current() != m.Workspaces[0] || id != 3 {
49 t.Fatalf("workspace %d focus %d", m.Active, id)
50 }
51 })
52 }
53}
54
55// The window variant moves only the selected window of a stacked column.
56func TestOverviewMoveWindowFromStack(t *testing.T) {
57 m := overviewMonitor()
58 w := m.Current()
59 w.FocusID(2)
60 w.ConsumeOrExpel(-1)
61 if len(w.Columns) != 2 {
62 t.Fatalf("columns %v", windows(m))
63 }
64 w.FocusID(3)
65 m.ToggleOverview()
66 m.OverviewMove(-1, 0)
67 if m.overviewTarget() != 2 {
68 t.Fatalf("selection %d", m.overviewTarget())
69 }
70 m.Apply(ActionMoveWindowToWorkspaceNext)
71 if !slices.Equal(w.windows(), []WindowID{1, 3}) || !slices.Contains(m.Workspaces[1].windows(), 2) {
72 t.Fatalf("windows %v", windows(m))
73 }
74}
75
76// The selected column moves even when a covering float is the row's focus.
77func TestOverviewMoveSelectedUnderFloat(t *testing.T) {
78 m := stackMonitor()
79 m.ToggleOverview()
80 m.OverviewMove(0, -1)
81 id := m.overviewTarget()
82 if id == 0 || id == 9 {
83 t.Fatalf("selection %d", id)
84 }
85 m.Apply(ActionMoveColumnToWorkspaceNext)
86 if !slices.Contains(m.Workspaces[1].windows(), id) || m.Workspaces[1].isFloat(9) {
87 t.Fatalf("moved the wrong window: %v floats %v", windows(m), m.Workspaces[0].Floats)
88 }
89 // The provisional stack front went with the moved column.
90 if m.ov.row != nil || m.ov.selected != 0 {
91 t.Fatalf("stale selection: row %v selected %d", m.ov.row != nil, m.ov.selected)
92 }
93}
94
95// A bind with nothing to move or nowhere to go leaves the overview as is.
96func TestOverviewMoveNoop(t *testing.T) {
97 tests := []struct {
98 name string
99 setup func() *Monitor
100 action Action
101 }{
102 {"stash card", func() *Monitor {
103 m := pileMonitor()
104 m.ToggleOverview()
105 m.OverviewMove(-1, 0)
106 if m.cardAt(m.Current()) < 0 {
107 t.Fatal("no stash card selected")
108 }
109 return m
110 }, ActionMoveColumnToWorkspaceNext},
111 {"hidden small float", func() *Monitor {
112 m := newMonitor("", "")
113 m.SetOutput(300, 200)
114 m.AddFloating(9, 50, 50)
115 m.ToggleOverview()
116 return m
117 }, ActionMoveWindowToWorkspaceNext},
118 {"up from first", func() *Monitor {
119 m := overviewMonitor()
120 m.ToggleOverview()
121 m.OverviewMove(-1, 0)
122 return m
123 }, ActionMoveColumnToWorkspacePrev},
124 {"to its own row", func() *Monitor {
125 m := overviewMonitor()
126 m.ToggleOverview()
127 m.OverviewMove(-1, 0)
128 return m
129 }, "move-column-to-workspace 1"},
130 {"down from named", func() *Monitor {
131 m := namedOverviewMonitor()
132 m.ToggleNamed("game")
133 m.ToggleOverview()
134 return m
135 }, ActionMoveColumnToWorkspaceNext},
136 {"fixed maximize kept", func() *Monitor {
137 m := overviewMonitor()
138 w := m.Current()
139 w.Overflow = OverflowFixed
140 w.FocusID(3)
141 w.ToggleFullWidth()
142 m.ToggleOverview()
143 m.OverviewMove(0, -1)
144 return m
145 }, ActionMoveColumnToWorkspacePrev},
146 }
147 for _, tt := range tests {
148 t.Run(tt.name, func(t *testing.T) {
149 m := tt.setup()
150 before, ws := m.Layout(), windows(m)
151 m.Apply(tt.action)
152 if !slices.Equal(before, m.Layout()) || !slices.EqualFunc(ws, windows(m), slices.Equal) {
153 t.Fatalf("changed: %v -> %v", ws, windows(m))
154 }
155 })
156 }
157}
158
159// A browsed destination row keeps the moved window focused on close.
160func TestOverviewMoveDestinationKeepsFocus(t *testing.T) {
161 m := overviewMonitor()
162 m.ToggleOverview()
163 m.OverviewMove(0, 1)
164 m.OverviewMove(0, -1)
165 m.OverviewMove(-1, 0)
166 m.Apply(ActionMoveColumnToWorkspaceNext)
167 m.ToggleOverview()
168 if id, _ := m.Workspaces[1].Focused(); id != 2 {
169 t.Fatalf("destination focus %d, want 2", id)
170 }
171}
172
173// Closing after a move drops the moved window from the restored maximize
174// history.
175func TestOverviewMoveDropsMaximizeHistory(t *testing.T) {
176 m := overviewMonitor()
177 w := m.Current()
178 w.Overflow = OverflowFixed
179 w.FocusID(3)
180 w.ToggleFullWidth()
181 m.ToggleOverview()
182 m.Apply(ActionMoveColumnToWorkspaceNext)
183 if w.has(3) {
184 t.Fatal("3 not moved")
185 }
186 m.CancelOverview()
187 if slices.Contains(w.maximized, 3) {
188 t.Fatalf("maximized %v keeps a moved window", w.maximized)
189 }
190}
191
192// A fixed fullscreen row moves its window like the bind outside the
193// overview: fullscreen ends, and Escape leaves the row on a window still
194// there.
195func TestOverviewMoveFromFullscreenRow(t *testing.T) {
196 for _, follow := range []bool{false, true} {
197 m := newMonitor("", "")
198 m.SetOutput(300, 200)
199 m.SetMaxColumns(4)
200 for id := WindowID(1); id <= 4; id++ {
201 m.AddWindow(id)
202 }
203 w := m.Current()
204 w.Overflow = OverflowFixed
205 w.FocusID(2)
206 m.ToggleFullscreen()
207 m.SetFollowMove(follow)
208 m.ToggleOverview()
209 if m.overviewTarget() != 2 {
210 t.Fatalf("follow %v: selection %d", follow, m.overviewTarget())
211 }
212 m.Apply(ActionMoveColumnToWorkspaceNext)
213 if !slices.Equal(w.windows(), []WindowID{1, 3, 4}) || !slices.Equal(m.Workspaces[1].windows(), []WindowID{2}) {
214 t.Fatalf("follow %v: windows %v", follow, windows(m))
215 }
216 if w.fullscreen != 0 || m.Workspaces[1].fullscreen != 0 {
217 t.Fatalf("follow %v: fullscreen kept", follow)
218 }
219 m.CancelOverview()
220 if id, ok := w.Focused(); !ok || !w.has(id) || m.Current() != w {
221 t.Fatalf("follow %v: focus %d on workspace %d", follow, id, m.Active)
222 }
223 }
224}
225
226// A move from a browsed row back to the opening row keeps the moved window
227// focused there on Escape, like any destination.
228func TestOverviewMoveBackToOpeningRow(t *testing.T) {
229 m := overviewMonitor()
230 m.ToggleOverview()
231 m.OverviewMove(0, 1)
232 m.Apply("move-column-to-workspace 1")
233 m.CancelOverview()
234 if id, _ := m.Current().Focused(); m.Current() != m.Workspaces[0] || id != 4 {
235 t.Fatalf("workspace %d focus %d, want 4 on 1 (%v)", m.Active, id, windows(m))
236 }
237}