1package core
2
3import (
4 "testing"
5 "testing/quick"
6)
7
8func TestStyleCanonicalClearsInactiveColorPayloads(t *testing.T) {
9 style := Style{
10 Foreground: 7,
11 Background: 8,
12 HasForegroundRGB: true,
13 ForegroundRGB: RGB{R: 1, G: 2, B: 3},
14 BackgroundRGB: RGB{R: 4, G: 5, B: 6},
15 UnderlineColor: 9,
16 UnderlineColorRGB: RGB{R: 7, G: 8, B: 9},
17 HasUnderlineColorRGB: true,
18 }
19
20 got := style.Canonical()
21 if got.Foreground != 0 {
22 t.Fatalf("inactive indexed foreground = %d, want 0", got.Foreground)
23 }
24 if got.BackgroundRGB != (RGB{}) {
25 t.Fatalf("inactive RGB background = %+v, want zero", got.BackgroundRGB)
26 }
27 if got.UnderlineColor != 0 || got.HasUnderlineColor {
28 t.Fatalf("inactive indexed underline = (%d, %t), want zero and false", got.UnderlineColor, got.HasUnderlineColor)
29 }
30}
31
32func TestStyleCanonicalCollapsesEveryInactiveVariant(t *testing.T) {
33 tests := []struct {
34 name string
35 left Style
36 right Style
37 }{
38 {
39 name: "indexed colors ignore RGB payloads and absent underline payloads",
40 left: Style{Foreground: 1, Background: 2},
41 right: Style{
42 Foreground: 1,
43 ForegroundRGB: RGB{R: 1, G: 2, B: 3},
44 Background: 2,
45 BackgroundRGB: RGB{R: 4, G: 5, B: 6},
46 UnderlineColor: 3,
47 UnderlineColorRGB: RGB{R: 7, G: 8, B: 9},
48 },
49 },
50 {
51 name: "RGB colors ignore indexed payloads",
52 left: Style{
53 HasForegroundRGB: true,
54 ForegroundRGB: RGB{R: 1, G: 2, B: 3},
55 HasBackgroundRGB: true,
56 BackgroundRGB: RGB{R: 4, G: 5, B: 6},
57 HasUnderlineColorRGB: true,
58 UnderlineColorRGB: RGB{R: 7, G: 8, B: 9},
59 },
60 right: Style{
61 Foreground: 1,
62 Background: 2,
63 HasForegroundRGB: true,
64 ForegroundRGB: RGB{R: 1, G: 2, B: 3},
65 HasBackgroundRGB: true,
66 BackgroundRGB: RGB{R: 4, G: 5, B: 6},
67 HasUnderlineColor: true,
68 UnderlineColor: 3,
69 HasUnderlineColorRGB: true,
70 UnderlineColorRGB: RGB{R: 7, G: 8, B: 9},
71 },
72 },
73 }
74
75 for _, test := range tests {
76 t.Run(test.name, func(t *testing.T) {
77 if !test.left.Equal(test.right) {
78 t.Fatal("fixture styles must be semantically equal")
79 }
80 if test.left.Canonical() != test.right.Canonical() {
81 t.Fatalf("canonical styles differ: %+v != %+v", test.left.Canonical(), test.right.Canonical())
82 }
83 })
84 }
85}
86
87func TestStyleCanonicalExactlyMatchesEqual(t *testing.T) {
88 property := func(a, b Style) bool {
89 return a.Equal(b) == (a.Canonical() == b.Canonical())
90 }
91 if err := quick.Check(property, &quick.Config{MaxCount: 10_000}); err != nil {
92 t.Fatal(err)
93 }
94}
95
96func TestStyleCanonicalIsIdempotent(t *testing.T) {
97 property := func(style Style) bool {
98 canonical := style.Canonical()
99 return canonical.Canonical() == canonical
100 }
101 if err := quick.Check(property, &quick.Config{MaxCount: 10_000}); err != nil {
102 t.Fatal(err)
103 }
104}
105
106func TestZeroAndDefaultStylesRemainDistinct(t *testing.T) {
107 zero := Style{}
108 defaultStyle := DefaultStyle()
109 if zero.Equal(defaultStyle) {
110 t.Fatal("zero Style unexpectedly equals DefaultStyle")
111 }
112 if zero.Canonical() == defaultStyle.Canonical() {
113 t.Fatal("canonicalization collapsed indexed color zero into unset default colors")
114 }
115}