From 0662733ad95583c5ec2fb701f2b5bbcaf54d5e86 Mon Sep 17 00:00:00 2001
From: mjarkk <mkopenga@gmail.com>
Date: Sat, 31 Jul 2021 20:48:40 +0200
Subject: [PATCH] add tests for color changes

---
 pkg/gui/style/color.go      |   9 +-
 pkg/gui/style/decoration.go |   6 +-
 pkg/gui/style/style_test.go | 383 ++++++++++--------------------------
 pkg/gui/style/text_style.go |   6 +-
 pkg/utils/color_test.go     |  63 ------
 5 files changed, 118 insertions(+), 349 deletions(-)
 delete mode 100644 pkg/utils/color_test.go

diff --git a/pkg/gui/style/color.go b/pkg/gui/style/color.go
index b2df21bbf..2b13c9236 100644
--- a/pkg/gui/style/color.go
+++ b/pkg/gui/style/color.go
@@ -23,10 +23,17 @@ func (c Color) IsRGB() bool {
 	return c.rgb != nil
 }
 
-func (c Color) ToRGB() Color {
+func (c Color) ToRGB(isBg bool) Color {
 	if c.IsRGB() {
 		return c
 	}
 
+	if isBg {
+		// We need to convert bg color to fg color
+		// This is a gookit/color bug,
+		// https://github.com/gookit/color/issues/39
+		return NewRGBColor((*c.basic - 10).RGB())
+	}
+
 	return NewRGBColor(c.basic.RGB())
 }
diff --git a/pkg/gui/style/decoration.go b/pkg/gui/style/decoration.go
index 51887fbdd..f6fedb879 100644
--- a/pkg/gui/style/decoration.go
+++ b/pkg/gui/style/decoration.go
@@ -8,15 +8,15 @@ type Decoration struct {
 	reverse   bool
 }
 
-func (d Decoration) SetBold() {
+func (d *Decoration) SetBold() {
 	d.bold = true
 }
 
-func (d Decoration) SetUnderline() {
+func (d *Decoration) SetUnderline() {
 	d.underline = true
 }
 
-func (d Decoration) SetReverse() {
+func (d *Decoration) SetReverse() {
 	d.reverse = true
 }
 
diff --git a/pkg/gui/style/style_test.go b/pkg/gui/style/style_test.go
index 669bd4db5..3b301d160 100644
--- a/pkg/gui/style/style_test.go
+++ b/pkg/gui/style/style_test.go
@@ -7,308 +7,131 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestNewStyle(t *testing.T) {
+func TestMerge(t *testing.T) {
 	type scenario struct {
 		name          string
-		fg, bg        color.Color
-		expectedStyle color.Style
+		toMerge       []TextStyle
+		expectedStyle TextStyle
 	}
 
+	fgRed := color.FgRed
+	bgRed := color.BgRed
+	fgBlue := color.FgBlue
+
+	rgbPinkLib := color.Rgb(0xFF, 0x00, 0xFF)
+	rgbPink := NewRGBColor(rgbPinkLib)
+
+	rgbYellowLib := color.Rgb(0xFF, 0xFF, 0x00)
+	rgbYellow := NewRGBColor(rgbYellowLib)
+
 	scenarios := []scenario{
 		{
 			"no color",
-			0, 0,
-			color.Style{},
+			nil,
+			TextStyle{style: color.Style{}},
 		},
 		{
 			"only fg color",
-			color.FgRed, 0,
-			color.Style{color.FgRed},
+			[]TextStyle{FgRed},
+			TextStyle{fg: &Color{basic: &fgRed}, style: color.Style{fgRed}},
 		},
 		{
 			"only bg color",
-			0, color.BgRed,
-			color.Style{color.BgRed},
+			[]TextStyle{BgRed},
+			TextStyle{bg: &Color{basic: &bgRed}, style: color.Style{bgRed}},
 		},
 		{
 			"fg and bg color",
-			color.FgBlue, color.BgRed,
-			color.Style{color.FgBlue, color.BgRed},
+			[]TextStyle{FgBlue, BgRed},
+			TextStyle{
+				fg:    &Color{basic: &fgBlue},
+				bg:    &Color{basic: &bgRed},
+				style: color.Style{fgBlue, bgRed},
+			},
+		},
+		{
+			"single attribute",
+			[]TextStyle{AttrBold},
+			TextStyle{
+				decoration: Decoration{bold: true},
+				style:      color.Style{color.OpBold},
+			},
+		},
+		{
+			"multiple attributes",
+			[]TextStyle{AttrBold, AttrUnderline},
+			TextStyle{
+				decoration: Decoration{
+					bold:      true,
+					underline: true,
+				},
+				style: color.Style{color.OpBold, color.OpUnderscore},
+			},
+		},
+		{
+			"multiple attributes and colors",
+			[]TextStyle{AttrBold, FgBlue, AttrUnderline, BgRed},
+			TextStyle{
+				fg: &Color{basic: &fgBlue},
+				bg: &Color{basic: &bgRed},
+				decoration: Decoration{
+					bold:      true,
+					underline: true,
+				},
+				style: color.Style{fgBlue, bgRed, color.OpBold, color.OpUnderscore},
+			},
+		},
+		{
+			"rgb fg color",
+			[]TextStyle{New().SetFg(rgbPink)},
+			TextStyle{
+				fg:    &rgbPink,
+				style: color.NewRGBStyle(rgbPinkLib).SetOpts(color.Opts{}),
+			},
+		},
+		{
+			"rgb fg and bg color",
+			[]TextStyle{New().SetFg(rgbPink).SetBg(rgbYellow)},
+			TextStyle{
+				fg:    &rgbPink,
+				bg:    &rgbYellow,
+				style: color.NewRGBStyle(rgbPinkLib, rgbYellowLib).SetOpts(color.Opts{}),
+			},
+		},
+		{
+			"rgb fg and bg color with opts",
+			[]TextStyle{AttrBold, New().SetFg(rgbPink).SetBg(rgbYellow), AttrUnderline},
+			TextStyle{
+				fg: &rgbPink,
+				bg: &rgbYellow,
+				decoration: Decoration{
+					bold:      true,
+					underline: true,
+				},
+				style: color.NewRGBStyle(rgbPinkLib, rgbYellowLib).SetOpts(color.Opts{color.OpBold, color.OpUnderscore}),
+			},
+		},
+		{
+			"mix color-16 with rgb colors",
+			[]TextStyle{New().SetFg(rgbYellow), BgRed},
+			TextStyle{
+				fg: &rgbYellow,
+				bg: &Color{basic: &bgRed},
+				style: color.NewRGBStyle(
+					rgbYellowLib,
+					fgRed.RGB(), // We need to use FG here,  https://github.com/gookit/color/issues/39
+				).SetOpts(color.Opts{}),
+			},
 		},
 	}
 
 	for _, s := range scenarios {
 		t.Run(s.name, func(t *testing.T) {
-			style := New(s.fg, s.bg)
-			basicStyle, ok := style.(BasicTextStyle)
-			assert.True(t, ok, "New(..) should return a interface of type BasicTextStyle")
-			assert.Equal(t, s.fg, basicStyle.fg)
-			assert.Equal(t, s.bg, basicStyle.bg)
-			assert.Equal(t, []color.Color(nil), basicStyle.opts)
-			assert.Equal(t, s.expectedStyle, basicStyle.style)
-		})
-	}
-}
-
-func TestBasicSetColor(t *testing.T) {
-	type scenario struct {
-		name       string
-		colorToSet TextStyle
-		expect     TextStyle
-	}
-
-	scenarios := []scenario{
-		{
-			"empty color",
-			TextStyle{},
-			TextStyle{fg: color.FgRed, bg: color.BgBlue, opts: []color.Color{color.OpBold}}},
-		{
-			"set new fg color",
-			TextStyle{fg: color.FgCyan},
-			TextStyle{fg: color.FgCyan, bg: color.BgBlue, opts: []color.Color{color.OpBold}},
-		},
-		{
-			"set new bg color",
-			TextStyle{bg: color.BgGray},
-			TextStyle{fg: color.FgRed, bg: color.BgGray, opts: []color.Color{color.OpBold}},
-		},
-		{
-			"set new fg and bg color",
-			TextStyle{fg: color.FgCyan, bg: color.BgGray},
-			TextStyle{fg: color.FgCyan, bg: color.BgGray, opts: []color.Color{color.OpBold}},
-		},
-		{
-			"add options",
-			TextStyle{opts: []color.Color{color.OpUnderscore}},
-			TextStyle{fg: color.FgRed, bg: color.BgBlue, opts: []color.Color{color.OpBold, color.OpUnderscore}},
-		},
-		{
-			"add options that already exists",
-			TextStyle{opts: []color.Color{color.OpBold}},
-			TextStyle{fg: color.FgRed, bg: color.BgBlue, opts: []color.Color{color.OpBold}},
-		},
-	}
-
-	for _, s := range scenarios {
-		t.Run(s.name, func(t *testing.T) {
-			style, ok := New(color.FgRed, color.BgBlue).
-				SetBold(true).
-				SetColor(s.colorToSet).(BasicTextStyle)
-			assert.True(t, ok, "SetColor should return a interface of type BasicTextStyle if the input was also BasicTextStyle")
-
-			style.style = nil
-			assert.Equal(t, s.expect, style)
-		})
-	}
-}
-
-func TestRGBSetColor(t *testing.T) {
-	type scenario struct {
-		name       string
-		colorToSet TextStyle
-		expect     RGBTextStyle
-	}
-
-	red := color.FgRed.RGB()
-	cyan := color.FgCyan.RGB()
-	blue := color.FgBlue.RGB()
-	gray := color.FgGray.RGB()
-
-	toBg := func(c color.RGBColor) *color.RGBColor {
-		c[3] = 1
-		return &c
-	}
-
-	scenarios := []scenario{
-		{
-			"empty RGBTextStyle input",
-			RGBTextStyle{},
-			RGBTextStyle{fgSet: true, fg: red, bg: toBg(blue), opts: []color.Color{color.OpBold}},
-		},
-		{
-			"empty BasicTextStyle input",
-			TextStyle{},
-			RGBTextStyle{fgSet: true, fg: red, bg: toBg(blue), opts: []color.Color{color.OpBold}},
-		},
-		{
-			"set fg and bg color using BasicTextStyle",
-			TextStyle{fg: color.FgCyan, bg: color.BgGray},
-			RGBTextStyle{fgSet: true, fg: cyan, bg: toBg(gray), opts: []color.Color{color.OpBold}},
-		},
-		{
-			"set fg and bg color using RGBTextStyle",
-			RGBTextStyle{fgSet: true, fg: cyan, bg: toBg(gray)},
-			RGBTextStyle{fgSet: true, fg: cyan, bg: toBg(gray), opts: []color.Color{color.OpBold}},
-		},
-		{
-			"add options",
-			RGBTextStyle{opts: []color.Color{color.OpUnderscore}},
-			RGBTextStyle{fgSet: true, fg: red, bg: toBg(blue), opts: []color.Color{color.OpBold, color.OpUnderscore}},
-		},
-		{
-			"add options using BasicTextStyle",
-			TextStyle{opts: []color.Color{color.OpUnderscore}},
-			RGBTextStyle{fgSet: true, fg: red, bg: toBg(blue), opts: []color.Color{color.OpBold, color.OpUnderscore}},
-		},
-		{
-			"add options that already exists",
-			RGBTextStyle{opts: []color.Color{color.OpBold}},
-			RGBTextStyle{fgSet: true, fg: red, bg: toBg(blue), opts: []color.Color{color.OpBold}},
-		},
-	}
-
-	for _, s := range scenarios {
-		t.Run(s.name, func(t *testing.T) {
-			style, ok := New(color.FgRed, color.BgBlue).SetBold().(BasicTextStyle)
-			assert.True(t, ok, "SetBold should return a interface of type BasicTextStyle")
-
-			rgbStyle, ok := style.convertToRGB().MergeStyle(s.colorToSet).(RGBTextStyle)
-			assert.True(t, ok, "SetColor should return a interface of type RGBTextColor")
-
-			rgbStyle.style = color.RGBStyle{}
-			assert.Equal(t, s.expect, rgbStyle)
-		})
-	}
-}
-
-func TestConvertBasicToRGB(t *testing.T) {
-	type scenario struct {
-		name string
-		test func(*testing.T)
-	}
-
-	scenarios := []scenario{
-		{
-			"convert to rgb with fg",
-			func(t *testing.T) {
-				basicStyle, ok := New(color.FgRed, 0).(BasicTextStyle)
-				assert.True(t, ok, "New(..) should return a interface of type BasicTextStyle")
-
-				rgbStyle := basicStyle.convertToRGB()
-				assert.True(t, rgbStyle.fgSet)
-				assert.Equal(t, color.RGB(197, 30, 20), rgbStyle.fg)
-				assert.Nil(t, rgbStyle.bg)
-			},
-		},
-		{
-			"convert to rgb with fg and bg",
-			func(t *testing.T) {
-				basicStyle, ok := New(color.FgRed, color.BgRed).(BasicTextStyle)
-				assert.True(t, ok, "New(..) should return a interface of type BasicTextStyle")
-
-				rgbStyle := basicStyle.convertToRGB()
-				assert.True(t, rgbStyle.fgSet)
-				assert.Equal(t, color.RGB(197, 30, 20), rgbStyle.fg)
-				assert.Equal(t, color.RGB(197, 30, 20, true), *rgbStyle.bg)
-			},
-		},
-		{
-			"convert to rgb using SetRGBColor",
-			func(t *testing.T) {
-				style := New(color.FgRed, 0)
-				rgbStyle, ok := style.SetRGBColor(255, 00, 255, true).(RGBTextStyle)
-				assert.True(t, ok, "SetRGBColor should return a interface of type RGBTextStyle")
-
-				assert.True(t, rgbStyle.fgSet)
-				assert.Equal(t, color.RGB(197, 30, 20), rgbStyle.fg)
-				assert.Equal(t, color.RGB(255, 0, 255, true), *rgbStyle.bg)
-			},
-		},
-		{
-			"convert to rgb using SetRGBColor multiple times",
-			func(t *testing.T) {
-				style := New(color.FgRed, 0)
-				rgbStyle, ok := style.SetRGBColor(00, 255, 255, false).SetRGBColor(255, 00, 255, true).(RGBTextStyle)
-				assert.True(t, ok, "SetRGBColor should return a interface of type RGBTextStyle")
-
-				assert.True(t, rgbStyle.fgSet)
-				assert.Equal(t, color.RGB(0, 255, 255), rgbStyle.fg)
-				assert.Equal(t, color.RGB(255, 0, 255, true), *rgbStyle.bg)
-			},
-		},
-	}
-
-	for _, s := range scenarios {
-		t.Run(s.name, s.test)
-	}
-}
-
-func TestSettingAtributes(t *testing.T) {
-	type scenario struct {
-		name         string
-		test         func(s TextStyle) TextStyle
-		expectedOpts []color.Color
-	}
-
-	scenarios := []scenario{
-		{
-			"no attributes",
-			func(s TextStyle) TextStyle {
-				return s
-			},
-			[]color.Color{},
-		},
-		{
-			"set single attribute",
-			func(s TextStyle) TextStyle {
-				return s.SetBold(true)
-			},
-			[]color.Color{color.OpBold},
-		},
-		{
-			"set multiple attributes",
-			func(s TextStyle) TextStyle {
-				return s.SetBold(true).SetUnderline(true)
-			},
-			[]color.Color{color.OpBold, color.OpUnderscore},
-		},
-		{
-			"unset a attributes",
-			func(s TextStyle) TextStyle {
-				return s.SetBold(true).SetBold(false)
-			},
-			[]color.Color{},
-		},
-		{
-			"unset a attributes with multiple attributes",
-			func(s TextStyle) TextStyle {
-				return s.SetBold(true).SetUnderline(true).SetBold(false)
-			},
-			[]color.Color{color.OpUnderscore},
-		},
-		{
-			"unset all attributes with multiple attributes",
-			func(s TextStyle) TextStyle {
-				return s.SetBold(true).SetUnderline(true).SetBold(false).SetUnderline(false)
-			},
-			[]color.Color{},
-		},
-	}
-
-	for _, s := range scenarios {
-		t.Run(s.name, func(t *testing.T) {
-			// Test basic style
-			style := New(color.FgRed, 0)
-			basicStyle, ok := style.(BasicTextStyle)
-			assert.True(t, ok, "New(..) should return a interface of type BasicTextStyle")
-			basicStyle, ok = s.test(basicStyle).(BasicTextStyle)
-			assert.True(t, ok, "underlaying type should not be changed after test")
-			assert.Len(t, basicStyle.opts, len(s.expectedOpts))
-			for _, opt := range basicStyle.opts {
-				assert.Contains(t, s.expectedOpts, opt)
-			}
-			for _, opt := range s.expectedOpts {
-				assert.Contains(t, basicStyle.style, opt)
-			}
-
-			// Test RGB style
-			rgbStyle := New(color.FgRed, 0).(BasicTextStyle).convertToRGB()
-			rgbStyle, ok = s.test(rgbStyle).(RGBTextStyle)
-			assert.True(t, ok, "underlaying type should not be changed after test")
-			assert.Len(t, rgbStyle.opts, len(s.expectedOpts))
-			for _, opt := range rgbStyle.opts {
-				assert.Contains(t, s.expectedOpts, opt)
+			style := New()
+			for _, other := range s.toMerge {
+				style = style.MergeStyle(other)
 			}
+			assert.Equal(t, s.expectedStyle, style)
 		})
 	}
 }
diff --git a/pkg/gui/style/text_style.go b/pkg/gui/style/text_style.go
index d2bfccaeb..fd893a2be 100644
--- a/pkg/gui/style/text_style.go
+++ b/pkg/gui/style/text_style.go
@@ -134,11 +134,13 @@ func (b TextStyle) deriveRGBStyle() *color.RGBStyle {
 	style := &color.RGBStyle{}
 
 	if b.fg != nil {
-		style.SetFg(*b.fg.ToRGB().rgb)
+		style.SetFg(*b.fg.ToRGB(false).rgb)
 	}
 
 	if b.bg != nil {
-		style.SetBg(*b.bg.ToRGB().rgb)
+		// We need to convert the bg firstly to a foreground color,
+		// For more info see
+		style.SetBg(*b.bg.ToRGB(true).rgb)
 	}
 
 	style.SetOpts(b.decoration.ToOpts())
diff --git a/pkg/utils/color_test.go b/pkg/utils/color_test.go
deleted file mode 100644
index 8d7f5c48e..000000000
--- a/pkg/utils/color_test.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package utils
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func TestGetHexColorValues(t *testing.T) {
-	scenarios := []struct {
-		name     string
-		hexColor string
-		rgb      []int32
-		valid    bool
-	}{
-		{
-			name:     "valid uppercase hex color",
-			hexColor: "#00FF00",
-			rgb:      []int32{0, 255, 0},
-			valid:    true,
-		},
-		{
-			name:     "valid lowercase hex color",
-			hexColor: "#00ff00",
-			rgb:      []int32{0, 255, 0},
-			valid:    true,
-		},
-		{
-			name:     "valid short hex color",
-			hexColor: "#0bf",
-			rgb:      []int32{0, 187, 255},
-			valid:    true,
-		},
-		{
-			name:     "invalid hex value",
-			hexColor: "#zz00ff",
-			valid:    false,
-		},
-		{
-			name:     "invalid length hex color",
-			hexColor: "#",
-			valid:    false,
-		},
-		{
-			name:     "invalid length hex color",
-			hexColor: "#aaaaaaaaaaa",
-			valid:    false,
-		},
-	}
-
-	for _, s := range scenarios {
-		s := s
-		t.Run(s.name, func(t *testing.T) {
-			r, g, b, valid := GetHexColorValues(s.hexColor)
-			assert.EqualValues(t, s.valid, valid, s.hexColor)
-			if valid {
-				assert.EqualValues(t, s.rgb[0], r, s.hexColor)
-				assert.EqualValues(t, s.rgb[1], g, s.hexColor)
-				assert.EqualValues(t, s.rgb[2], b, s.hexColor)
-			}
-		})
-	}
-}