feat/hints a471fa4

placement_test.go RawHistoryFolder

1package core
2
3import (
4	"image"
5	"slices"
6	"testing"
7
8	"github.com/bnema/neferwl/internal/ports"
9)
10
11func TestPlaceOutputs(t *testing.T) {
12	at := func(x, y int) *image.Point { return &image.Point{X: x, Y: y} }
13	rel := func(r ports.OutputRelation, to string, off int) ports.OutputAnchor {
14		return ports.OutputAnchor{Relation: r, To: to, Offset: off}
15	}
16	const (
17		rightOf = ports.RelationRightOf
18		leftOf  = ports.RelationLeftOf
19		above   = ports.RelationAbove
20		below   = ports.RelationBelow
21	)
22	tests := []struct {
23		name  string
24		items []placeItem
25		want  []image.Point
26	}{
27		{"none", nil, []image.Point{}},
28		{"one automatic", []placeItem{{name: "A", w: 400, h: 300}}, []image.Point{{X: 0, Y: 0}}},
29		{
30			"three automatic",
31			[]placeItem{{name: "A", w: 400, h: 300}, {name: "B", w: 200, h: 300}, {name: "C", w: 300, h: 300}},
32			[]image.Point{{X: 0, Y: 0}, {X: 400, Y: 0}, {X: 600, Y: 0}},
33		},
34		{
35			"explicit position then automatic",
36			[]placeItem{{name: "DP-1", w: 400, h: 300}, {name: "DP-2", w: 400, h: 300, pos: at(100, 40)}},
37			[]image.Point{{X: 500, Y: 0}, {X: 100, Y: 40}},
38		},
39		{
40			"explicit negative x leaves automatic at zero",
41			[]placeItem{{name: "A", w: 300, h: 300}, {name: "B", w: 400, h: 300, pos: at(-500, 0)}},
42			[]image.Point{{X: 0, Y: 0}, {X: -500, Y: 0}},
43		},
44		{
45			"all explicit with negative position",
46			[]placeItem{{name: "A", w: 400, h: 300, pos: at(-200, 12)}, {name: "B", w: 400, h: 300, pos: at(20, -120)}},
47			[]image.Point{{X: -200, Y: 12}, {X: 20, Y: -120}},
48		},
49		{
50			"automatic after explicit origins at both ends",
51			[]placeItem{{name: "A", w: 400, h: 300, pos: at(1000, 0)}, {name: "B", w: 400, h: 300, pos: at(0, 0)}, {name: "C", w: 300, h: 300}},
52			[]image.Point{{X: 1000, Y: 0}, {X: 0, Y: 0}, {X: 1400, Y: 0}},
53		},
54		{
55			"automatic around an explicit origin",
56			[]placeItem{{name: "A", w: 400, h: 300}, {name: "B", w: 800, h: 300, pos: at(0, 500)}, {name: "C", w: 300, h: 300}},
57			[]image.Point{{X: 800, Y: 0}, {X: 0, Y: 500}, {X: 1200, Y: 0}},
58		},
59		{
60			"right-of with offset",
61			[]placeItem{
62				{name: "DP-1", w: 2560, h: 1440},
63				{name: "DP-2", w: 1920, h: 1080, anchor: rel(rightOf, "DP-1", 180)},
64			},
65			[]image.Point{{X: 0, Y: 0}, {X: 2560, Y: 180}},
66		},
67		{
68			"left-of with negative offset",
69			[]placeItem{
70				{name: "DP-1", w: 2560, h: 1440, pos: at(100, 50)},
71				{name: "DP-2", w: 1920, h: 1080, anchor: rel(leftOf, "DP-1", -30)},
72			},
73			[]image.Point{{X: 100, Y: 50}, {X: -1820, Y: 20}},
74		},
75		{
76			"above",
77			[]placeItem{
78				{name: "DP-1", w: 2560, h: 1440},
79				{name: "DP-2", w: 1920, h: 1080, anchor: rel(above, "DP-1", 40)},
80			},
81			[]image.Point{{X: 0, Y: 0}, {X: 40, Y: -1080}},
82		},
83		{
84			"below",
85			[]placeItem{
86				{name: "DP-1", w: 2560, h: 1440},
87				{name: "DP-2", w: 1920, h: 1080, anchor: rel(below, "DP-1", -40)},
88			},
89			[]image.Point{{X: 0, Y: 0}, {X: -40, Y: 1440}},
90		},
91		{
92			"chain resolved against item order",
93			[]placeItem{
94				{name: "C", w: 100, h: 100, anchor: rel(rightOf, "B", 0)},
95				{name: "B", w: 200, h: 100, anchor: rel(below, "A", 10)},
96				{name: "A", w: 300, h: 150, pos: at(0, 0)},
97			},
98			[]image.Point{{X: 210, Y: 150}, {X: 10, Y: 150}, {X: 0, Y: 0}},
99		},
100		{
101			"two by three grid",
102			[]placeItem{
103				{name: "DP-1", w: 2560, h: 1440},
104				{name: "DP-2", w: 1920, h: 1080, anchor: rel(leftOf, "DP-1", 360)},
105				{name: "DP-3", w: 1920, h: 1080, anchor: rel(rightOf, "DP-1", 360)},
106				{name: "HDMI-1", w: 2560, h: 1440, anchor: rel(above, "DP-1", 0)},
107				{name: "DP-4", w: 1920, h: 1200, anchor: rel(leftOf, "HDMI-1", 240)},
108				{name: "DP-5", w: 1920, h: 1200, anchor: rel(rightOf, "HDMI-1", 240)},
109			},
110			[]image.Point{{X: 0, Y: 0}, {X: -1920, Y: 360}, {X: 2560, Y: 360}, {X: 0, Y: -1440}, {X: -1920, Y: -1200}, {X: 2560, Y: -1200}},
111		},
112		{
113			"missing reference is automatic, after the plain automatic ones",
114			[]placeItem{
115				{name: "A", w: 400, h: 300, anchor: rel(rightOf, "GONE", 50)},
116				{name: "B", w: 200, h: 300},
117			},
118			[]image.Point{{X: 200, Y: 0}, {X: 0, Y: 0}},
119		},
120		{
121			"missing reference left of a negative origin stays right of zero",
122			[]placeItem{
123				{name: "A", w: 400, h: 300, pos: at(-900, 0)},
124				{name: "B", w: 200, h: 300, anchor: rel(above, "GONE", 0)},
125			},
126			[]image.Point{{X: -900, Y: 0}, {X: 0, Y: 0}},
127		},
128		{
129			"cycle is broken at its first output, the rest follows it",
130			[]placeItem{
131				{name: "A", w: 400, h: 300, anchor: rel(rightOf, "B", 0)},
132				{name: "B", w: 200, h: 300, anchor: rel(below, "A", 0)},
133			},
134			[]image.Point{{X: 0, Y: 0}, {X: 0, Y: 300}},
135		},
136		{
137			"explicit origin beats relation",
138			[]placeItem{
139				{name: "A", w: 400, h: 300},
140				{name: "B", w: 200, h: 300, pos: at(7, 9), anchor: rel(rightOf, "A", 0)},
141			},
142			[]image.Point{{X: 207, Y: 0}, {X: 7, Y: 9}},
143		},
144		{
145			"relation follows an explicit origin",
146			[]placeItem{
147				{name: "A", w: 400, h: 300, pos: at(1000, 200)},
148				{name: "B", w: 200, h: 300, anchor: rel(rightOf, "A", 5)},
149			},
150			[]image.Point{{X: 1000, Y: 200}, {X: 1400, Y: 205}},
151		},
152		{
153			"anchored output is not overlapped by a later automatic one",
154			[]placeItem{
155				{name: "A", w: 400, h: 300},
156				{name: "B", w: 200, h: 300},
157				{name: "C", w: 300, h: 300, anchor: rel(rightOf, "A", 0)},
158			},
159			[]image.Point{{X: 0, Y: 0}, {X: 700, Y: 0}, {X: 400, Y: 0}},
160		},
161	}
162	for _, tc := range tests {
163		t.Run(tc.name, func(t *testing.T) {
164			if got := placeOutputs(tc.items); !slices.Equal(got, tc.want) {
165				t.Fatalf("got %v, want %v", got, tc.want)
166			}
167		})
168	}
169}

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