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