1package style 2 3import ( 4 "bytes" 5 "testing" 6 "text/template" 7 8 "github.com/gookit/color" 9 "github.com/stretchr/testify/assert" 10 "github.com/xo/terminfo" 11) 12 13func init() { 14 // on CI we've got no color capability so we're forcing it here 15 color.ForceSetColorLevel(terminfo.ColorLevelMillions) 16} 17 18func TestMerge(t *testing.T) { 19 type scenario struct { 20 name string 21 toMerge []TextStyle 22 expectedStyle TextStyle 23 expectedStr string 24 } 25 26 fgRed := color.FgRed 27 bgRed := color.BgRed 28 fgBlue := color.FgBlue 29 30 rgbPinkLib := color.Rgb(0xFF, 0x00, 0xFF) 31 rgbPink := NewRGBColor(rgbPinkLib) 32 33 rgbYellowLib := color.Rgb(0xFF, 0xFF, 0x00) 34 rgbYellow := NewRGBColor(rgbYellowLib) 35 36 strToPrint := "foo" 37 38 scenarios := []scenario{ 39 { 40 "no color", 41 nil, 42 TextStyle{Style: color.Style{}}, 43 "foo", 44 }, 45 { 46 "only fg color", 47 []TextStyle{FgRed}, 48 TextStyle{fg: &Color{basic: &fgRed}, Style: color.Style{fgRed}}, 49 "\x1b[31mfoo\x1b[0m", 50 }, 51 { 52 "only bg color", 53 []TextStyle{BgRed}, 54 TextStyle{bg: &Color{basic: &bgRed}, Style: color.Style{bgRed}}, 55 "\x1b[41mfoo\x1b[0m", 56 }, 57 { 58 "fg and bg color", 59 []TextStyle{FgBlue, BgRed}, 60 TextStyle{ 61 fg: &Color{basic: &fgBlue}, 62 bg: &Color{basic: &bgRed}, 63 Style: color.Style{fgBlue, bgRed}, 64 }, 65 "\x1b[34;41mfoo\x1b[0m", 66 }, 67 { 68 "single attribute", 69 []TextStyle{AttrBold}, 70 TextStyle{ 71 decoration: Decoration{bold: true}, 72 Style: color.Style{color.OpBold}, 73 }, 74 "\x1b[1mfoo\x1b[0m", 75 }, 76 { 77 "multiple attributes", 78 []TextStyle{AttrBold, AttrUnderline}, 79 TextStyle{ 80 decoration: Decoration{ 81 bold: true, 82 underline: true, 83 }, 84 Style: color.Style{color.OpBold, color.OpUnderscore}, 85 }, 86 "\x1b[1;4mfoo\x1b[0m", 87 }, 88 { 89 "multiple attributes and colors", 90 []TextStyle{AttrBold, FgBlue, AttrUnderline, BgRed}, 91 TextStyle{ 92 fg: &Color{basic: &fgBlue}, 93 bg: &Color{basic: &bgRed}, 94 decoration: Decoration{ 95 bold: true, 96 underline: true, 97 }, 98 Style: color.Style{fgBlue, bgRed, color.OpBold, color.OpUnderscore}, 99 }, 100 "\x1b[34;41;1;4mfoo\x1b[0m", 101 }, 102 { 103 "rgb fg color", 104 []TextStyle{New().SetFg(rgbPink)}, 105 TextStyle{ 106 fg: &rgbPink, 107 Style: color.NewRGBStyle(rgbPinkLib).SetOpts(color.Opts{}), 108 }, 109 // '38;2' qualifies an RGB foreground color 110 "\x1b[38;2;255;0;255mfoo\x1b[0m", 111 }, 112 { 113 "rgb fg and bg color", 114 []TextStyle{New().SetFg(rgbPink).SetBg(rgbYellow)}, 115 TextStyle{ 116 fg: &rgbPink, 117 bg: &rgbYellow, 118 Style: color.NewRGBStyle(rgbPinkLib, rgbYellowLib).SetOpts(color.Opts{}), 119 }, 120 // '48;2' qualifies an RGB background color 121 "\x1b[38;2;255;0;255;48;2;255;255;0mfoo\x1b[0m", 122 }, 123 { 124 "rgb fg and bg color with opts", 125 []TextStyle{AttrBold, New().SetFg(rgbPink).SetBg(rgbYellow), AttrUnderline}, 126 TextStyle{ 127 fg: &rgbPink, 128 bg: &rgbYellow, 129 decoration: Decoration{ 130 bold: true, 131 underline: true, 132 }, 133 Style: color.NewRGBStyle(rgbPinkLib, rgbYellowLib).SetOpts(color.Opts{color.OpBold, color.OpUnderscore}), 134 }, 135 "\x1b[38;2;255;0;255;48;2;255;255;0;1;4mfoo\x1b[0m", 136 }, 137 { 138 "mix color-16 with rgb colors", 139 []TextStyle{New().SetFg(rgbYellow), BgRed}, 140 TextStyle{ 141 fg: &rgbYellow, 142 bg: &Color{basic: &bgRed}, 143 Style: color.NewRGBStyle( 144 rgbYellowLib, 145 fgRed.RGB(), // We need to use FG here, https://github.com/gookit/color/issues/39 146 ).SetOpts(color.Opts{}), 147 }, 148 "\x1b[38;2;255;255;0;48;2;197;30;20mfoo\x1b[0m", 149 }, 150 } 151 152 for _, s := range scenarios { 153 s := s 154 t.Run(s.name, func(t *testing.T) { 155 style := New() 156 for _, other := range s.toMerge { 157 style = style.MergeStyle(other) 158 } 159 assert.Equal(t, s.expectedStyle, style) 160 assert.Equal(t, s.expectedStr, style.Sprint(strToPrint)) 161 }) 162 } 163} 164 165func TestTemplateFuncMapAddColors(t *testing.T) { 166 type scenario struct { 167 name string 168 tmpl string 169 expect string 170 } 171 172 scenarios := []scenario{ 173 { 174 "normal template", 175 "{{ .Foo }}", 176 "bar", 177 }, 178 { 179 "colored string", 180 "{{ .Foo | red }}", 181 "\x1b[31mbar\x1b[0m", 182 }, 183 { 184 "string with decorator", 185 "{{ .Foo | bold }}", 186 "\x1b[1mbar\x1b[0m", 187 }, 188 { 189 "string with color and decorator", 190 "{{ .Foo | bold | red }}", 191 "\x1b[31m\x1b[1mbar\x1b[0m\x1b[0m", 192 }, 193 { 194 "multiple string with different colors", 195 "{{ .Foo | red }} - {{ .Foo | blue }}", 196 "\x1b[31mbar\x1b[0m - \x1b[34mbar\x1b[0m", 197 }, 198 } 199 200 for _, s := range scenarios { 201 s := s 202 t.Run(s.name, func(t *testing.T) { 203 tmpl, err := template.New("test template").Funcs(TemplateFuncMapAddColors(template.FuncMap{})).Parse(s.tmpl) 204 assert.NoError(t, err) 205 206 buff := bytes.NewBuffer(nil) 207 err = tmpl.Execute(buff, struct{ Foo string }{"bar"}) 208 assert.NoError(t, err) 209 210 assert.Equal(t, s.expect, buff.String()) 211 }) 212 } 213} 214