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}