overview_behavior_test.go RawHistoryFolder

1package core
2
3import (
4	"math"
5	"slices"
6	"testing"
7
8	"github.com/bnema/neferwl/internal/ports"
9)
10
11func TestOverviewVerticalBoundariesAndInputs(t *testing.T) {
12	m := stackMonitor()
13	w := m.Current()
14	m.ToggleOverview()
15	if m.stackFront(w) != (stackItem{stackFloat, 9}) {
16		t.Fatal("screen front")
17	}
18	m.overviewFocus(ActionFocusWorkspaceUp)
19	if m.stackFront(w).kind != stackColumns || m.Active != 0 {
20		t.Fatal("swipe did not visit card")
21	}
22	m.overviewFocus(ActionFocusWorkspaceUp)
23	if m.Active != 0 {
24		t.Fatal("up at top crossed workspace boundary")
25	}
26	m.overviewFocus(ActionFocusWorkspaceDown)
27	if m.stackFront(w).kind != stackFloat || m.Active != 0 {
28		t.Fatal("down did not return to front")
29	}
30	m.OverviewMove(0, 1)
31	if m.Active != 1 {
32		t.Fatal("down did not reach workspace below")
33	}
34	m.OverviewMove(0, -1)
35	if m.Active != 0 || m.stackFront(w).kind != stackFloat {
36		t.Fatal("row change kept stack cursor")
37	}
38	if !m.overviewScroll(ports.PointerAxis{Source: ports.AxisWheel, Vertical: ports.ScrollAxis{Set: true, V120: -120}}) || m.stackFront(w).kind != stackColumns {
39		t.Fatal("wheel did not visit card")
40	}
41	m.CancelOverview()
42}
43
44func TestOverviewPeekEnvelopeAndSingleCardHorizontal(t *testing.T) {
45	m := stackMonitor()
46	w := m.Current()
47	w.AddFloating(8, 300, 200)
48	w.AddFloating(7, 300, 200)
49	m.ToggleOverview()
50	front := previewOf(t, m.Layout(), 7)
51	back := previewOf(t, m.Layout(), 8)
52	step := float64(front.Rect.Y - back.Rect.Y)
53	if step < float64(w.Usable.H)*w.overviewZoom()*0.06 {
54		t.Fatalf("peek step %.1f", step)
55	}
56	y := w.Usable.Y + (w.Usable.H-w.rowHeight())/2
57	if back.Rect.Y < y || front.Rect.Y+front.Rect.H > y+w.rowHeight() {
58		t.Fatalf("outside row front %+v back %+v row %d..%d", front, back, y, y+w.rowHeight())
59	}
60	m.OverviewMove(1, 0)
61	if m.stackFront(w) != (stackItem{stackFloat, 7}) {
62		t.Fatal("right moved single card")
63	}
64	m.OverviewMove(-1, 0)
65	if m.stackFront(w) != (stackItem{stackFloat, 8}) {
66		t.Fatal("left did not send single card behind")
67	}
68	m.CancelOverview()
69}
70
71// The stash front is centred on the same row band as the stack's front,
72// whether that row has peeking cards or just one column-group card.
73func TestOverviewPileAlignedWithRowFront(t *testing.T) {
74	for _, stacked := range []bool{false, true} {
75		m := pileMonitor()
76		w := m.Current()
77		if stacked {
78			m.SetBorder(2)
79			w.AddFloating(9, 300, 200)
80		}
81		m.ToggleOverview()
82		ps := m.Layout()
83		pile := previewOf(t, ps, 7)
84		frontID := WindowID(1)
85		if stacked {
86			frontID = 9
87		}
88		front := previewOf(t, ps, frontID)
89		pileCentre := float64(pile.Rect.Y) + float64(pile.Rect.H)/2
90		frontBandCentre := float64(front.Rect.Y) + float64(w.Usable.H)*front.Preview/2
91		if math.Abs(pileCentre-frontBandCentre) > 1.5 {
92			t.Fatalf("stacked %v: pile centre %.1f, front band centre %.1f: pile %+v front %+v", stacked, pileCentre, frontBandCentre, pile, front)
93		}
94	}
95}
96
97func TestOverviewSingleCardLeftFallsBackToStash(t *testing.T) {
98	m := stackMonitor()
99	w := m.Current()
100	w.FocusID(1)
101	w.ToggleWindowStash()
102	w.RemoveWindow(2)
103	w.RemoveWindow(3)
104	// A covering float is the only stack item; stash stays independently reachable.
105	m.ToggleOverview()
106	m.OverviewMove(-1, 0)
107	if m.cardAt(w) < 0 {
108		t.Fatal("left with no card behind did not enter stash")
109	}
110	m.CancelOverview()
111}
112
113func TestOverviewStackMRUAndSize(t *testing.T) {
114	m := maximizedOverview()
115	w := m.Current()
116	m.ToggleOverview()
117	m.OverviewMove(0, -1)
118	m.ToggleOverview()
119	m.ToggleOverview()
120	if got := w.stack(); !slices.Equal(got, []stackItem{{stackColumn, 1}, {kind: stackColumns}}) {
121		t.Fatalf("stack %v", got)
122	}
123	if m.hiddenColumn(w) != 1 {
124		t.Fatalf("MRU preselection %d, want Firefox column", m.hiddenColumn(w))
125	}
126	if p := previewOf(t, m.Layout(), 2); math.Abs(float64(p.Rect.W)-float64(w.columnRectsFor(true)[1].W)*p.Preview) > 1 {
127		t.Fatalf("prior maximized buffer not fitted to cell: %+v", p)
128	}
129	m.CancelOverview()
130}
131
132// browsedMonitor has window 1 on workspace 1 and columns 2..5 on workspace
133// 2, focused on 5 with one column per screen, and shows workspace 1.
134func browsedMonitor(ov Overflow) (*Monitor, *Workspace) {
135	m := newMonitor("", "")
136	m.SetOutput(300, 200)
137	m.SetMaxColumns(1)
138	m.AddWindow(1)
139	m.Focus(1)
140	for id := WindowID(2); id <= 5; id++ {
141		m.AddWindow(id)
142	}
143	ws2 := m.Workspaces[1]
144	ws2.Overflow = ov
145	ws2.FocusID(5)
146	ws2.scroll()
147	m.Focus(0)
148	return m, ws2
149}
150
151// Escape gives every row the overview showed its focus and scroll back,
152// as first shown, even after leaving and revisiting it.
153func TestOverviewBrowsedRowsRestored(t *testing.T) {
154	paths := map[string][][2]int{
155		"browse":  {{0, 1}, {-1, 0}, {-1, 0}},
156		"revisit": {{0, 1}, {-1, 0}, {0, -1}, {0, 1}, {-1, 0}},
157	}
158	for _, ov := range []Overflow{OverflowScroll, OverflowFixed} {
159		for name, path := range paths {
160			m, ws2 := browsedMonitor(ov)
161			view := ws2.ViewX
162			m.ToggleOverview()
163			for _, d := range path {
164				m.OverviewMove(d[0], d[1])
165			}
166			m.CancelOverview()
167			if id, _ := ws2.Focused(); id != 5 || ws2.ViewX != view || m.Current() != m.Workspaces[0] {
168				t.Fatalf("%v %s: focus %d view %v (want %v) current %v", ov, name, id, ws2.ViewX, view, m.Active)
169			}
170		}
171	}
172}
173
174func TestOverviewReturnRestoresBrowsedRows(t *testing.T) {
175	m, ws2 := browsedMonitor(OverflowScroll)
176	m.Focus(2)
177	m.AddWindow(6)
178	m.Focus(0)
179	m.ToggleOverview()
180	m.OverviewMove(0, 1)
181	m.OverviewMove(-1, 0)
182	m.OverviewMove(-1, 0)
183	m.OverviewMove(0, 1)
184	if m.Current() != m.Workspaces[2] {
185		t.Fatal("not on workspace 3")
186	}
187	m.ToggleOverview()
188	if id, _ := ws2.Focused(); id != 5 || m.Current() != m.Workspaces[2] {
189		t.Fatalf("focus %d current %v", id, m.Active)
190	}
191}

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