v0.4.0 b6be3a8

neighbor_test.go RawHistoryFolder

1package core
2
3import (
4	"testing"
5
6	"github.com/bnema/neferwl/internal/ports"
7)
8
9type tile struct {
10	name       string
11	x, y, w, h int
12}
13
14// layoutCore builds a core whose screens sit at the given logical rectangles,
15// in order, with the screen at index focus focused.
16func layoutCore(focus int, tiles ...tile) *Core {
17	c := &Core{focusScreen: focus}
18	for _, t := range tiles {
19		s := &screen{info: ports.OutputInfo{Name: t.name}, mon: newMonitor(t.name, ""), x: t.x, y: t.y}
20		s.mon.SetOutput(t.w, t.h)
21		c.screens = append(c.screens, s)
22	}
23	return c
24}
25
26func TestNeighbor(t *testing.T) {
27	dirs := []direction{dirLeft, dirRight, dirUp, dirDown}
28	dirNames := [...]string{"left", "right", "up", "down"}
29	// want holds the expected neighbor name for left, right, up, down; "" is
30	// the edge.
31	type step struct {
32		from string
33		want [4]string
34	}
35	cases := []struct {
36		name  string
37		tiles []tile
38		steps []step
39	}{
40		{"row of three in connection order", []tile{{"B", 200, 0, 200, 100}, {"A", 0, 0, 200, 100}, {"C", 400, 0, 200, 100}}, []step{
41			{"A", [4]string{"", "B", "", ""}},
42			{"B", [4]string{"A", "C", "", ""}},
43			{"C", [4]string{"B", "", "", ""}},
44		}},
45		{"column of two", []tile{{"A", 0, 0, 200, 100}, {"B", 0, 100, 200, 100}}, []step{
46			{"A", [4]string{"", "", "", "B"}},
47			{"B", [4]string{"", "", "A", ""}},
48		}},
49		{"row with y offsets", []tile{{"A", 0, 0, 200, 100}, {"B", 200, 60, 200, 100}, {"C", 400, 120, 200, 100}}, []step{
50			// A and C share no edge range: only diagonal vertical neighbors.
51			{"A", [4]string{"", "B", "", "C"}},
52			{"B", [4]string{"A", "C", "", ""}},
53			{"C", [4]string{"B", "", "A", ""}},
54		}},
55		{"diagonal only", []tile{{"A", 0, 0, 100, 100}, {"B", 100, 150, 100, 100}}, []step{
56			{"A", [4]string{"", "B", "", "B"}},
57			{"B", [4]string{"A", "", "A", ""}},
58		}},
59		{"shared edge beats nearer diagonal", []tile{{"A", 0, 0, 100, 100}, {"B", 100, 150, 100, 100}, {"C", 300, 50, 100, 100}}, []step{
60			{"A", [4]string{"", "C", "", "B"}},
61		}},
62		{"smaller gap wins", []tile{{"A", 0, 0, 100, 100}, {"far", 300, 0, 100, 100}, {"near", 150, 0, 100, 100}}, []step{
63			{"A", [4]string{"", "near", "", ""}},
64		}},
65		{"larger shared edge wins at equal gap", []tile{{"A", 0, 0, 100, 100}, {"little", 100, 80, 100, 100}, {"big", 100, 20, 100, 60}}, []step{
66			{"A", [4]string{"", "big", "", ""}},
67		}},
68		{"closer center wins at equal overlap", []tile{{"A", 0, 0, 100, 100}, {"low", 100, 50, 100, 100}, {"mid", 100, -20, 100, 70}}, []step{
69			// Both share 50 rows; mid's center is nearer to A's.
70			{"A", [4]string{"", "mid", "", ""}},
71		}},
72		{"lowest index at a full tie", []tile{{"A", 0, 0, 100, 100}, {"first", 100, 0, 100, 100}, {"second", 100, 0, 100, 100}}, []step{
73			{"A", [4]string{"", "first", "", ""}},
74		}},
75		{"overlapping screens fall back to centers", []tile{{"A", 0, 0, 200, 100}, {"B", 100, 0, 200, 100}}, []step{
76			// Centers are 100 and 200 apart on x only.
77			{"A", [4]string{"", "B", "", ""}},
78			{"B", [4]string{"A", "", "", ""}},
79		}},
80		{"outputs anchored to the same reference", []tile{{"R", 0, 0, 100, 100}, {"X", 100, 0, 100, 100}, {"Y", 100, 0, 300, 50}}, []step{
81			// X and Y overlap; the strict pass finds nothing between them.
82			{"X", [4]string{"R", "Y", "Y", ""}},
83			// R is fully left of Y and wins over the closer, overlapping X.
84			{"Y", [4]string{"R", "", "", "X"}},
85		}},
86		{"same-height outputs anchored to the same reference: one is unreachable by direction", []tile{{"R", 0, 0, 100, 100}, {"Y", 100, 0, 300, 100}, {"X", 100, 0, 100, 100}}, []step{
87			{"R", [4]string{"", "Y", "", ""}},
88			{"Y", [4]string{"R", "", "", ""}},
89			{"X", [4]string{"R", "Y", "", ""}},
90		}},
91		{"strict candidate beats a closer overlapping one", []tile{{"A", 0, 0, 100, 100}, {"near", 50, 0, 100, 100}, {"far", 300, 0, 100, 100}}, []step{
92			{"A", [4]string{"", "far", "", ""}},
93			{"near", [4]string{"A", "far", "", ""}},
94		}},
95		{"identical centers are unreachable", []tile{{"A", 0, 0, 100, 100}, {"B", 0, 0, 100, 100}}, []step{
96			{"A", [4]string{"", "", "", ""}},
97			{"B", [4]string{"", "", "", ""}},
98		}},
99		{"placeholder ignored", []tile{{"A", 0, 0, 100, 100}, {"", 100, 0, 100, 100}, {"B", 300, 0, 100, 100}}, []step{
100			{"A", [4]string{"", "B", "", ""}},
101			{"B", [4]string{"A", "", "", ""}},
102		}},
103		{"two by three grid", []tile{
104			{"DP-1", 0, 0, 2560, 1440}, {"DP-2", -1920, 360, 1920, 1080}, {"DP-3", 2560, 360, 1920, 1080},
105			{"HDMI-1", 0, -1440, 2560, 1440}, {"DP-4", -1920, -1200, 1920, 1200}, {"DP-5", 2560, -1200, 1920, 1200},
106		}, []step{
107			{"DP-4", [4]string{"", "HDMI-1", "", "DP-2"}},
108			{"HDMI-1", [4]string{"DP-4", "DP-5", "", "DP-1"}},
109			{"DP-5", [4]string{"HDMI-1", "", "", "DP-3"}},
110			{"DP-2", [4]string{"", "DP-1", "DP-4", ""}},
111			{"DP-1", [4]string{"DP-2", "DP-3", "HDMI-1", ""}},
112			{"DP-3", [4]string{"DP-1", "", "DP-5", ""}},
113		}},
114	}
115	for _, tc := range cases {
116		t.Run(tc.name, func(t *testing.T) {
117			for _, st := range tc.steps {
118				c := layoutCore(0, tc.tiles...)
119				for i, s := range c.screens {
120					if s.name() == st.from {
121						c.focusScreen = i
122					}
123				}
124				for i, d := range dirs {
125					got := ""
126					if n := c.neighbor(d); n >= 0 {
127						got = c.screens[n].name()
128					}
129					if got != st.want[i] {
130						t.Errorf("from %s %s: got %q, want %q", st.from, dirNames[i], got, st.want[i])
131					}
132				}
133			}
134		})
135	}
136	if n := layoutCore(0, tile{"A", 0, 0, 10, 10}).neighbor(direction(0)); n != -1 {
137		t.Fatalf("invalid direction: %d", n)
138	}
139}

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