package layout import ( "fmt" "reflect" "strings" "testing" uv "github.com/charmbracelet/ultraviolet" "github.com/charmbracelet/ultraviolet/screen" ) func BenchmarkLayout_Split(b *testing.B) { layout := Vertical(Len(2), Fill(1), Ratio{1, 5}, Percent(10)) area := uv.Rect(123, 123, 1000, 2000) b.Run("without cache", func(b *testing.B) { for b.Loop() { _, _, err := layout.split(area) if err != nil { b.Fatal(err) } } }) b.Run("with cache", func(b *testing.B) { for b.Loop() { _, _, err := layout.splitCached(area) if err != nil { b.Fatal(err) } } }) } func TestPriorityIsValid(t *testing.T) { t.Parallel() assert := func(ok bool) { t.Helper() if !ok { t.Error("invalid priority") } } // Ensures that the constants are defined in the correct order of priority. assert(spacerSizeEq > maxSizeLTE) assert(maxSizeLTE > maxSizeEq) assert(minSizeGTE == maxSizeLTE) assert(maxSizeLTE > lengthSizeEq) assert(lengthSizeEq > percentSizeEq) assert(percentSizeEq > ratioSizeEq) assert(ratioSizeEq > maxSizeEq) assert(minSizeGTE > fillGrow) assert(fillGrow > grow) assert(grow > spaceGrow) assert(spaceGrow > allSegmentGrow) } type LayoutSplitTestCase struct { Flex Flex Width int Constraints []Constraint Want string } func (tc LayoutSplitTestCase) Name() string { return fmt.Sprintf("Flex(%s) Width(%d) Constraints(%s)", tc.Flex, tc.Width, tc.Constraints) } func (tc LayoutSplitTestCase) Test(t *testing.T) { t.Helper() t.Parallel() letters(t, tc.Flex, tc.Constraints, tc.Width, tc.Want) } func TestLength(t *testing.T) { t.Parallel() testCases := []LayoutSplitTestCase{ {FlexLegacy, 1, []Constraint{Len(0)}, "a"}, {FlexLegacy, 1, []Constraint{Len(1)}, "a"}, {FlexLegacy, 1, []Constraint{Len(2)}, "a"}, {FlexLegacy, 2, []Constraint{Len(0)}, "aa"}, {FlexLegacy, 2, []Constraint{Len(1)}, "aa"}, {FlexLegacy, 2, []Constraint{Len(2)}, "aa"}, {FlexLegacy, 2, []Constraint{Len(3)}, "aa"}, {FlexLegacy, 1, []Constraint{Len(0), Len(0)}, "b"}, {FlexLegacy, 1, []Constraint{Len(0), Len(1)}, "b"}, {FlexLegacy, 1, []Constraint{Len(0), Len(2)}, "b"}, {FlexLegacy, 1, []Constraint{Len(1), Len(0)}, "a"}, {FlexLegacy, 1, []Constraint{Len(1), Len(1)}, "a"}, {FlexLegacy, 1, []Constraint{Len(1), Len(2)}, "a"}, {FlexLegacy, 1, []Constraint{Len(2), Len(0)}, "a"}, {FlexLegacy, 1, []Constraint{Len(2), Len(1)}, "a"}, {FlexLegacy, 1, []Constraint{Len(2), Len(2)}, "a"}, {FlexLegacy, 2, []Constraint{Len(0), Len(0)}, "bb"}, {FlexLegacy, 2, []Constraint{Len(0), Len(1)}, "bb"}, {FlexLegacy, 2, []Constraint{Len(0), Len(2)}, "bb"}, {FlexLegacy, 2, []Constraint{Len(0), Len(3)}, "bb"}, {FlexLegacy, 2, []Constraint{Len(1), Len(0)}, "ab"}, {FlexLegacy, 2, []Constraint{Len(1), Len(1)}, "ab"}, {FlexLegacy, 2, []Constraint{Len(1), Len(2)}, "ab"}, {FlexLegacy, 2, []Constraint{Len(1), Len(3)}, "ab"}, {FlexLegacy, 2, []Constraint{Len(2), Len(0)}, "aa"}, {FlexLegacy, 2, []Constraint{Len(2), Len(1)}, "aa"}, {FlexLegacy, 2, []Constraint{Len(2), Len(2)}, "aa"}, {FlexLegacy, 2, []Constraint{Len(2), Len(3)}, "aa"}, {FlexLegacy, 2, []Constraint{Len(3), Len(0)}, "aa"}, {FlexLegacy, 2, []Constraint{Len(3), Len(1)}, "aa"}, {FlexLegacy, 2, []Constraint{Len(3), Len(2)}, "aa"}, {FlexLegacy, 2, []Constraint{Len(3), Len(3)}, "aa"}, {FlexLegacy, 3, []Constraint{Len(2), Len(2)}, "aab"}, } for _, tc := range testCases { t.Run(tc.Name(), tc.Test) } } func TestMax(t *testing.T) { t.Parallel() testCases := []LayoutSplitTestCase{ {FlexLegacy, 1, []Constraint{Max(0)}, "a"}, {FlexLegacy, 1, []Constraint{Max(1)}, "a"}, {FlexLegacy, 1, []Constraint{Max(2)}, "a"}, {FlexLegacy, 2, []Constraint{Max(0)}, "aa"}, {FlexLegacy, 2, []Constraint{Max(1)}, "aa"}, {FlexLegacy, 2, []Constraint{Max(2)}, "aa"}, {FlexLegacy, 2, []Constraint{Max(3)}, "aa"}, {FlexLegacy, 1, []Constraint{Max(0), Max(0)}, "b"}, {FlexLegacy, 1, []Constraint{Max(0), Max(1)}, "b"}, {FlexLegacy, 1, []Constraint{Max(0), Max(2)}, "b"}, {FlexLegacy, 1, []Constraint{Max(1), Max(0)}, "a"}, {FlexLegacy, 1, []Constraint{Max(1), Max(1)}, "a"}, {FlexLegacy, 1, []Constraint{Max(1), Max(2)}, "a"}, {FlexLegacy, 1, []Constraint{Max(2), Max(0)}, "a"}, {FlexLegacy, 1, []Constraint{Max(2), Max(1)}, "a"}, {FlexLegacy, 1, []Constraint{Max(2), Max(2)}, "a"}, {FlexLegacy, 2, []Constraint{Max(0), Max(0)}, "bb"}, {FlexLegacy, 2, []Constraint{Max(0), Max(1)}, "bb"}, {FlexLegacy, 2, []Constraint{Max(0), Max(2)}, "bb"}, {FlexLegacy, 2, []Constraint{Max(0), Max(3)}, "bb"}, {FlexLegacy, 2, []Constraint{Max(1), Max(0)}, "ab"}, {FlexLegacy, 2, []Constraint{Max(1), Max(1)}, "ab"}, {FlexLegacy, 2, []Constraint{Max(1), Max(2)}, "ab"}, {FlexLegacy, 2, []Constraint{Max(1), Max(3)}, "ab"}, {FlexLegacy, 2, []Constraint{Max(2), Max(0)}, "aa"}, {FlexLegacy, 2, []Constraint{Max(2), Max(1)}, "aa"}, {FlexLegacy, 2, []Constraint{Max(2), Max(2)}, "aa"}, {FlexLegacy, 2, []Constraint{Max(2), Max(3)}, "aa"}, {FlexLegacy, 2, []Constraint{Max(3), Max(0)}, "aa"}, {FlexLegacy, 2, []Constraint{Max(3), Max(1)}, "aa"}, {FlexLegacy, 2, []Constraint{Max(3), Max(2)}, "aa"}, {FlexLegacy, 2, []Constraint{Max(3), Max(3)}, "aa"}, {FlexLegacy, 3, []Constraint{Max(2), Max(2)}, "aab"}, } for _, tc := range testCases { t.Run(tc.Name(), tc.Test) } } func TestMin(t *testing.T) { t.Parallel() testCases := []LayoutSplitTestCase{ {FlexLegacy, 1, []Constraint{Min(0), Min(0)}, "b"}, {FlexLegacy, 1, []Constraint{Min(0), Min(1)}, "b"}, {FlexLegacy, 1, []Constraint{Min(0), Min(2)}, "b"}, {FlexLegacy, 1, []Constraint{Min(1), Min(0)}, "a"}, {FlexLegacy, 1, []Constraint{Min(1), Min(1)}, "a"}, {FlexLegacy, 1, []Constraint{Min(1), Min(2)}, "a"}, {FlexLegacy, 1, []Constraint{Min(2), Min(0)}, "a"}, {FlexLegacy, 1, []Constraint{Min(2), Min(1)}, "a"}, {FlexLegacy, 1, []Constraint{Min(2), Min(2)}, "a"}, {FlexLegacy, 2, []Constraint{Min(0), Min(0)}, "bb"}, {FlexLegacy, 2, []Constraint{Min(0), Min(1)}, "bb"}, {FlexLegacy, 2, []Constraint{Min(0), Min(2)}, "bb"}, {FlexLegacy, 2, []Constraint{Min(0), Min(3)}, "bb"}, {FlexLegacy, 2, []Constraint{Min(1), Min(0)}, "ab"}, {FlexLegacy, 2, []Constraint{Min(1), Min(1)}, "ab"}, {FlexLegacy, 2, []Constraint{Min(1), Min(2)}, "ab"}, {FlexLegacy, 2, []Constraint{Min(1), Min(3)}, "ab"}, {FlexLegacy, 2, []Constraint{Min(2), Min(0)}, "aa"}, {FlexLegacy, 2, []Constraint{Min(2), Min(1)}, "aa"}, {FlexLegacy, 2, []Constraint{Min(2), Min(2)}, "aa"}, {FlexLegacy, 2, []Constraint{Min(2), Min(3)}, "aa"}, {FlexLegacy, 2, []Constraint{Min(3), Min(0)}, "aa"}, {FlexLegacy, 2, []Constraint{Min(3), Min(1)}, "aa"}, {FlexLegacy, 2, []Constraint{Min(3), Min(2)}, "aa"}, {FlexLegacy, 2, []Constraint{Min(3), Min(3)}, "aa"}, {FlexLegacy, 3, []Constraint{Min(2), Min(2)}, "aab"}, } for _, tc := range testCases { t.Run(tc.Name(), tc.Test) } } func TestPercent(t *testing.T) { t.Parallel() testCases := []LayoutSplitTestCase{ {FlexStart, 10, []Constraint{Percent(0), Percent(0)}, " "}, {FlexStart, 10, []Constraint{Percent(0), Percent(25)}, "bbb "}, {FlexStart, 10, []Constraint{Percent(0), Percent(50)}, "bbbbb "}, {FlexStart, 10, []Constraint{Percent(0), Percent(100)}, "bbbbbbbbbb"}, {FlexStart, 10, []Constraint{Percent(0), Percent(200)}, "bbbbbbbbbb"}, {FlexStart, 10, []Constraint{Percent(10), Percent(0)}, "a "}, {FlexStart, 10, []Constraint{Percent(10), Percent(25)}, "abbb "}, {FlexStart, 10, []Constraint{Percent(10), Percent(50)}, "abbbbb "}, {FlexStart, 10, []Constraint{Percent(10), Percent(100)}, "abbbbbbbbb"}, {FlexStart, 10, []Constraint{Percent(10), Percent(200)}, "abbbbbbbbb"}, {FlexStart, 10, []Constraint{Percent(25), Percent(0)}, "aaa "}, {FlexStart, 10, []Constraint{Percent(25), Percent(25)}, "aaabb "}, {FlexStart, 10, []Constraint{Percent(25), Percent(50)}, "aaabbbbb "}, {FlexStart, 10, []Constraint{Percent(25), Percent(100)}, "aaabbbbbbb"}, {FlexStart, 10, []Constraint{Percent(25), Percent(200)}, "aaabbbbbbb"}, {FlexStart, 10, []Constraint{Percent(33), Percent(0)}, "aaa "}, {FlexStart, 10, []Constraint{Percent(33), Percent(25)}, "aaabbb "}, {FlexStart, 10, []Constraint{Percent(33), Percent(50)}, "aaabbbbb "}, {FlexStart, 10, []Constraint{Percent(33), Percent(100)}, "aaabbbbbbb"}, {FlexStart, 10, []Constraint{Percent(33), Percent(200)}, "aaabbbbbbb"}, {FlexStart, 10, []Constraint{Percent(50), Percent(0)}, "aaaaa "}, {FlexStart, 10, []Constraint{Percent(50), Percent(50)}, "aaaaabbbbb"}, {FlexStart, 10, []Constraint{Percent(50), Percent(100)}, "aaaaabbbbb"}, {FlexStart, 10, []Constraint{Percent(100), Percent(0)}, "aaaaaaaaaa"}, {FlexStart, 10, []Constraint{Percent(100), Percent(50)}, "aaaaabbbbb"}, {FlexStart, 10, []Constraint{Percent(100), Percent(100)}, "aaaaabbbbb"}, {FlexStart, 10, []Constraint{Percent(100), Percent(200)}, "aaaaabbbbb"}, {FlexSpaceBetween, 10, []Constraint{Percent(0), Percent(0)}, " "}, {FlexSpaceBetween, 10, []Constraint{Percent(0), Percent(25)}, " bb"}, {FlexSpaceBetween, 10, []Constraint{Percent(0), Percent(50)}, " bbbbb"}, {FlexSpaceBetween, 10, []Constraint{Percent(0), Percent(100)}, "bbbbbbbbbb"}, {FlexSpaceBetween, 10, []Constraint{Percent(0), Percent(200)}, "bbbbbbbbbb"}, {FlexSpaceBetween, 10, []Constraint{Percent(10), Percent(0)}, "a "}, {FlexSpaceBetween, 10, []Constraint{Percent(10), Percent(25)}, "a bb"}, {FlexSpaceBetween, 10, []Constraint{Percent(10), Percent(50)}, "a bbbbb"}, {FlexSpaceBetween, 10, []Constraint{Percent(10), Percent(100)}, "abbbbbbbbb"}, {FlexSpaceBetween, 10, []Constraint{Percent(10), Percent(200)}, "abbbbbbbbb"}, {FlexSpaceBetween, 10, []Constraint{Percent(25), Percent(0)}, "aaa "}, {FlexSpaceBetween, 10, []Constraint{Percent(25), Percent(25)}, "aaa bb"}, {FlexSpaceBetween, 10, []Constraint{Percent(25), Percent(50)}, "aaa bbbbb"}, {FlexSpaceBetween, 10, []Constraint{Percent(25), Percent(100)}, "aaabbbbbbb"}, {FlexLegacy, 10, []Constraint{Percent(25), Percent(200)}, "aaabbbbbbb"}, {FlexSpaceBetween, 10, []Constraint{Percent(33), Percent(0)}, "aaa "}, {FlexSpaceBetween, 10, []Constraint{Percent(33), Percent(25)}, "aaa bb"}, {FlexSpaceBetween, 10, []Constraint{Percent(33), Percent(50)}, "aaa bbbbb"}, {FlexSpaceBetween, 10, []Constraint{Percent(33), Percent(100)}, "aaabbbbbbb"}, {FlexSpaceBetween, 10, []Constraint{Percent(33), Percent(200)}, "aaabbbbbbb"}, {FlexSpaceBetween, 10, []Constraint{Percent(50), Percent(0)}, "aaaaa "}, {FlexSpaceBetween, 10, []Constraint{Percent(50), Percent(50)}, "aaaaabbbbb"}, {FlexSpaceBetween, 10, []Constraint{Percent(50), Percent(100)}, "aaaaabbbbb"}, {FlexSpaceBetween, 10, []Constraint{Percent(100), Percent(0)}, "aaaaaaaaaa"}, {FlexSpaceBetween, 10, []Constraint{Percent(100), Percent(50)}, "aaaaabbbbb"}, {FlexSpaceBetween, 10, []Constraint{Percent(100), Percent(100)}, "aaaaabbbbb"}, {FlexSpaceBetween, 10, []Constraint{Percent(100), Percent(200)}, "aaaaabbbbb"}, } for _, tc := range testCases { t.Run(tc.Name(), tc.Test) } } func TestRatio(t *testing.T) { t.Parallel() testCases := []LayoutSplitTestCase{ {FlexLegacy, 1, []Constraint{Ratio{0, 1}}, "a"}, {FlexLegacy, 1, []Constraint{Ratio{0, 1}}, "a"}, {FlexLegacy, 2, []Constraint{Ratio{0, 1}}, "aa"}, {FlexLegacy, 10, []Constraint{Ratio{0, 1}, Ratio{0, 1}}, "bbbbbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{0, 1}, Ratio{1, 4}}, "bbbbbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{0, 1}, Ratio{1, 2}}, "bbbbbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{0, 1}, Ratio{1, 1}}, "bbbbbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{0, 1}, Ratio{2, 1}}, "bbbbbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 10}, Ratio{0, 1}}, "abbbbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 10}, Ratio{1, 4}}, "abbbbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 10}, Ratio{1, 2}}, "abbbbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 10}, Ratio{1, 1}}, "abbbbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 10}, Ratio{2, 1}}, "abbbbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 4}, Ratio{0, 1}}, "aaabbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 4}, Ratio{1, 4}}, "aaabbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 4}, Ratio{1, 2}}, "aaabbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 4}, Ratio{1, 1}}, "aaabbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 4}, Ratio{2, 1}}, "aaabbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 3}, Ratio{0, 1}}, "aaabbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 3}, Ratio{1, 4}}, "aaabbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 3}, Ratio{1, 2}}, "aaabbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 3}, Ratio{1, 1}}, "aaabbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 3}, Ratio{2, 1}}, "aaabbbbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 2}, Ratio{0, 1}}, "aaaaabbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 2}, Ratio{1, 2}}, "aaaaabbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 2}, Ratio{1, 1}}, "aaaaabbbbb"}, {FlexLegacy, 10, []Constraint{Ratio{1, 1}, Ratio{0, 1}}, "aaaaaaaaaa"}, {FlexLegacy, 10, []Constraint{Ratio{1, 1}, Ratio{1, 2}}, "aaaaaaaaaa"}, {FlexLegacy, 10, []Constraint{Ratio{1, 1}, Ratio{1, 1}}, "aaaaaaaaaa"}, {FlexStart, 10, []Constraint{Ratio{0, 1}, Ratio{0, 1}}, " "}, {FlexStart, 10, []Constraint{Ratio{0, 1}, Ratio{1, 4}}, "bbb "}, {FlexStart, 10, []Constraint{Ratio{0, 1}, Ratio{1, 2}}, "bbbbb "}, {FlexStart, 10, []Constraint{Ratio{0, 1}, Ratio{1, 1}}, "bbbbbbbbbb"}, {FlexStart, 10, []Constraint{Ratio{0, 1}, Ratio{2, 1}}, "bbbbbbbbbb"}, {FlexStart, 10, []Constraint{Ratio{1, 10}, Ratio{0, 1}}, "a "}, {FlexStart, 10, []Constraint{Ratio{1, 10}, Ratio{1, 4}}, "abbb "}, {FlexStart, 10, []Constraint{Ratio{1, 10}, Ratio{1, 2}}, "abbbbb "}, {FlexStart, 10, []Constraint{Ratio{1, 10}, Ratio{1, 1}}, "abbbbbbbbb"}, {FlexStart, 10, []Constraint{Ratio{1, 10}, Ratio{2, 1}}, "abbbbbbbbb"}, {FlexStart, 10, []Constraint{Ratio{1, 4}, Ratio{0, 1}}, "aaa "}, {FlexStart, 10, []Constraint{Ratio{1, 4}, Ratio{1, 4}}, "aaabb "}, {FlexStart, 10, []Constraint{Ratio{1, 4}, Ratio{1, 2}}, "aaabbbbb "}, {FlexStart, 10, []Constraint{Ratio{1, 4}, Ratio{1, 1}}, "aaabbbbbbb"}, {FlexStart, 10, []Constraint{Ratio{1, 4}, Ratio{2, 1}}, "aaabbbbbbb"}, {FlexStart, 10, []Constraint{Ratio{1, 3}, Ratio{0, 1}}, "aaa "}, {FlexStart, 10, []Constraint{Ratio{1, 3}, Ratio{1, 4}}, "aaabbb "}, {FlexStart, 10, []Constraint{Ratio{1, 3}, Ratio{1, 2}}, "aaabbbbb "}, {FlexStart, 10, []Constraint{Ratio{1, 3}, Ratio{1, 1}}, "aaabbbbbbb"}, {FlexStart, 10, []Constraint{Ratio{1, 3}, Ratio{2, 1}}, "aaabbbbbbb"}, {FlexStart, 10, []Constraint{Ratio{1, 2}, Ratio{0, 1}}, "aaaaa "}, {FlexStart, 10, []Constraint{Ratio{1, 2}, Ratio{1, 2}}, "aaaaabbbbb"}, {FlexStart, 10, []Constraint{Ratio{1, 2}, Ratio{1, 1}}, "aaaaabbbbb"}, {FlexStart, 10, []Constraint{Ratio{1, 1}, Ratio{0, 1}}, "aaaaaaaaaa"}, {FlexStart, 10, []Constraint{Ratio{1, 1}, Ratio{1, 2}}, "aaaaabbbbb"}, {FlexStart, 10, []Constraint{Ratio{1, 1}, Ratio{1, 1}}, "aaaaabbbbb"}, {FlexStart, 10, []Constraint{Ratio{1, 1}, Ratio{2, 1}}, "aaaaabbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{0, 1}, Ratio{0, 1}}, " "}, {FlexSpaceBetween, 10, []Constraint{Ratio{0, 1}, Ratio{1, 4}}, " bb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{0, 1}, Ratio{1, 2}}, " bbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{0, 1}, Ratio{1, 1}}, "bbbbbbbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{0, 1}, Ratio{2, 1}}, "bbbbbbbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 10}, Ratio{0, 1}}, "a "}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 10}, Ratio{1, 4}}, "a bb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 10}, Ratio{1, 2}}, "a bbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 10}, Ratio{1, 1}}, "abbbbbbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 10}, Ratio{2, 1}}, "abbbbbbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 4}, Ratio{0, 1}}, "aaa "}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 4}, Ratio{1, 4}}, "aaa bb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 4}, Ratio{1, 2}}, "aaa bbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 4}, Ratio{1, 1}}, "aaabbbbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 4}, Ratio{2, 1}}, "aaabbbbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 3}, Ratio{0, 1}}, "aaa "}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 3}, Ratio{1, 4}}, "aaa bb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 3}, Ratio{1, 2}}, "aaa bbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 3}, Ratio{1, 1}}, "aaabbbbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 3}, Ratio{2, 1}}, "aaabbbbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 2}, Ratio{0, 1}}, "aaaaa "}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 2}, Ratio{1, 2}}, "aaaaabbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 2}, Ratio{1, 1}}, "aaaaabbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 1}, Ratio{0, 1}}, "aaaaaaaaaa"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 1}, Ratio{1, 2}}, "aaaaabbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 1}, Ratio{1, 1}}, "aaaaabbbbb"}, {FlexSpaceBetween, 10, []Constraint{Ratio{1, 1}, Ratio{2, 1}}, "aaaaabbbbb"}, } for _, tc := range testCases { t.Run(tc.Name(), tc.Test) } } type Rect = uv.Rectangle func TestEdgeCases(t *testing.T) { t.Parallel() testCases := []struct { name string constraints []Constraint direction Direction split Rect want Splitted }{ { name: "50% 50% min(0) stretches into last", constraints: []Constraint{ Percent(50), Percent(50), Min(0), }, direction: DirectionVertical, split: uv.Rect(0, 0, 1, 1), want: []Rect{ uv.Rect(0, 0, 1, 1), uv.Rect(0, 1, 1, 0), uv.Rect(0, 1, 1, 0), }, }, { name: "max(1) 99% min(0) stretches into last", constraints: []Constraint{ Max(1), Percent(99), Min(0), }, direction: DirectionVertical, split: uv.Rect(0, 0, 1, 1), want: []Rect{ uv.Rect(0, 0, 1, 0), uv.Rect(0, 0, 1, 1), uv.Rect(0, 1, 1, 0), }, }, { name: "min(1) length(0) min(1)", constraints: []Constraint{ Min(1), Len(0), Min(1), }, direction: DirectionHorizontal, split: uv.Rect(0, 0, 1, 1), want: []Rect{ uv.Rect(0, 0, 1, 1), uv.Rect(1, 0, 0, 1), uv.Rect(1, 0, 0, 1), }, }, { name: "stretches the 2nd last length instead of the last min based on ranking", constraints: []Constraint{ Len(3), Min(4), Len(1), Min(4), }, direction: DirectionHorizontal, split: uv.Rect(0, 0, 7, 1), want: []Rect{ uv.Rect(0, 0, 0, 1), uv.Rect(0, 0, 4, 1), uv.Rect(4, 0, 0, 1), uv.Rect(4, 0, 3, 1), }, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Parallel() layout := Layout{ Constraints: tc.constraints, Direction: tc.direction, }.Split(tc.split) if !reflect.DeepEqual(tc.want, layout) { t.Fatalf("not equal: want %#+v, got %#+v", tc.want, layout) } }) } } func TestFlexConstraint(t *testing.T) { t.Parallel() testCases := []struct { name string constraints []Constraint want [][]int flex Flex }{ { name: "length legacy", constraints: []Constraint{ Len(50), }, want: [][]int{{0, 100}}, flex: FlexLegacy, }, { name: "length start", constraints: []Constraint{ Len(50), }, want: [][]int{{0, 50}}, flex: FlexStart, }, { name: "length end", constraints: []Constraint{ Len(50), }, want: [][]int{{50, 100}}, flex: FlexEnd, }, { name: "length end", constraints: []Constraint{ Len(50), }, want: [][]int{{25, 75}}, flex: FlexCenter, }, { name: "ratio legacy", constraints: []Constraint{ Ratio{1, 2}, }, want: [][]int{{0, 100}}, flex: FlexLegacy, }, { name: "ratio start", constraints: []Constraint{ Ratio{1, 2}, }, want: [][]int{{0, 50}}, flex: FlexStart, }, { name: "ratio end", constraints: []Constraint{ Ratio{1, 2}, }, want: [][]int{{50, 100}}, flex: FlexEnd, }, { name: "ratio center", constraints: []Constraint{ Ratio{1, 2}, }, want: [][]int{{25, 75}}, flex: FlexCenter, }, { name: "percent legacy", constraints: []Constraint{ Percent(50), }, want: [][]int{{0, 100}}, flex: FlexLegacy, }, { name: "percent start", constraints: []Constraint{ Percent(50), }, want: [][]int{{0, 50}}, flex: FlexStart, }, { name: "percent end", constraints: []Constraint{ Percent(50), }, want: [][]int{{50, 100}}, flex: FlexEnd, }, { name: "percent center", constraints: []Constraint{ Percent(50), }, want: [][]int{{25, 75}}, flex: FlexCenter, }, { name: "min legacy", constraints: []Constraint{ Min(50), }, want: [][]int{{0, 100}}, flex: FlexLegacy, }, { name: "min start", constraints: []Constraint{ Min(50), }, want: [][]int{{0, 100}}, flex: FlexStart, }, { name: "min end", constraints: []Constraint{ Min(50), }, want: [][]int{{0, 100}}, flex: FlexEnd, }, { name: "min center", constraints: []Constraint{ Min(50), }, want: [][]int{{0, 100}}, flex: FlexCenter, }, { name: "min legacy", constraints: []Constraint{ Min(50), }, want: [][]int{{0, 100}}, flex: FlexLegacy, }, { name: "max legacy", constraints: []Constraint{ Max(50), }, want: [][]int{{0, 100}}, flex: FlexLegacy, }, { name: "max start", constraints: []Constraint{ Max(50), }, want: [][]int{{0, 50}}, flex: FlexStart, }, { name: "max end", constraints: []Constraint{ Max(50), }, want: [][]int{{50, 100}}, flex: FlexEnd, }, { name: "max center", constraints: []Constraint{ Max(50), }, want: [][]int{{25, 75}}, flex: FlexCenter, }, { name: "space between becomes stretch", constraints: []Constraint{ Min(1), }, want: [][]int{{0, 100}}, flex: FlexSpaceBetween, }, { name: "space between becomes stretch", constraints: []Constraint{ Max(20), }, want: [][]int{{0, 100}}, flex: FlexSpaceBetween, }, { name: "space between becomes stretch", constraints: []Constraint{ Len(20), }, want: [][]int{{0, 100}}, flex: FlexSpaceBetween, }, { name: "len legacy 2", constraints: []Constraint{ Len(25), Len(25), }, want: [][]int{{0, 25}, {25, 100}}, flex: FlexLegacy, }, { name: "len start 2", constraints: []Constraint{ Len(25), Len(25), }, want: [][]int{{0, 25}, {25, 50}}, flex: FlexStart, }, { name: "len center 2", constraints: []Constraint{ Len(25), Len(25), }, want: [][]int{{25, 50}, {50, 75}}, flex: FlexCenter, }, { name: "len end 2", constraints: []Constraint{ Len(25), Len(25), }, want: [][]int{{50, 75}, {75, 100}}, flex: FlexEnd, }, { name: "len space between", constraints: []Constraint{ Len(25), Len(25), }, want: [][]int{{0, 25}, {75, 100}}, flex: FlexSpaceBetween, }, { name: "len space evenly", constraints: []Constraint{ Len(25), Len(25), }, want: [][]int{{17, 42}, {58, 83}}, flex: FlexSpaceEvenly, }, { name: "len space around", constraints: []Constraint{ Len(25), Len(25), }, want: [][]int{{13, 38}, {63, 88}}, flex: FlexSpaceAround, }, { name: "percentage around", constraints: []Constraint{ Percent(25), Percent(25), }, want: [][]int{{0, 25}, {25, 100}}, flex: FlexLegacy, }, { name: "percentage start", constraints: []Constraint{ Percent(25), Percent(25), }, want: [][]int{{0, 25}, {25, 50}}, flex: FlexStart, }, { name: "percentage center", constraints: []Constraint{ Percent(25), Percent(25), }, want: [][]int{{25, 50}, {50, 75}}, flex: FlexCenter, }, { name: "percentage end", constraints: []Constraint{ Percent(25), Percent(25), }, want: [][]int{{50, 75}, {75, 100}}, flex: FlexEnd, }, { name: "percentage space between", constraints: []Constraint{ Percent(25), Percent(25), }, want: [][]int{{0, 25}, {75, 100}}, flex: FlexSpaceBetween, }, { name: "percentage space evenly", constraints: []Constraint{ Percent(25), Percent(25), }, want: [][]int{{17, 42}, {58, 83}}, flex: FlexSpaceEvenly, }, { name: "percentage space around", constraints: []Constraint{ Percent(25), Percent(25), }, want: [][]int{{13, 38}, {63, 88}}, flex: FlexSpaceAround, }, { name: "min legacy 2", constraints: []Constraint{ Min(25), Min(25), }, want: [][]int{{0, 25}, {25, 100}}, flex: FlexLegacy, }, { name: "min start 2", constraints: []Constraint{ Min(25), Min(25), }, want: [][]int{{0, 50}, {50, 100}}, flex: FlexStart, }, { name: "min center 2", constraints: []Constraint{ Min(25), Min(25), }, want: [][]int{{0, 50}, {50, 100}}, flex: FlexCenter, }, { name: "min end 2", constraints: []Constraint{ Min(25), Min(25), }, want: [][]int{{0, 50}, {50, 100}}, flex: FlexEnd, }, { name: "min space between", constraints: []Constraint{ Min(25), Min(25), }, want: [][]int{{0, 50}, {50, 100}}, flex: FlexSpaceBetween, }, { name: "min space evenly", constraints: []Constraint{ Min(25), Min(25), }, want: [][]int{{0, 50}, {50, 100}}, flex: FlexSpaceEvenly, }, { name: "min space around", constraints: []Constraint{ Min(25), Min(25), }, want: [][]int{{0, 50}, {50, 100}}, flex: FlexSpaceAround, }, { name: "max legacy 2", constraints: []Constraint{ Max(25), Max(25), }, want: [][]int{{0, 25}, {25, 100}}, flex: FlexLegacy, }, { name: "max start 2", constraints: []Constraint{ Max(25), Max(25), }, want: [][]int{{0, 25}, {25, 50}}, flex: FlexStart, }, { name: "max center 2", constraints: []Constraint{ Max(25), Max(25), }, want: [][]int{{25, 50}, {50, 75}}, flex: FlexCenter, }, { name: "max end 2", constraints: []Constraint{ Max(25), Max(25), }, want: [][]int{{50, 75}, {75, 100}}, flex: FlexEnd, }, { name: "max space between", constraints: []Constraint{ Max(25), Max(25), }, want: [][]int{{0, 25}, {75, 100}}, flex: FlexSpaceBetween, }, { name: "max space evenly", constraints: []Constraint{ Max(25), Max(25), }, want: [][]int{{17, 42}, {58, 83}}, flex: FlexSpaceEvenly, }, { name: "max space around", constraints: []Constraint{ Max(25), Max(25), }, want: [][]int{{13, 38}, {63, 88}}, flex: FlexSpaceAround, }, { name: "length spaced around", constraints: []Constraint{ Len(25), Len(25), Len(25), }, want: [][]int{{0, 25}, {38, 63}, {75, 100}}, flex: FlexSpaceBetween, }, { name: "one segment legacy", constraints: []Constraint{ Len(50), }, want: [][]int{{0, 100}}, flex: FlexLegacy, }, { name: "one segment start", constraints: []Constraint{ Len(50), }, want: [][]int{{0, 50}}, flex: FlexStart, }, { name: "one segment end", constraints: []Constraint{ Len(50), }, want: [][]int{{50, 100}}, flex: FlexEnd, }, { name: "one segment center", constraints: []Constraint{ Len(50), }, want: [][]int{{25, 75}}, flex: FlexCenter, }, { name: "one segment space between", constraints: []Constraint{ Len(50), }, want: [][]int{{0, 100}}, flex: FlexSpaceBetween, }, { name: "one segment space evenly", constraints: []Constraint{ Len(50), }, want: [][]int{{25, 75}}, flex: FlexSpaceEvenly, }, { name: "one segment space around", constraints: []Constraint{ Len(50), }, want: [][]int{{25, 75}}, flex: FlexSpaceAround, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Parallel() rect := uv.Rect(0, 0, 100, 1) rects := Horizontal(tc.constraints...).WithFlex(tc.flex).Split(rect) ranges := make([][]int, 0, len(rects)) for _, r := range rects { ranges = append(ranges, []int{r.Min.X, r.Max.X}) } if !reflect.DeepEqual(tc.want, ranges) { t.Fatalf("not equal: want %#+v, got %#+v", tc.want, ranges) } }) } } func TestFlexSpacing(t *testing.T) { t.Parallel() testCases := []struct { name string want [][]int constraints []Constraint flex Flex spacing int }{ { name: "length zero spacing", want: [][]int{{0, 20}, {20, 20}, {40, 20}}, constraints: []Constraint{Len(20), Len(20), Len(20)}, flex: FlexStart, spacing: 0, }, { name: "length overlap 2", want: [][]int{{0, 20}, {19, 20}, {38, 20}}, constraints: []Constraint{Len(20), Len(20), Len(20)}, flex: FlexStart, spacing: -1, }, { name: "length overlap 3", want: [][]int{{21, 20}, {40, 20}, {59, 20}}, constraints: []Constraint{Len(20), Len(20), Len(20)}, flex: FlexCenter, spacing: -1, }, { name: "length overlap 4", want: [][]int{{42, 20}, {61, 20}, {80, 20}}, constraints: []Constraint{Len(20), Len(20), Len(20)}, flex: FlexEnd, spacing: -1, }, { name: "length overlap 5", want: [][]int{{0, 20}, {19, 20}, {38, 62}}, constraints: []Constraint{Len(20), Len(20), Len(20)}, flex: FlexLegacy, spacing: -1, }, { name: "length overlap 6", want: [][]int{{0, 20}, {40, 20}, {80, 20}}, constraints: []Constraint{Len(20), Len(20), Len(20)}, flex: FlexSpaceBetween, spacing: -1, }, { name: "length overlap 7", want: [][]int{{10, 20}, {40, 20}, {70, 20}}, constraints: []Constraint{Len(20), Len(20), Len(20)}, flex: FlexSpaceEvenly, spacing: -1, }, { name: "length overlap 8", want: [][]int{{7, 20}, {40, 20}, {73, 20}}, constraints: []Constraint{Len(20), Len(20), Len(20)}, flex: FlexSpaceAround, spacing: -1, }, { name: "length spacing 1", want: [][]int{{0, 20}, {22, 20}, {44, 20}}, constraints: []Constraint{Len(20), Len(20), Len(20)}, flex: FlexStart, spacing: 2, }, { name: "length spacing 2", want: [][]int{{18, 20}, {40, 20}, {62, 20}}, constraints: []Constraint{Len(20), Len(20), Len(20)}, flex: FlexCenter, spacing: 2, }, { name: "length spacing 3", want: [][]int{{36, 20}, {58, 20}, {80, 20}}, constraints: []Constraint{Len(20), Len(20), Len(20)}, flex: FlexEnd, spacing: 2, }, { name: "length spacing 4", want: [][]int{{0, 20}, {22, 20}, {44, 56}}, constraints: []Constraint{Len(20), Len(20), Len(20)}, flex: FlexLegacy, spacing: 2, }, { name: "length spacing 5", want: [][]int{{0, 20}, {40, 20}, {80, 20}}, constraints: []Constraint{Len(20), Len(20), Len(20)}, flex: FlexSpaceBetween, spacing: 2, }, { name: "length spacing 6", want: [][]int{{10, 20}, {40, 20}, {70, 20}}, constraints: []Constraint{Len(20), Len(20), Len(20)}, flex: FlexSpaceEvenly, spacing: 2, }, { name: "length spacing 7", want: [][]int{{7, 20}, {40, 20}, {73, 20}}, constraints: []Constraint{Len(20), Len(20), Len(20)}, flex: FlexSpaceAround, spacing: 2, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { t.Parallel() rect := uv.Rect(0, 0, 100, 1) splitted := Horizontal(tc.constraints...). WithFlex(tc.flex). WithSpacing(tc.spacing). Split(rect) got := make([][]int, 0, len(splitted)) for _, r := range splitted { got = append(got, []int{r.Min.X, r.Dx()}) } if !reflect.DeepEqual(tc.want, got) { t.Fatalf("not equal: want %#+v, got %#+v", tc.want, got) } }) } } func letters(t *testing.T, flex Flex, constraints []Constraint, width int, expected string) { t.Helper() area := uv.Rect(0, 0, width, 1) layout := Layout{ Direction: DirectionHorizontal, Constraints: constraints, Flex: flex, }.Split(area) got := uv.NewScreenBuffer(area.Dx(), area.Dy()) latin := []rune("abcdefghijklmnopqrstuvwxyz") for i := 0; i < min(len(constraints), len(layout)); i++ { c := latin[i] area := layout[i] s := strings.Repeat(string(c), area.Dx()) buffer := uv.NewScreenBuffer(area.Dx(), area.Dy()) screen.NewContext(buffer).WriteString(s) buffer.Draw(got, area) } want := newBufferString(expected) if !reflect.DeepEqual(want, got) { t.Fatalf("not equal: want %#+v, got %#+v", want, got) } } func newBufferString(s string) uv.ScreenBuffer { var width, height int for line := range strings.Lines(s) { width = max(width, len(line)) height++ } buf := uv.NewScreenBuffer(width, height) screen.NewContext(buf).WriteString(s) return buf }