v0.3.0 cf370a0

workspace_size_test.go RawHistoryFolder

1package core_test
2
3import (
4	"testing"
5
6	"github.com/bnema/neferwl/internal/ports"
7)
8
9var ultrawide = ports.OutputInfo{Name: "DP-3", Make: "Acme", Model: "W", Serial: "3", Width: 3440, Height: 1440}
10
11// sizedRig runs an ultrawide with the named workspace "focus" (1920x1080)
12// bound to Alt+w, and shows it.
13func sizedRig(t *testing.T, overflow string, edit func(*ports.Config)) (*multiRig, ports.Scene) {
14	t.Helper()
15	r := startMulti(t, func(c *ports.Config) {
16		c.Layout.Gaps = 0
17		c.Layout.MaxColumns = 2
18		c.Workspaces = []ports.WorkspaceConfig{{Name: "focus", Size: [2]int{1920, 1080}, LayoutRules: ports.LayoutRules{Overflow: overflow}}}
19		c.Binds["Alt+w"] = "workspace focus"
20		if edit != nil {
21			edit(c)
22		}
23	}, ultrawide)
24	return r, r.key(t, "w", ports.ModAlt)[0]
25}
26
27var frame = ports.Rect{X: 760, Y: 180, W: 1920, H: 1080}
28
29func within(r, o ports.Rect) bool {
30	return r.X >= o.X && r.Y >= o.Y && r.X+r.W <= o.X+o.W && r.Y+r.H <= o.Y+o.H
31}
32
33func TestWorkspaceSizeInheritsByDefault(t *testing.T) {
34	r := startMulti(t, func(c *ports.Config) { c.Layout.Gaps = 0 }, ultrawide)
35	set := r.mapWindow(t, 1)
36	if set[0].WorkspaceClip != (ports.Rect{}) || set[0].Windows[0].Rect != (ports.Rect{W: 3440, H: 1440}) {
37		t.Fatalf("%+v", set[0])
38	}
39}
40
41func TestWorkspaceSizeScrollColumnsStayInFrame(t *testing.T) {
42	r, _ := sizedRig(t, "scroll", nil)
43	r.mapWindow(t, 1)
44	r.mapWindow(t, 2)
45	s := r.mapWindow(t, 3)[0]
46	if s.WorkspaceClip != frame || s.OutputWidth != 3440 || s.OutputHeight != 1440 {
47		t.Fatalf("clip %+v output %dx%d", s.WorkspaceClip, s.OutputWidth, s.OutputHeight)
48	}
49	for _, w := range s.Windows {
50		if w.Hidden || !w.Rect.Overlaps(frame) {
51			continue
52		}
53		if w.Rect.Y != frame.Y || w.Rect.H != frame.H {
54			t.Fatalf("%+v not framed", w)
55		}
56	}
57	// Focused column (last) ends at the frame's right edge, half the frame wide.
58	last := s.Windows[2].Rect
59	if last != (ports.Rect{X: 1720, Y: 180, W: 960, H: 1080}) {
60		t.Fatal(last)
61	}
62}
63
64func TestWorkspaceSizeFixedColumnsFillFrame(t *testing.T) {
65	r, _ := sizedRig(t, "fixed", nil)
66	r.mapWindow(t, 1)
67	s := r.mapWindow(t, 2)[0]
68	for i, want := range []ports.Rect{{X: 760, Y: 180, W: 960, H: 1080}, {X: 1720, Y: 180, W: 960, H: 1080}} {
69		if s.Windows[i].Rect != want {
70			t.Fatalf("%d: %+v", i, s.Windows[i])
71		}
72	}
73}
74
75func TestWorkspaceSizeFullscreenFillsFrame(t *testing.T) {
76	r, _ := sizedRig(t, "scroll", nil)
77	r.mapWindow(t, 1)
78	r.mapWindow(t, 2)
79	s := r.key(t, "f", ports.ModAlt|ports.ModShift)[0]
80	full := 0
81	for _, w := range s.Windows {
82		if w.Fullscreen {
83			full++
84			if w.Rect != frame {
85				t.Fatalf("%+v", w)
86			}
87		}
88	}
89	if full != 1 {
90		t.Fatal(s.Windows)
91	}
92}
93
94func TestWorkspaceSizeFixedFullscreenKeepsFrame(t *testing.T) {
95	r, _ := sizedRig(t, "fixed", nil)
96	r.mapWindow(t, 1)
97	r.mapWindow(t, 2)
98	s := r.key(t, "f", ports.ModAlt|ports.ModShift)[0]
99	if s.WorkspaceClip != frame {
100		t.Fatalf("fullscreen clip %+v", s.WorkspaceClip)
101	}
102	for _, w := range s.Windows {
103		if w.Fullscreen && w.Rect != frame {
104			t.Fatalf("%+v", w)
105		}
106	}
107	// Reload keeps the size; removing it frees the workspace.
108	cfg := r.cfg
109	r.reload <- ports.ConfigChanged{Config: cfg}
110	if s := receive(t, r.scenes)[0]; s.WorkspaceClip != frame {
111		t.Fatalf("after reload %+v", s.WorkspaceClip)
112	}
113	cfg.Workspaces = []ports.WorkspaceConfig{{Name: "focus", LayoutRules: ports.LayoutRules{Overflow: "fixed"}}}
114	r.reload <- ports.ConfigChanged{Config: cfg}
115	if s := receive(t, r.scenes)[0]; s.WorkspaceClip != (ports.Rect{}) {
116		t.Fatalf("after size removal %+v", s.WorkspaceClip)
117	}
118}
119
120func TestWorkspaceSizeFloatsContained(t *testing.T) {
121	r, _ := sizedRig(t, "scroll", nil)
122	r.mapWindow(t, 1)
123	r.client <- ports.WindowMapped{ID: 2, Floating: true, Width: 5000, Height: 4000}
124	s := receive(t, r.scenes)[0]
125	for _, w := range s.Windows {
126		if w.Floating && !within(w.Rect, frame) {
127			t.Fatalf("float %+v outside %+v", w.Rect, frame)
128		}
129	}
130}
131
132func TestWorkspaceSizeReloadAndRemoval(t *testing.T) {
133	r, _ := sizedRig(t, "scroll", nil)
134	r.mapWindow(t, 1)
135	cfg := r.cfg
136	cfg.Workspaces = []ports.WorkspaceConfig{{Name: "focus", Size: [2]int{1000, 600}}}
137	r.reload <- ports.ConfigChanged{Config: cfg}
138	s := receive(t, r.scenes)[0]
139	if want := (ports.Rect{X: 1220, Y: 420, W: 1000, H: 600}); s.WorkspaceClip != want || s.Windows[0].Rect != want {
140		t.Fatalf("%+v %+v", s.WorkspaceClip, s.Windows[0])
141	}
142	// Larger than the monitor clamps to it: no clip, no invisible area.
143	cfg.Workspaces = []ports.WorkspaceConfig{{Name: "focus", Size: [2]int{9000, 9000}}}
144	r.reload <- ports.ConfigChanged{Config: cfg}
145	s = receive(t, r.scenes)[0]
146	if s.WorkspaceClip != (ports.Rect{}) || s.Windows[0].Rect != (ports.Rect{W: 3440, H: 1440}) {
147		t.Fatalf("%+v %+v", s.WorkspaceClip, s.Windows[0])
148	}
149	cfg.Workspaces = []ports.WorkspaceConfig{{Name: "focus", Size: [2]int{1920, 1080}}}
150	r.reload <- ports.ConfigChanged{Config: cfg}
151	if s = receive(t, r.scenes)[0]; s.WorkspaceClip != frame {
152		t.Fatal(s.WorkspaceClip)
153	}
154	cfg.Workspaces = []ports.WorkspaceConfig{{Name: "focus"}}
155	r.reload <- ports.ConfigChanged{Config: cfg}
156	if s = receive(t, r.scenes)[0]; s.WorkspaceClip != (ports.Rect{}) || s.Windows[0].Rect != (ports.Rect{W: 3440, H: 1440}) {
157		t.Fatalf("%+v %+v", s.WorkspaceClip, s.Windows[0])
158	}
159}
160
161func TestWorkspaceSizeFollowsOutputResize(t *testing.T) {
162	r, _ := sizedRig(t, "scroll", nil)
163	r.mapWindow(t, 1)
164	smaller := ultrawide
165	smaller.Width, smaller.Height = 1600, 900
166	s := r.plug(t, smaller)[0]
167	// 1920x1080 no longer fits: it clamps to the monitor.
168	if s.WorkspaceClip != (ports.Rect{}) || s.Windows[0].Rect != (ports.Rect{W: 1600, H: 900}) {
169		t.Fatalf("%+v %+v", s.WorkspaceClip, s.Windows[0])
170	}
171	s = r.plug(t, ultrawide)[0]
172	if s.WorkspaceClip != frame || s.Windows[0].Rect != frame {
173		t.Fatalf("%+v %+v", s.WorkspaceClip, s.Windows[0])
174	}
175}
176
177func TestWorkspaceSizeReservedZoneIntersectsFrame(t *testing.T) {
178	r, _ := sizedRig(t, "scroll", nil)
179	// A bar inside the top of the monitor but above the frame reserves nothing of it.
180	bar := ports.LayerSurface{ID: 9, Layer: ports.LayerTop, Anchor: ports.AnchorTop | ports.AnchorLeft | ports.AnchorRight, Height: 100, ExclusiveZone: 100}
181	r.client <- ports.LayerChanged{Layers: []ports.LayerSurface{bar}}
182	receive(t, r.scenes)
183	s := r.mapWindow(t, 1)[0]
184	if s.Windows[0].Rect != frame {
185		t.Fatalf("%+v", s.Windows[0])
186	}
187	// A bar reaching into the frame shrinks it from the top.
188	bar.Height, bar.ExclusiveZone = 300, 300
189	r.client <- ports.LayerChanged{Layers: []ports.LayerSurface{bar}}
190	s = receive(t, r.scenes)[0]
191	if want := (ports.Rect{X: 760, Y: 300, W: 1920, H: 960}); s.Windows[0].Rect != want || s.WorkspaceClip != frame {
192		t.Fatalf("%+v clip %+v", s.Windows[0], s.WorkspaceClip)
193	}
194}
195
196func TestWorkspaceSizeFractionalScaleUnchanged(t *testing.T) {
197	r, _ := sizedRig(t, "scroll", func(c *ports.Config) {
198		c.Outputs = []ports.OutputConfig{{Name: "DP-3", Scale: 2}}
199	})
200	s := r.mapWindow(t, 1)[0]
201	// The monitor is 1720x720 logical; a 1920x1080 workspace clamps to it.
202	if s.OutputWidth != 1720 || s.OutputHeight != 720 || s.Scale != 2 || s.WorkspaceClip != (ports.Rect{}) {
203		t.Fatalf("%dx%d scale %v clip %+v", s.OutputWidth, s.OutputHeight, s.Scale, s.WorkspaceClip)
204	}
205}
206
207func TestWorkspaceSizeInputOutsideFrame(t *testing.T) {
208	r, _ := sizedRig(t, "scroll", nil)
209	r.mapWindow(t, 1)
210	r.mapWindow(t, 2)
211	for len(r.commands) > 0 {
212		<-r.commands
213	}
214	// Inside the frame the window under the pointer gets the focus; window 1
215	// is scrolled partly off but its visible part outside the frame is dead.
216	r.input <- ports.PointerMotion{X: 2000, Y: 700}
217	if v, ok := command(t, r.commands).(ports.PointerFocus); !ok || v.ID != 2 {
218		t.Fatal(v)
219	}
220	r.input <- ports.PointerMotion{X: 100, Y: 700}
221	for {
222		if v, ok := command(t, r.commands).(ports.PointerFocus); ok {
223			if v.ID != 0 {
224				t.Fatalf("outside the frame: %+v", v)
225			}
226			break
227		}
228	}
229}

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