okami.mom/ultraviolet

git clone https://git.nkp.pet/okami.mom/ultraviolet

Vadimperf(layout): add LRU cache for layout splitting (#91)227cc8b

main
32.8 KiB1160 linesraw
1package layout
2
3import (
4	"fmt"
5	"reflect"
6	"strings"
7	"testing"
8
9	uv "github.com/charmbracelet/ultraviolet"
10	"github.com/charmbracelet/ultraviolet/screen"
11)
12
13func BenchmarkLayout_Split(b *testing.B) {
14	layout := Vertical(Len(2), Fill(1), Ratio{1, 5}, Percent(10))
15
16	area := uv.Rect(123, 123, 1000, 2000)
17
18	b.Run("without cache", func(b *testing.B) {
19		for b.Loop() {
20			_, _, err := layout.split(area)
21			if err != nil {
22				b.Fatal(err)
23			}
24		}
25	})
26
27	b.Run("with cache", func(b *testing.B) {
28		for b.Loop() {
29			_, _, err := layout.splitCached(area)
30			if err != nil {
31				b.Fatal(err)
32			}
33		}
34	})
35}
36
37func TestPriorityIsValid(t *testing.T) {
38	t.Parallel()
39
40	assert := func(ok bool) {
41		t.Helper()
42
43		if !ok {
44			t.Error("invalid priority")
45		}
46	}
47
48	// Ensures that the constants are defined in the correct order of priority.
49
50	assert(spacerSizeEq > maxSizeLTE)
51	assert(maxSizeLTE > maxSizeEq)
52	assert(minSizeGTE == maxSizeLTE)
53	assert(maxSizeLTE > lengthSizeEq)
54	assert(lengthSizeEq > percentSizeEq)
55	assert(percentSizeEq > ratioSizeEq)
56	assert(ratioSizeEq > maxSizeEq)
57	assert(minSizeGTE > fillGrow)
58	assert(fillGrow > grow)
59	assert(grow > spaceGrow)
60	assert(spaceGrow > allSegmentGrow)
61}
62
63type LayoutSplitTestCase struct {
64	Flex        Flex
65	Width       int
66	Constraints []Constraint
67	Want        string
68}
69
70func (tc LayoutSplitTestCase) Name() string {
71	return fmt.Sprintf("Flex(%s) Width(%d) Constraints(%s)", tc.Flex, tc.Width, tc.Constraints)
72}
73
74func (tc LayoutSplitTestCase) Test(t *testing.T) {
75	t.Helper()
76
77	t.Parallel()
78
79	letters(t, tc.Flex, tc.Constraints, tc.Width, tc.Want)
80}
81
82func TestLength(t *testing.T) {
83	t.Parallel()
84
85	testCases := []LayoutSplitTestCase{
86		{FlexLegacy, 1, []Constraint{Len(0)}, "a"},
87		{FlexLegacy, 1, []Constraint{Len(1)}, "a"},
88		{FlexLegacy, 1, []Constraint{Len(2)}, "a"},
89		{FlexLegacy, 2, []Constraint{Len(0)}, "aa"},
90		{FlexLegacy, 2, []Constraint{Len(1)}, "aa"},
91		{FlexLegacy, 2, []Constraint{Len(2)}, "aa"},
92		{FlexLegacy, 2, []Constraint{Len(3)}, "aa"},
93		{FlexLegacy, 1, []Constraint{Len(0), Len(0)}, "b"},
94		{FlexLegacy, 1, []Constraint{Len(0), Len(1)}, "b"},
95		{FlexLegacy, 1, []Constraint{Len(0), Len(2)}, "b"},
96		{FlexLegacy, 1, []Constraint{Len(1), Len(0)}, "a"},
97		{FlexLegacy, 1, []Constraint{Len(1), Len(1)}, "a"},
98		{FlexLegacy, 1, []Constraint{Len(1), Len(2)}, "a"},
99		{FlexLegacy, 1, []Constraint{Len(2), Len(0)}, "a"},
100		{FlexLegacy, 1, []Constraint{Len(2), Len(1)}, "a"},
101		{FlexLegacy, 1, []Constraint{Len(2), Len(2)}, "a"},
102		{FlexLegacy, 2, []Constraint{Len(0), Len(0)}, "bb"},
103		{FlexLegacy, 2, []Constraint{Len(0), Len(1)}, "bb"},
104		{FlexLegacy, 2, []Constraint{Len(0), Len(2)}, "bb"},
105		{FlexLegacy, 2, []Constraint{Len(0), Len(3)}, "bb"},
106		{FlexLegacy, 2, []Constraint{Len(1), Len(0)}, "ab"},
107		{FlexLegacy, 2, []Constraint{Len(1), Len(1)}, "ab"},
108		{FlexLegacy, 2, []Constraint{Len(1), Len(2)}, "ab"},
109		{FlexLegacy, 2, []Constraint{Len(1), Len(3)}, "ab"},
110		{FlexLegacy, 2, []Constraint{Len(2), Len(0)}, "aa"},
111		{FlexLegacy, 2, []Constraint{Len(2), Len(1)}, "aa"},
112		{FlexLegacy, 2, []Constraint{Len(2), Len(2)}, "aa"},
113		{FlexLegacy, 2, []Constraint{Len(2), Len(3)}, "aa"},
114		{FlexLegacy, 2, []Constraint{Len(3), Len(0)}, "aa"},
115		{FlexLegacy, 2, []Constraint{Len(3), Len(1)}, "aa"},
116		{FlexLegacy, 2, []Constraint{Len(3), Len(2)}, "aa"},
117		{FlexLegacy, 2, []Constraint{Len(3), Len(3)}, "aa"},
118		{FlexLegacy, 3, []Constraint{Len(2), Len(2)}, "aab"},
119	}
120
121	for _, tc := range testCases {
122		t.Run(tc.Name(), tc.Test)
123	}
124}
125
126func TestMax(t *testing.T) {
127	t.Parallel()
128
129	testCases := []LayoutSplitTestCase{
130		{FlexLegacy, 1, []Constraint{Max(0)}, "a"},
131		{FlexLegacy, 1, []Constraint{Max(1)}, "a"},
132		{FlexLegacy, 1, []Constraint{Max(2)}, "a"},
133		{FlexLegacy, 2, []Constraint{Max(0)}, "aa"},
134		{FlexLegacy, 2, []Constraint{Max(1)}, "aa"},
135		{FlexLegacy, 2, []Constraint{Max(2)}, "aa"},
136		{FlexLegacy, 2, []Constraint{Max(3)}, "aa"},
137		{FlexLegacy, 1, []Constraint{Max(0), Max(0)}, "b"},
138		{FlexLegacy, 1, []Constraint{Max(0), Max(1)}, "b"},
139		{FlexLegacy, 1, []Constraint{Max(0), Max(2)}, "b"},
140		{FlexLegacy, 1, []Constraint{Max(1), Max(0)}, "a"},
141		{FlexLegacy, 1, []Constraint{Max(1), Max(1)}, "a"},
142		{FlexLegacy, 1, []Constraint{Max(1), Max(2)}, "a"},
143		{FlexLegacy, 1, []Constraint{Max(2), Max(0)}, "a"},
144		{FlexLegacy, 1, []Constraint{Max(2), Max(1)}, "a"},
145		{FlexLegacy, 1, []Constraint{Max(2), Max(2)}, "a"},
146		{FlexLegacy, 2, []Constraint{Max(0), Max(0)}, "bb"},
147		{FlexLegacy, 2, []Constraint{Max(0), Max(1)}, "bb"},
148		{FlexLegacy, 2, []Constraint{Max(0), Max(2)}, "bb"},
149		{FlexLegacy, 2, []Constraint{Max(0), Max(3)}, "bb"},
150		{FlexLegacy, 2, []Constraint{Max(1), Max(0)}, "ab"},
151		{FlexLegacy, 2, []Constraint{Max(1), Max(1)}, "ab"},
152		{FlexLegacy, 2, []Constraint{Max(1), Max(2)}, "ab"},
153		{FlexLegacy, 2, []Constraint{Max(1), Max(3)}, "ab"},
154		{FlexLegacy, 2, []Constraint{Max(2), Max(0)}, "aa"},
155		{FlexLegacy, 2, []Constraint{Max(2), Max(1)}, "aa"},
156		{FlexLegacy, 2, []Constraint{Max(2), Max(2)}, "aa"},
157		{FlexLegacy, 2, []Constraint{Max(2), Max(3)}, "aa"},
158		{FlexLegacy, 2, []Constraint{Max(3), Max(0)}, "aa"},
159		{FlexLegacy, 2, []Constraint{Max(3), Max(1)}, "aa"},
160		{FlexLegacy, 2, []Constraint{Max(3), Max(2)}, "aa"},
161		{FlexLegacy, 2, []Constraint{Max(3), Max(3)}, "aa"},
162		{FlexLegacy, 3, []Constraint{Max(2), Max(2)}, "aab"},
163	}
164
165	for _, tc := range testCases {
166		t.Run(tc.Name(), tc.Test)
167	}
168}
169
170func TestMin(t *testing.T) {
171	t.Parallel()
172
173	testCases := []LayoutSplitTestCase{
174		{FlexLegacy, 1, []Constraint{Min(0), Min(0)}, "b"},
175		{FlexLegacy, 1, []Constraint{Min(0), Min(1)}, "b"},
176		{FlexLegacy, 1, []Constraint{Min(0), Min(2)}, "b"},
177		{FlexLegacy, 1, []Constraint{Min(1), Min(0)}, "a"},
178		{FlexLegacy, 1, []Constraint{Min(1), Min(1)}, "a"},
179		{FlexLegacy, 1, []Constraint{Min(1), Min(2)}, "a"},
180		{FlexLegacy, 1, []Constraint{Min(2), Min(0)}, "a"},
181		{FlexLegacy, 1, []Constraint{Min(2), Min(1)}, "a"},
182		{FlexLegacy, 1, []Constraint{Min(2), Min(2)}, "a"},
183		{FlexLegacy, 2, []Constraint{Min(0), Min(0)}, "bb"},
184		{FlexLegacy, 2, []Constraint{Min(0), Min(1)}, "bb"},
185		{FlexLegacy, 2, []Constraint{Min(0), Min(2)}, "bb"},
186		{FlexLegacy, 2, []Constraint{Min(0), Min(3)}, "bb"},
187		{FlexLegacy, 2, []Constraint{Min(1), Min(0)}, "ab"},
188		{FlexLegacy, 2, []Constraint{Min(1), Min(1)}, "ab"},
189		{FlexLegacy, 2, []Constraint{Min(1), Min(2)}, "ab"},
190		{FlexLegacy, 2, []Constraint{Min(1), Min(3)}, "ab"},
191		{FlexLegacy, 2, []Constraint{Min(2), Min(0)}, "aa"},
192		{FlexLegacy, 2, []Constraint{Min(2), Min(1)}, "aa"},
193		{FlexLegacy, 2, []Constraint{Min(2), Min(2)}, "aa"},
194		{FlexLegacy, 2, []Constraint{Min(2), Min(3)}, "aa"},
195		{FlexLegacy, 2, []Constraint{Min(3), Min(0)}, "aa"},
196		{FlexLegacy, 2, []Constraint{Min(3), Min(1)}, "aa"},
197		{FlexLegacy, 2, []Constraint{Min(3), Min(2)}, "aa"},
198		{FlexLegacy, 2, []Constraint{Min(3), Min(3)}, "aa"},
199		{FlexLegacy, 3, []Constraint{Min(2), Min(2)}, "aab"},
200	}
201
202	for _, tc := range testCases {
203		t.Run(tc.Name(), tc.Test)
204	}
205}
206
207func TestPercent(t *testing.T) {
208	t.Parallel()
209
210	testCases := []LayoutSplitTestCase{
211		{FlexStart, 10, []Constraint{Percent(0), Percent(0)}, "          "},
212		{FlexStart, 10, []Constraint{Percent(0), Percent(25)}, "bbb       "},
213		{FlexStart, 10, []Constraint{Percent(0), Percent(50)}, "bbbbb     "},
214		{FlexStart, 10, []Constraint{Percent(0), Percent(100)}, "bbbbbbbbbb"},
215		{FlexStart, 10, []Constraint{Percent(0), Percent(200)}, "bbbbbbbbbb"},
216		{FlexStart, 10, []Constraint{Percent(10), Percent(0)}, "a         "},
217		{FlexStart, 10, []Constraint{Percent(10), Percent(25)}, "abbb      "},
218		{FlexStart, 10, []Constraint{Percent(10), Percent(50)}, "abbbbb    "},
219		{FlexStart, 10, []Constraint{Percent(10), Percent(100)}, "abbbbbbbbb"},
220		{FlexStart, 10, []Constraint{Percent(10), Percent(200)}, "abbbbbbbbb"},
221		{FlexStart, 10, []Constraint{Percent(25), Percent(0)}, "aaa       "},
222		{FlexStart, 10, []Constraint{Percent(25), Percent(25)}, "aaabb     "},
223		{FlexStart, 10, []Constraint{Percent(25), Percent(50)}, "aaabbbbb  "},
224		{FlexStart, 10, []Constraint{Percent(25), Percent(100)}, "aaabbbbbbb"},
225		{FlexStart, 10, []Constraint{Percent(25), Percent(200)}, "aaabbbbbbb"},
226		{FlexStart, 10, []Constraint{Percent(33), Percent(0)}, "aaa       "},
227		{FlexStart, 10, []Constraint{Percent(33), Percent(25)}, "aaabbb    "},
228		{FlexStart, 10, []Constraint{Percent(33), Percent(50)}, "aaabbbbb  "},
229		{FlexStart, 10, []Constraint{Percent(33), Percent(100)}, "aaabbbbbbb"},
230		{FlexStart, 10, []Constraint{Percent(33), Percent(200)}, "aaabbbbbbb"},
231		{FlexStart, 10, []Constraint{Percent(50), Percent(0)}, "aaaaa     "},
232		{FlexStart, 10, []Constraint{Percent(50), Percent(50)}, "aaaaabbbbb"},
233		{FlexStart, 10, []Constraint{Percent(50), Percent(100)}, "aaaaabbbbb"},
234		{FlexStart, 10, []Constraint{Percent(100), Percent(0)}, "aaaaaaaaaa"},
235		{FlexStart, 10, []Constraint{Percent(100), Percent(50)}, "aaaaabbbbb"},
236		{FlexStart, 10, []Constraint{Percent(100), Percent(100)}, "aaaaabbbbb"},
237		{FlexStart, 10, []Constraint{Percent(100), Percent(200)}, "aaaaabbbbb"},
238		{FlexSpaceBetween, 10, []Constraint{Percent(0), Percent(0)}, "          "},
239		{FlexSpaceBetween, 10, []Constraint{Percent(0), Percent(25)}, "        bb"},
240		{FlexSpaceBetween, 10, []Constraint{Percent(0), Percent(50)}, "     bbbbb"},
241		{FlexSpaceBetween, 10, []Constraint{Percent(0), Percent(100)}, "bbbbbbbbbb"},
242		{FlexSpaceBetween, 10, []Constraint{Percent(0), Percent(200)}, "bbbbbbbbbb"},
243		{FlexSpaceBetween, 10, []Constraint{Percent(10), Percent(0)}, "a         "},
244		{FlexSpaceBetween, 10, []Constraint{Percent(10), Percent(25)}, "a       bb"},
245		{FlexSpaceBetween, 10, []Constraint{Percent(10), Percent(50)}, "a    bbbbb"},
246		{FlexSpaceBetween, 10, []Constraint{Percent(10), Percent(100)}, "abbbbbbbbb"},
247		{FlexSpaceBetween, 10, []Constraint{Percent(10), Percent(200)}, "abbbbbbbbb"},
248		{FlexSpaceBetween, 10, []Constraint{Percent(25), Percent(0)}, "aaa       "},
249		{FlexSpaceBetween, 10, []Constraint{Percent(25), Percent(25)}, "aaa     bb"},
250		{FlexSpaceBetween, 10, []Constraint{Percent(25), Percent(50)}, "aaa  bbbbb"},
251		{FlexSpaceBetween, 10, []Constraint{Percent(25), Percent(100)}, "aaabbbbbbb"},
252		{FlexLegacy, 10, []Constraint{Percent(25), Percent(200)}, "aaabbbbbbb"},
253		{FlexSpaceBetween, 10, []Constraint{Percent(33), Percent(0)}, "aaa       "},
254		{FlexSpaceBetween, 10, []Constraint{Percent(33), Percent(25)}, "aaa     bb"},
255		{FlexSpaceBetween, 10, []Constraint{Percent(33), Percent(50)}, "aaa  bbbbb"},
256		{FlexSpaceBetween, 10, []Constraint{Percent(33), Percent(100)}, "aaabbbbbbb"},
257		{FlexSpaceBetween, 10, []Constraint{Percent(33), Percent(200)}, "aaabbbbbbb"},
258		{FlexSpaceBetween, 10, []Constraint{Percent(50), Percent(0)}, "aaaaa     "},
259		{FlexSpaceBetween, 10, []Constraint{Percent(50), Percent(50)}, "aaaaabbbbb"},
260		{FlexSpaceBetween, 10, []Constraint{Percent(50), Percent(100)}, "aaaaabbbbb"},
261		{FlexSpaceBetween, 10, []Constraint{Percent(100), Percent(0)}, "aaaaaaaaaa"},
262		{FlexSpaceBetween, 10, []Constraint{Percent(100), Percent(50)}, "aaaaabbbbb"},
263		{FlexSpaceBetween, 10, []Constraint{Percent(100), Percent(100)}, "aaaaabbbbb"},
264		{FlexSpaceBetween, 10, []Constraint{Percent(100), Percent(200)}, "aaaaabbbbb"},
265	}
266
267	for _, tc := range testCases {
268		t.Run(tc.Name(), tc.Test)
269	}
270}
271
272func TestRatio(t *testing.T) {
273	t.Parallel()
274
275	testCases := []LayoutSplitTestCase{
276		{FlexLegacy, 1, []Constraint{Ratio{0, 1}}, "a"},
277		{FlexLegacy, 1, []Constraint{Ratio{0, 1}}, "a"},
278		{FlexLegacy, 2, []Constraint{Ratio{0, 1}}, "aa"},
279		{FlexLegacy, 10, []Constraint{Ratio{0, 1}, Ratio{0, 1}}, "bbbbbbbbbb"},
280		{FlexLegacy, 10, []Constraint{Ratio{0, 1}, Ratio{1, 4}}, "bbbbbbbbbb"},
281		{FlexLegacy, 10, []Constraint{Ratio{0, 1}, Ratio{1, 2}}, "bbbbbbbbbb"},
282		{FlexLegacy, 10, []Constraint{Ratio{0, 1}, Ratio{1, 1}}, "bbbbbbbbbb"},
283		{FlexLegacy, 10, []Constraint{Ratio{0, 1}, Ratio{2, 1}}, "bbbbbbbbbb"},
284		{FlexLegacy, 10, []Constraint{Ratio{1, 10}, Ratio{0, 1}}, "abbbbbbbbb"},
285		{FlexLegacy, 10, []Constraint{Ratio{1, 10}, Ratio{1, 4}}, "abbbbbbbbb"},
286		{FlexLegacy, 10, []Constraint{Ratio{1, 10}, Ratio{1, 2}}, "abbbbbbbbb"},
287		{FlexLegacy, 10, []Constraint{Ratio{1, 10}, Ratio{1, 1}}, "abbbbbbbbb"},
288		{FlexLegacy, 10, []Constraint{Ratio{1, 10}, Ratio{2, 1}}, "abbbbbbbbb"},
289		{FlexLegacy, 10, []Constraint{Ratio{1, 4}, Ratio{0, 1}}, "aaabbbbbbb"},
290		{FlexLegacy, 10, []Constraint{Ratio{1, 4}, Ratio{1, 4}}, "aaabbbbbbb"},
291		{FlexLegacy, 10, []Constraint{Ratio{1, 4}, Ratio{1, 2}}, "aaabbbbbbb"},
292		{FlexLegacy, 10, []Constraint{Ratio{1, 4}, Ratio{1, 1}}, "aaabbbbbbb"},
293		{FlexLegacy, 10, []Constraint{Ratio{1, 4}, Ratio{2, 1}}, "aaabbbbbbb"},
294		{FlexLegacy, 10, []Constraint{Ratio{1, 3}, Ratio{0, 1}}, "aaabbbbbbb"},
295		{FlexLegacy, 10, []Constraint{Ratio{1, 3}, Ratio{1, 4}}, "aaabbbbbbb"},
296		{FlexLegacy, 10, []Constraint{Ratio{1, 3}, Ratio{1, 2}}, "aaabbbbbbb"},
297		{FlexLegacy, 10, []Constraint{Ratio{1, 3}, Ratio{1, 1}}, "aaabbbbbbb"},
298		{FlexLegacy, 10, []Constraint{Ratio{1, 3}, Ratio{2, 1}}, "aaabbbbbbb"},
299		{FlexLegacy, 10, []Constraint{Ratio{1, 2}, Ratio{0, 1}}, "aaaaabbbbb"},
300		{FlexLegacy, 10, []Constraint{Ratio{1, 2}, Ratio{1, 2}}, "aaaaabbbbb"},
301		{FlexLegacy, 10, []Constraint{Ratio{1, 2}, Ratio{1, 1}}, "aaaaabbbbb"},
302		{FlexLegacy, 10, []Constraint{Ratio{1, 1}, Ratio{0, 1}}, "aaaaaaaaaa"},
303		{FlexLegacy, 10, []Constraint{Ratio{1, 1}, Ratio{1, 2}}, "aaaaaaaaaa"},
304		{FlexLegacy, 10, []Constraint{Ratio{1, 1}, Ratio{1, 1}}, "aaaaaaaaaa"},
305		{FlexStart, 10, []Constraint{Ratio{0, 1}, Ratio{0, 1}}, "          "},
306		{FlexStart, 10, []Constraint{Ratio{0, 1}, Ratio{1, 4}}, "bbb       "},
307		{FlexStart, 10, []Constraint{Ratio{0, 1}, Ratio{1, 2}}, "bbbbb     "},
308		{FlexStart, 10, []Constraint{Ratio{0, 1}, Ratio{1, 1}}, "bbbbbbbbbb"},
309		{FlexStart, 10, []Constraint{Ratio{0, 1}, Ratio{2, 1}}, "bbbbbbbbbb"},
310		{FlexStart, 10, []Constraint{Ratio{1, 10}, Ratio{0, 1}}, "a         "},
311		{FlexStart, 10, []Constraint{Ratio{1, 10}, Ratio{1, 4}}, "abbb      "},
312		{FlexStart, 10, []Constraint{Ratio{1, 10}, Ratio{1, 2}}, "abbbbb    "},
313		{FlexStart, 10, []Constraint{Ratio{1, 10}, Ratio{1, 1}}, "abbbbbbbbb"},
314		{FlexStart, 10, []Constraint{Ratio{1, 10}, Ratio{2, 1}}, "abbbbbbbbb"},
315		{FlexStart, 10, []Constraint{Ratio{1, 4}, Ratio{0, 1}}, "aaa       "},
316		{FlexStart, 10, []Constraint{Ratio{1, 4}, Ratio{1, 4}}, "aaabb     "},
317		{FlexStart, 10, []Constraint{Ratio{1, 4}, Ratio{1, 2}}, "aaabbbbb  "},
318		{FlexStart, 10, []Constraint{Ratio{1, 4}, Ratio{1, 1}}, "aaabbbbbbb"},
319		{FlexStart, 10, []Constraint{Ratio{1, 4}, Ratio{2, 1}}, "aaabbbbbbb"},
320		{FlexStart, 10, []Constraint{Ratio{1, 3}, Ratio{0, 1}}, "aaa       "},
321		{FlexStart, 10, []Constraint{Ratio{1, 3}, Ratio{1, 4}}, "aaabbb    "},
322		{FlexStart, 10, []Constraint{Ratio{1, 3}, Ratio{1, 2}}, "aaabbbbb  "},
323		{FlexStart, 10, []Constraint{Ratio{1, 3}, Ratio{1, 1}}, "aaabbbbbbb"},
324		{FlexStart, 10, []Constraint{Ratio{1, 3}, Ratio{2, 1}}, "aaabbbbbbb"},
325		{FlexStart, 10, []Constraint{Ratio{1, 2}, Ratio{0, 1}}, "aaaaa     "},
326		{FlexStart, 10, []Constraint{Ratio{1, 2}, Ratio{1, 2}}, "aaaaabbbbb"},
327		{FlexStart, 10, []Constraint{Ratio{1, 2}, Ratio{1, 1}}, "aaaaabbbbb"},
328		{FlexStart, 10, []Constraint{Ratio{1, 1}, Ratio{0, 1}}, "aaaaaaaaaa"},
329		{FlexStart, 10, []Constraint{Ratio{1, 1}, Ratio{1, 2}}, "aaaaabbbbb"},
330		{FlexStart, 10, []Constraint{Ratio{1, 1}, Ratio{1, 1}}, "aaaaabbbbb"},
331		{FlexStart, 10, []Constraint{Ratio{1, 1}, Ratio{2, 1}}, "aaaaabbbbb"},
332		{FlexSpaceBetween, 10, []Constraint{Ratio{0, 1}, Ratio{0, 1}}, "          "},
333		{FlexSpaceBetween, 10, []Constraint{Ratio{0, 1}, Ratio{1, 4}}, "        bb"},
334		{FlexSpaceBetween, 10, []Constraint{Ratio{0, 1}, Ratio{1, 2}}, "     bbbbb"},
335		{FlexSpaceBetween, 10, []Constraint{Ratio{0, 1}, Ratio{1, 1}}, "bbbbbbbbbb"},
336		{FlexSpaceBetween, 10, []Constraint{Ratio{0, 1}, Ratio{2, 1}}, "bbbbbbbbbb"},
337		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 10}, Ratio{0, 1}}, "a         "},
338		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 10}, Ratio{1, 4}}, "a       bb"},
339		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 10}, Ratio{1, 2}}, "a    bbbbb"},
340		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 10}, Ratio{1, 1}}, "abbbbbbbbb"},
341		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 10}, Ratio{2, 1}}, "abbbbbbbbb"},
342		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 4}, Ratio{0, 1}}, "aaa       "},
343		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 4}, Ratio{1, 4}}, "aaa     bb"},
344		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 4}, Ratio{1, 2}}, "aaa  bbbbb"},
345		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 4}, Ratio{1, 1}}, "aaabbbbbbb"},
346		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 4}, Ratio{2, 1}}, "aaabbbbbbb"},
347		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 3}, Ratio{0, 1}}, "aaa       "},
348		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 3}, Ratio{1, 4}}, "aaa     bb"},
349		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 3}, Ratio{1, 2}}, "aaa  bbbbb"},
350		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 3}, Ratio{1, 1}}, "aaabbbbbbb"},
351		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 3}, Ratio{2, 1}}, "aaabbbbbbb"},
352		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 2}, Ratio{0, 1}}, "aaaaa     "},
353		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 2}, Ratio{1, 2}}, "aaaaabbbbb"},
354		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 2}, Ratio{1, 1}}, "aaaaabbbbb"},
355		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 1}, Ratio{0, 1}}, "aaaaaaaaaa"},
356		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 1}, Ratio{1, 2}}, "aaaaabbbbb"},
357		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 1}, Ratio{1, 1}}, "aaaaabbbbb"},
358		{FlexSpaceBetween, 10, []Constraint{Ratio{1, 1}, Ratio{2, 1}}, "aaaaabbbbb"},
359	}
360
361	for _, tc := range testCases {
362		t.Run(tc.Name(), tc.Test)
363	}
364}
365
366type Rect = uv.Rectangle
367
368func TestEdgeCases(t *testing.T) {
369	t.Parallel()
370
371	testCases := []struct {
372		name        string
373		constraints []Constraint
374		direction   Direction
375		split       Rect
376		want        Splitted
377	}{
378		{
379			name: "50% 50% min(0) stretches into last",
380			constraints: []Constraint{
381				Percent(50),
382				Percent(50),
383				Min(0),
384			},
385			direction: DirectionVertical,
386			split:     uv.Rect(0, 0, 1, 1),
387			want: []Rect{
388				uv.Rect(0, 0, 1, 1),
389				uv.Rect(0, 1, 1, 0),
390				uv.Rect(0, 1, 1, 0),
391			},
392		},
393		{
394			name: "max(1) 99% min(0) stretches into last",
395			constraints: []Constraint{
396				Max(1),
397				Percent(99),
398				Min(0),
399			},
400			direction: DirectionVertical,
401			split:     uv.Rect(0, 0, 1, 1),
402			want: []Rect{
403				uv.Rect(0, 0, 1, 0),
404				uv.Rect(0, 0, 1, 1),
405				uv.Rect(0, 1, 1, 0),
406			},
407		},
408		{
409			name: "min(1) length(0) min(1)",
410			constraints: []Constraint{
411				Min(1),
412				Len(0),
413				Min(1),
414			},
415			direction: DirectionHorizontal,
416			split:     uv.Rect(0, 0, 1, 1),
417			want: []Rect{
418				uv.Rect(0, 0, 1, 1),
419				uv.Rect(1, 0, 0, 1),
420				uv.Rect(1, 0, 0, 1),
421			},
422		},
423		{
424			name: "stretches the 2nd last length instead of the last min based on ranking",
425			constraints: []Constraint{
426				Len(3),
427				Min(4),
428				Len(1),
429				Min(4),
430			},
431			direction: DirectionHorizontal,
432			split:     uv.Rect(0, 0, 7, 1),
433			want: []Rect{
434				uv.Rect(0, 0, 0, 1),
435				uv.Rect(0, 0, 4, 1),
436				uv.Rect(4, 0, 0, 1),
437				uv.Rect(4, 0, 3, 1),
438			},
439		},
440	}
441
442	for _, tc := range testCases {
443		t.Run(tc.name, func(t *testing.T) {
444			t.Parallel()
445
446			layout := Layout{
447				Constraints: tc.constraints,
448				Direction:   tc.direction,
449			}.Split(tc.split)
450
451			if !reflect.DeepEqual(tc.want, layout) {
452				t.Fatalf("not equal: want %#+v, got %#+v", tc.want, layout)
453			}
454		})
455	}
456}
457
458func TestFlexConstraint(t *testing.T) {
459	t.Parallel()
460
461	testCases := []struct {
462		name        string
463		constraints []Constraint
464		want        [][]int
465		flex        Flex
466	}{
467		{
468			name: "length legacy",
469			constraints: []Constraint{
470				Len(50),
471			},
472			want: [][]int{{0, 100}},
473			flex: FlexLegacy,
474		},
475		{
476			name: "length start",
477			constraints: []Constraint{
478				Len(50),
479			},
480			want: [][]int{{0, 50}},
481			flex: FlexStart,
482		},
483		{
484			name: "length end",
485			constraints: []Constraint{
486				Len(50),
487			},
488			want: [][]int{{50, 100}},
489			flex: FlexEnd,
490		},
491		{
492			name: "length end",
493			constraints: []Constraint{
494				Len(50),
495			},
496			want: [][]int{{25, 75}},
497			flex: FlexCenter,
498		},
499		{
500			name: "ratio legacy",
501			constraints: []Constraint{
502				Ratio{1, 2},
503			},
504			want: [][]int{{0, 100}},
505			flex: FlexLegacy,
506		},
507		{
508			name: "ratio start",
509			constraints: []Constraint{
510				Ratio{1, 2},
511			},
512			want: [][]int{{0, 50}},
513			flex: FlexStart,
514		},
515		{
516			name: "ratio end",
517			constraints: []Constraint{
518				Ratio{1, 2},
519			},
520			want: [][]int{{50, 100}},
521			flex: FlexEnd,
522		},
523		{
524			name: "ratio center",
525			constraints: []Constraint{
526				Ratio{1, 2},
527			},
528			want: [][]int{{25, 75}},
529			flex: FlexCenter,
530		},
531		{
532			name: "percent legacy",
533			constraints: []Constraint{
534				Percent(50),
535			},
536			want: [][]int{{0, 100}},
537			flex: FlexLegacy,
538		},
539		{
540			name: "percent start",
541			constraints: []Constraint{
542				Percent(50),
543			},
544			want: [][]int{{0, 50}},
545			flex: FlexStart,
546		},
547		{
548			name: "percent end",
549			constraints: []Constraint{
550				Percent(50),
551			},
552			want: [][]int{{50, 100}},
553			flex: FlexEnd,
554		},
555		{
556			name: "percent center",
557			constraints: []Constraint{
558				Percent(50),
559			},
560			want: [][]int{{25, 75}},
561			flex: FlexCenter,
562		},
563		{
564			name: "min legacy",
565			constraints: []Constraint{
566				Min(50),
567			},
568			want: [][]int{{0, 100}},
569			flex: FlexLegacy,
570		},
571		{
572			name: "min start",
573			constraints: []Constraint{
574				Min(50),
575			},
576			want: [][]int{{0, 100}},
577			flex: FlexStart,
578		},
579		{
580			name: "min end",
581			constraints: []Constraint{
582				Min(50),
583			},
584			want: [][]int{{0, 100}},
585			flex: FlexEnd,
586		},
587		{
588			name: "min center",
589			constraints: []Constraint{
590				Min(50),
591			},
592			want: [][]int{{0, 100}},
593			flex: FlexCenter,
594		},
595		{
596			name: "min legacy",
597			constraints: []Constraint{
598				Min(50),
599			},
600			want: [][]int{{0, 100}},
601			flex: FlexLegacy,
602		},
603		{
604			name: "max legacy",
605			constraints: []Constraint{
606				Max(50),
607			},
608			want: [][]int{{0, 100}},
609			flex: FlexLegacy,
610		},
611		{
612			name: "max start",
613			constraints: []Constraint{
614				Max(50),
615			},
616			want: [][]int{{0, 50}},
617			flex: FlexStart,
618		},
619		{
620			name: "max end",
621			constraints: []Constraint{
622				Max(50),
623			},
624			want: [][]int{{50, 100}},
625			flex: FlexEnd,
626		},
627		{
628			name: "max center",
629			constraints: []Constraint{
630				Max(50),
631			},
632			want: [][]int{{25, 75}},
633			flex: FlexCenter,
634		},
635		{
636			name: "space between becomes stretch",
637			constraints: []Constraint{
638				Min(1),
639			},
640			want: [][]int{{0, 100}},
641			flex: FlexSpaceBetween,
642		},
643		{
644			name: "space between becomes stretch",
645			constraints: []Constraint{
646				Max(20),
647			},
648			want: [][]int{{0, 100}},
649			flex: FlexSpaceBetween,
650		},
651		{
652			name: "space between becomes stretch",
653			constraints: []Constraint{
654				Len(20),
655			},
656			want: [][]int{{0, 100}},
657			flex: FlexSpaceBetween,
658		},
659		{
660			name: "len legacy 2",
661			constraints: []Constraint{
662				Len(25), Len(25),
663			},
664			want: [][]int{{0, 25}, {25, 100}},
665			flex: FlexLegacy,
666		},
667		{
668			name: "len start 2",
669			constraints: []Constraint{
670				Len(25), Len(25),
671			},
672			want: [][]int{{0, 25}, {25, 50}},
673			flex: FlexStart,
674		},
675		{
676			name: "len center 2",
677			constraints: []Constraint{
678				Len(25), Len(25),
679			},
680			want: [][]int{{25, 50}, {50, 75}},
681			flex: FlexCenter,
682		},
683		{
684			name: "len end 2",
685			constraints: []Constraint{
686				Len(25), Len(25),
687			},
688			want: [][]int{{50, 75}, {75, 100}},
689			flex: FlexEnd,
690		},
691		{
692			name: "len space between",
693			constraints: []Constraint{
694				Len(25), Len(25),
695			},
696			want: [][]int{{0, 25}, {75, 100}},
697			flex: FlexSpaceBetween,
698		},
699		{
700			name: "len space evenly",
701			constraints: []Constraint{
702				Len(25), Len(25),
703			},
704			want: [][]int{{17, 42}, {58, 83}},
705			flex: FlexSpaceEvenly,
706		},
707		{
708			name: "len space around",
709			constraints: []Constraint{
710				Len(25), Len(25),
711			},
712			want: [][]int{{13, 38}, {63, 88}},
713			flex: FlexSpaceAround,
714		},
715		{
716			name: "percentage around",
717			constraints: []Constraint{
718				Percent(25), Percent(25),
719			},
720			want: [][]int{{0, 25}, {25, 100}},
721			flex: FlexLegacy,
722		},
723		{
724			name: "percentage start",
725			constraints: []Constraint{
726				Percent(25), Percent(25),
727			},
728			want: [][]int{{0, 25}, {25, 50}},
729			flex: FlexStart,
730		},
731		{
732			name: "percentage center",
733			constraints: []Constraint{
734				Percent(25), Percent(25),
735			},
736			want: [][]int{{25, 50}, {50, 75}},
737			flex: FlexCenter,
738		},
739		{
740			name: "percentage end",
741			constraints: []Constraint{
742				Percent(25), Percent(25),
743			},
744			want: [][]int{{50, 75}, {75, 100}},
745			flex: FlexEnd,
746		},
747		{
748			name: "percentage space between",
749			constraints: []Constraint{
750				Percent(25), Percent(25),
751			},
752			want: [][]int{{0, 25}, {75, 100}},
753			flex: FlexSpaceBetween,
754		},
755		{
756			name: "percentage space evenly",
757			constraints: []Constraint{
758				Percent(25), Percent(25),
759			},
760			want: [][]int{{17, 42}, {58, 83}},
761			flex: FlexSpaceEvenly,
762		},
763		{
764			name: "percentage space around",
765			constraints: []Constraint{
766				Percent(25), Percent(25),
767			},
768			want: [][]int{{13, 38}, {63, 88}},
769			flex: FlexSpaceAround,
770		},
771		{
772			name: "min legacy 2",
773			constraints: []Constraint{
774				Min(25), Min(25),
775			},
776			want: [][]int{{0, 25}, {25, 100}},
777			flex: FlexLegacy,
778		},
779		{
780			name: "min start 2",
781			constraints: []Constraint{
782				Min(25), Min(25),
783			},
784			want: [][]int{{0, 50}, {50, 100}},
785			flex: FlexStart,
786		},
787		{
788			name: "min center 2",
789			constraints: []Constraint{
790				Min(25), Min(25),
791			},
792			want: [][]int{{0, 50}, {50, 100}},
793			flex: FlexCenter,
794		},
795		{
796			name: "min end 2",
797			constraints: []Constraint{
798				Min(25), Min(25),
799			},
800			want: [][]int{{0, 50}, {50, 100}},
801			flex: FlexEnd,
802		},
803		{
804			name: "min space between",
805			constraints: []Constraint{
806				Min(25), Min(25),
807			},
808			want: [][]int{{0, 50}, {50, 100}},
809			flex: FlexSpaceBetween,
810		},
811		{
812			name: "min space evenly",
813			constraints: []Constraint{
814				Min(25), Min(25),
815			},
816			want: [][]int{{0, 50}, {50, 100}},
817			flex: FlexSpaceEvenly,
818		},
819		{
820			name: "min space around",
821			constraints: []Constraint{
822				Min(25), Min(25),
823			},
824			want: [][]int{{0, 50}, {50, 100}},
825			flex: FlexSpaceAround,
826		},
827		{
828			name: "max legacy 2",
829			constraints: []Constraint{
830				Max(25), Max(25),
831			},
832			want: [][]int{{0, 25}, {25, 100}},
833			flex: FlexLegacy,
834		},
835		{
836			name: "max start 2",
837			constraints: []Constraint{
838				Max(25), Max(25),
839			},
840			want: [][]int{{0, 25}, {25, 50}},
841			flex: FlexStart,
842		},
843		{
844			name: "max center 2",
845			constraints: []Constraint{
846				Max(25), Max(25),
847			},
848			want: [][]int{{25, 50}, {50, 75}},
849			flex: FlexCenter,
850		},
851		{
852			name: "max end 2",
853			constraints: []Constraint{
854				Max(25), Max(25),
855			},
856			want: [][]int{{50, 75}, {75, 100}},
857			flex: FlexEnd,
858		},
859		{
860			name: "max space between",
861			constraints: []Constraint{
862				Max(25), Max(25),
863			},
864			want: [][]int{{0, 25}, {75, 100}},
865			flex: FlexSpaceBetween,
866		},
867		{
868			name: "max space evenly",
869			constraints: []Constraint{
870				Max(25), Max(25),
871			},
872			want: [][]int{{17, 42}, {58, 83}},
873			flex: FlexSpaceEvenly,
874		},
875		{
876			name: "max space around",
877			constraints: []Constraint{
878				Max(25), Max(25),
879			},
880			want: [][]int{{13, 38}, {63, 88}},
881			flex: FlexSpaceAround,
882		},
883		{
884			name: "length spaced around",
885			constraints: []Constraint{
886				Len(25), Len(25), Len(25),
887			},
888			want: [][]int{{0, 25}, {38, 63}, {75, 100}},
889			flex: FlexSpaceBetween,
890		},
891		{
892			name: "one segment legacy",
893			constraints: []Constraint{
894				Len(50),
895			},
896			want: [][]int{{0, 100}},
897			flex: FlexLegacy,
898		},
899		{
900			name: "one segment start",
901			constraints: []Constraint{
902				Len(50),
903			},
904			want: [][]int{{0, 50}},
905			flex: FlexStart,
906		},
907		{
908			name: "one segment end",
909			constraints: []Constraint{
910				Len(50),
911			},
912			want: [][]int{{50, 100}},
913			flex: FlexEnd,
914		},
915		{
916			name: "one segment center",
917			constraints: []Constraint{
918				Len(50),
919			},
920			want: [][]int{{25, 75}},
921			flex: FlexCenter,
922		},
923		{
924			name: "one segment space between",
925			constraints: []Constraint{
926				Len(50),
927			},
928			want: [][]int{{0, 100}},
929			flex: FlexSpaceBetween,
930		},
931		{
932			name: "one segment space evenly",
933			constraints: []Constraint{
934				Len(50),
935			},
936			want: [][]int{{25, 75}},
937			flex: FlexSpaceEvenly,
938		},
939		{
940			name: "one segment space around",
941			constraints: []Constraint{
942				Len(50),
943			},
944			want: [][]int{{25, 75}},
945			flex: FlexSpaceAround,
946		},
947	}
948
949	for _, tc := range testCases {
950		t.Run(tc.name, func(t *testing.T) {
951			t.Parallel()
952
953			rect := uv.Rect(0, 0, 100, 1)
954
955			rects := Horizontal(tc.constraints...).WithFlex(tc.flex).Split(rect)
956
957			ranges := make([][]int, 0, len(rects))
958
959			for _, r := range rects {
960				ranges = append(ranges, []int{r.Min.X, r.Max.X})
961			}
962
963			if !reflect.DeepEqual(tc.want, ranges) {
964				t.Fatalf("not equal: want %#+v, got %#+v", tc.want, ranges)
965			}
966		})
967	}
968}
969
970func TestFlexSpacing(t *testing.T) {
971	t.Parallel()
972
973	testCases := []struct {
974		name        string
975		want        [][]int
976		constraints []Constraint
977		flex        Flex
978		spacing     int
979	}{
980		{
981			name:        "length zero spacing",
982			want:        [][]int{{0, 20}, {20, 20}, {40, 20}},
983			constraints: []Constraint{Len(20), Len(20), Len(20)},
984			flex:        FlexStart,
985			spacing:     0,
986		},
987		{
988			name:        "length overlap 2",
989			want:        [][]int{{0, 20}, {19, 20}, {38, 20}},
990			constraints: []Constraint{Len(20), Len(20), Len(20)},
991			flex:        FlexStart,
992			spacing:     -1,
993		},
994		{
995			name:        "length overlap 3",
996			want:        [][]int{{21, 20}, {40, 20}, {59, 20}},
997			constraints: []Constraint{Len(20), Len(20), Len(20)},
998			flex:        FlexCenter,
999			spacing:     -1,
1000		},
1001		{
1002			name:        "length overlap 4",
1003			want:        [][]int{{42, 20}, {61, 20}, {80, 20}},
1004			constraints: []Constraint{Len(20), Len(20), Len(20)},
1005			flex:        FlexEnd,
1006			spacing:     -1,
1007		},
1008		{
1009			name:        "length overlap 5",
1010			want:        [][]int{{0, 20}, {19, 20}, {38, 62}},
1011			constraints: []Constraint{Len(20), Len(20), Len(20)},
1012			flex:        FlexLegacy,
1013			spacing:     -1,
1014		},
1015		{
1016			name:        "length overlap 6",
1017			want:        [][]int{{0, 20}, {40, 20}, {80, 20}},
1018			constraints: []Constraint{Len(20), Len(20), Len(20)},
1019			flex:        FlexSpaceBetween,
1020			spacing:     -1,
1021		},
1022		{
1023			name:        "length overlap 7",
1024			want:        [][]int{{10, 20}, {40, 20}, {70, 20}},
1025			constraints: []Constraint{Len(20), Len(20), Len(20)},
1026			flex:        FlexSpaceEvenly,
1027			spacing:     -1,
1028		},
1029		{
1030			name:        "length overlap 8",
1031			want:        [][]int{{7, 20}, {40, 20}, {73, 20}},
1032			constraints: []Constraint{Len(20), Len(20), Len(20)},
1033			flex:        FlexSpaceAround,
1034			spacing:     -1,
1035		},
1036
1037		{
1038			name:        "length spacing 1",
1039			want:        [][]int{{0, 20}, {22, 20}, {44, 20}},
1040			constraints: []Constraint{Len(20), Len(20), Len(20)},
1041			flex:        FlexStart,
1042			spacing:     2,
1043		},
1044		{
1045			name:        "length spacing 2",
1046			want:        [][]int{{18, 20}, {40, 20}, {62, 20}},
1047			constraints: []Constraint{Len(20), Len(20), Len(20)},
1048			flex:        FlexCenter,
1049			spacing:     2,
1050		},
1051		{
1052			name:        "length spacing 3",
1053			want:        [][]int{{36, 20}, {58, 20}, {80, 20}},
1054			constraints: []Constraint{Len(20), Len(20), Len(20)},
1055			flex:        FlexEnd,
1056			spacing:     2,
1057		},
1058		{
1059			name:        "length spacing 4",
1060			want:        [][]int{{0, 20}, {22, 20}, {44, 56}},
1061			constraints: []Constraint{Len(20), Len(20), Len(20)},
1062			flex:        FlexLegacy,
1063			spacing:     2,
1064		},
1065		{
1066			name:        "length spacing 5",
1067			want:        [][]int{{0, 20}, {40, 20}, {80, 20}},
1068			constraints: []Constraint{Len(20), Len(20), Len(20)},
1069			flex:        FlexSpaceBetween,
1070			spacing:     2,
1071		},
1072		{
1073			name:        "length spacing 6",
1074			want:        [][]int{{10, 20}, {40, 20}, {70, 20}},
1075			constraints: []Constraint{Len(20), Len(20), Len(20)},
1076			flex:        FlexSpaceEvenly,
1077			spacing:     2,
1078		},
1079		{
1080			name:        "length spacing 7",
1081			want:        [][]int{{7, 20}, {40, 20}, {73, 20}},
1082			constraints: []Constraint{Len(20), Len(20), Len(20)},
1083			flex:        FlexSpaceAround,
1084			spacing:     2,
1085		},
1086	}
1087
1088	for _, tc := range testCases {
1089		t.Run(tc.name, func(t *testing.T) {
1090			t.Parallel()
1091
1092			rect := uv.Rect(0, 0, 100, 1)
1093
1094			splitted := Horizontal(tc.constraints...).
1095				WithFlex(tc.flex).
1096				WithSpacing(tc.spacing).
1097				Split(rect)
1098
1099			got := make([][]int, 0, len(splitted))
1100
1101			for _, r := range splitted {
1102				got = append(got, []int{r.Min.X, r.Dx()})
1103			}
1104
1105			if !reflect.DeepEqual(tc.want, got) {
1106				t.Fatalf("not equal: want %#+v, got %#+v", tc.want, got)
1107			}
1108		})
1109	}
1110}
1111
1112func letters(t *testing.T, flex Flex, constraints []Constraint, width int, expected string) {
1113	t.Helper()
1114
1115	area := uv.Rect(0, 0, width, 1)
1116
1117	layout := Layout{
1118		Direction:   DirectionHorizontal,
1119		Constraints: constraints,
1120		Flex:        flex,
1121	}.Split(area)
1122
1123	got := uv.NewScreenBuffer(area.Dx(), area.Dy())
1124
1125	latin := []rune("abcdefghijklmnopqrstuvwxyz")
1126
1127	for i := 0; i < min(len(constraints), len(layout)); i++ {
1128		c := latin[i]
1129		area := layout[i]
1130
1131		s := strings.Repeat(string(c), area.Dx())
1132
1133		buffer := uv.NewScreenBuffer(area.Dx(), area.Dy())
1134
1135		screen.NewContext(buffer).WriteString(s)
1136
1137		buffer.Draw(got, area)
1138	}
1139
1140	want := newBufferString(expected)
1141
1142	if !reflect.DeepEqual(want, got) {
1143		t.Fatalf("not equal: want %#+v, got %#+v", want, got)
1144	}
1145}
1146
1147func newBufferString(s string) uv.ScreenBuffer {
1148	var width, height int
1149
1150	for line := range strings.Lines(s) {
1151		width = max(width, len(line))
1152		height++
1153	}
1154
1155	buf := uv.NewScreenBuffer(width, height)
1156
1157	screen.NewContext(buf).WriteString(s)
1158
1159	return buf
1160}