main f2d166b

overview_move_test.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close