feat/data-dnd e05cb15

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

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