lipgloss/style_test.go
2024-03-22 16:19:39 -04:00

485 lines
11 KiB
Go

package lipgloss
import (
"io"
"reflect"
"strings"
"testing"
"github.com/muesli/termenv"
)
func TestStyleRender(t *testing.T) {
r := NewRenderer(io.Discard)
r.SetColorProfile(termenv.TrueColor)
r.SetHasDarkBackground(true)
t.Parallel()
tt := []struct {
style Style
expected string
}{
{
r.NewStyle().Foreground(Color("#5A56E0")),
"\x1b[38;2;89;86;224mhello\x1b[0m",
},
{
r.NewStyle().Foreground(AdaptiveColor{Light: "#fffe12", Dark: "#5A56E0"}),
"\x1b[38;2;89;86;224mhello\x1b[0m",
},
{
r.NewStyle().Bold(true),
"\x1b[1mhello\x1b[0m",
},
{
r.NewStyle().Italic(true),
"\x1b[3mhello\x1b[0m",
},
{
r.NewStyle().Underline(true),
"\x1b[4;4mh\x1b[0m\x1b[4;4me\x1b[0m\x1b[4;4ml\x1b[0m\x1b[4;4ml\x1b[0m\x1b[4;4mo\x1b[0m",
},
{
r.NewStyle().Blink(true),
"\x1b[5mhello\x1b[0m",
},
{
r.NewStyle().Faint(true),
"\x1b[2mhello\x1b[0m",
},
}
for i, tc := range tt {
s := tc.style.Copy().SetString("hello")
res := s.Render()
if res != tc.expected {
t.Errorf("Test %d, expected:\n\n`%s`\n`%s`\n\nActual output:\n\n`%s`\n`%s`\n\n",
i, tc.expected, formatEscapes(tc.expected),
res, formatEscapes(res))
}
}
}
func TestStyleCustomRender(t *testing.T) {
r := NewRenderer(io.Discard)
r.SetHasDarkBackground(false)
r.SetColorProfile(termenv.TrueColor)
tt := []struct {
style Style
expected string
}{
{
r.NewStyle().Foreground(Color("#5A56E0")),
"\x1b[38;2;89;86;224mhello\x1b[0m",
},
{
r.NewStyle().Foreground(AdaptiveColor{Light: "#fffe12", Dark: "#5A56E0"}),
"\x1b[38;2;255;254;18mhello\x1b[0m",
},
{
r.NewStyle().Bold(true),
"\x1b[1mhello\x1b[0m",
},
{
r.NewStyle().Italic(true),
"\x1b[3mhello\x1b[0m",
},
{
r.NewStyle().Underline(true),
"\x1b[4;4mh\x1b[0m\x1b[4;4me\x1b[0m\x1b[4;4ml\x1b[0m\x1b[4;4ml\x1b[0m\x1b[4;4mo\x1b[0m",
},
{
r.NewStyle().Blink(true),
"\x1b[5mhello\x1b[0m",
},
{
r.NewStyle().Faint(true),
"\x1b[2mhello\x1b[0m",
},
{
NewStyle().Faint(true).Renderer(r),
"\x1b[2mhello\x1b[0m",
},
}
for i, tc := range tt {
s := tc.style.Copy().SetString("hello")
res := s.Render()
if res != tc.expected {
t.Errorf("Test %d, expected:\n\n`%s`\n`%s`\n\nActual output:\n\n`%s`\n`%s`\n\n",
i, tc.expected, formatEscapes(tc.expected),
res, formatEscapes(res))
}
}
}
func TestStyleRenderer(t *testing.T) {
r := NewRenderer(io.Discard)
s1 := NewStyle().Bold(true)
s2 := s1.Renderer(r)
if s1.r == s2.r {
t.Fatalf("expected different renderers")
}
}
func TestValueCopy(t *testing.T) {
t.Parallel()
s := NewStyle().
Bold(true)
i := s
i.Bold(false)
requireEqual(t, s.GetBold(), i.GetBold())
}
func TestStyleInherit(t *testing.T) {
t.Parallel()
s := NewStyle().
Bold(true).
Italic(true).
Underline(true).
Strikethrough(true).
Blink(true).
Faint(true).
Foreground(Color("#ffffff")).
Background(Color("#111111")).
Margin(1, 1, 1, 1).
Padding(1, 1, 1, 1)
i := NewStyle().Inherit(s)
requireEqual(t, s.GetBold(), i.GetBold())
requireEqual(t, s.GetItalic(), i.GetItalic())
requireEqual(t, s.GetUnderline(), i.GetUnderline())
requireEqual(t, s.GetStrikethrough(), i.GetStrikethrough())
requireEqual(t, s.GetBlink(), i.GetBlink())
requireEqual(t, s.GetFaint(), i.GetFaint())
requireEqual(t, s.GetForeground(), i.GetForeground())
requireEqual(t, s.GetBackground(), i.GetBackground())
requireNotEqual(t, s.GetMarginLeft(), i.GetMarginLeft())
requireNotEqual(t, s.GetMarginRight(), i.GetMarginRight())
requireNotEqual(t, s.GetMarginTop(), i.GetMarginTop())
requireNotEqual(t, s.GetMarginBottom(), i.GetMarginBottom())
requireNotEqual(t, s.GetPaddingLeft(), i.GetPaddingLeft())
requireNotEqual(t, s.GetPaddingRight(), i.GetPaddingRight())
requireNotEqual(t, s.GetPaddingTop(), i.GetPaddingTop())
requireNotEqual(t, s.GetPaddingBottom(), i.GetPaddingBottom())
}
func TestStyleCopy(t *testing.T) {
t.Parallel()
s := NewStyle().
Bold(true).
Italic(true).
Underline(true).
Strikethrough(true).
Blink(true).
Faint(true).
Foreground(Color("#ffffff")).
Background(Color("#111111")).
Margin(1, 1, 1, 1).
Padding(1, 1, 1, 1).
TabWidth(2)
i := s.Copy()
requireEqual(t, s.GetBold(), i.GetBold())
requireEqual(t, s.GetItalic(), i.GetItalic())
requireEqual(t, s.GetUnderline(), i.GetUnderline())
requireEqual(t, s.GetStrikethrough(), i.GetStrikethrough())
requireEqual(t, s.GetBlink(), i.GetBlink())
requireEqual(t, s.GetFaint(), i.GetFaint())
requireEqual(t, s.GetForeground(), i.GetForeground())
requireEqual(t, s.GetBackground(), i.GetBackground())
requireEqual(t, s.GetMarginLeft(), i.GetMarginLeft())
requireEqual(t, s.GetMarginRight(), i.GetMarginRight())
requireEqual(t, s.GetMarginTop(), i.GetMarginTop())
requireEqual(t, s.GetMarginBottom(), i.GetMarginBottom())
requireEqual(t, s.GetPaddingLeft(), i.GetPaddingLeft())
requireEqual(t, s.GetPaddingRight(), i.GetPaddingRight())
requireEqual(t, s.GetPaddingTop(), i.GetPaddingTop())
requireEqual(t, s.GetPaddingBottom(), i.GetPaddingBottom())
requireEqual(t, s.GetTabWidth(), i.GetTabWidth())
}
func TestStyleUnset(t *testing.T) {
t.Parallel()
s := NewStyle().Bold(true)
requireTrue(t, s.GetBold())
s.UnsetBold()
requireFalse(t, s.GetBold())
s = NewStyle().Italic(true)
requireTrue(t, s.GetItalic())
s.UnsetItalic()
requireFalse(t, s.GetItalic())
s = NewStyle().Underline(true)
requireTrue(t, s.GetUnderline())
s.UnsetUnderline()
requireFalse(t, s.GetUnderline())
s = NewStyle().Strikethrough(true)
requireTrue(t, s.GetStrikethrough())
s.UnsetStrikethrough()
requireFalse(t, s.GetStrikethrough())
s = NewStyle().Reverse(true)
requireTrue(t, s.GetReverse())
s.UnsetReverse()
requireFalse(t, s.GetReverse())
s = NewStyle().Blink(true)
requireTrue(t, s.GetBlink())
s.UnsetBlink()
requireFalse(t, s.GetBlink())
s = NewStyle().Faint(true)
requireTrue(t, s.GetFaint())
s.UnsetFaint()
requireFalse(t, s.GetFaint())
s = NewStyle().Inline(true)
requireTrue(t, s.GetInline())
s.UnsetInline()
requireFalse(t, s.GetInline())
// colors
col := Color("#ffffff")
s = NewStyle().Foreground(col)
requireEqual(t, col, s.GetForeground())
s.UnsetForeground()
requireNotEqual(t, col, s.GetForeground())
s = NewStyle().Background(col)
requireEqual(t, col, s.GetBackground())
s.UnsetBackground()
requireNotEqual(t, col, s.GetBackground())
// margins
s = NewStyle().Margin(1, 2, 3, 4)
requireEqual(t, 1, s.GetMarginTop())
s.UnsetMarginTop()
requireEqual(t, 0, s.GetMarginTop())
requireEqual(t, 2, s.GetMarginRight())
s.UnsetMarginRight()
requireEqual(t, 0, s.GetMarginRight())
requireEqual(t, 3, s.GetMarginBottom())
s.UnsetMarginBottom()
requireEqual(t, 0, s.GetMarginBottom())
requireEqual(t, 4, s.GetMarginLeft())
s.UnsetMarginLeft()
requireEqual(t, 0, s.GetMarginLeft())
// padding
s = NewStyle().Padding(1, 2, 3, 4)
requireEqual(t, 1, s.GetPaddingTop())
s.UnsetPaddingTop()
requireEqual(t, 0, s.GetPaddingTop())
requireEqual(t, 2, s.GetPaddingRight())
s.UnsetPaddingRight()
requireEqual(t, 0, s.GetPaddingRight())
requireEqual(t, 3, s.GetPaddingBottom())
s.UnsetPaddingBottom()
requireEqual(t, 0, s.GetPaddingBottom())
requireEqual(t, 4, s.GetPaddingLeft())
s.UnsetPaddingLeft()
requireEqual(t, 0, s.GetPaddingLeft())
// border
s = NewStyle().Border(normalBorder, true, true, true, true)
requireTrue(t, s.GetBorderTop())
s.UnsetBorderTop()
requireFalse(t, s.GetBorderTop())
requireTrue(t, s.GetBorderRight())
s.UnsetBorderRight()
requireFalse(t, s.GetBorderRight())
requireTrue(t, s.GetBorderBottom())
s.UnsetBorderBottom()
requireFalse(t, s.GetBorderBottom())
requireTrue(t, s.GetBorderLeft())
s.UnsetBorderLeft()
requireFalse(t, s.GetBorderLeft())
// tab width
s = NewStyle().TabWidth(2)
requireEqual(t, s.GetTabWidth(), 2)
s.UnsetTabWidth()
requireNotEqual(t, s.GetTabWidth(), 4)
}
func TestStyleValue(t *testing.T) {
t.Parallel()
tt := []struct {
name string
text string
style Style
expected string
}{
{
name: "empty",
text: "foo",
style: NewStyle(),
expected: "foo",
},
{
name: "set string",
text: "foo",
style: NewStyle().SetString("bar"),
expected: "bar foo",
},
{
name: "set string with bold",
text: "foo",
style: NewStyle().SetString("bar").Bold(true),
expected: "\x1b[1mbar foo\x1b[0m",
},
{
name: "new style with string",
text: "foo",
style: NewStyle().SetString("bar", "foobar"),
expected: "bar foobar foo",
},
{
name: "margin right",
text: "foo",
style: NewStyle().MarginRight(1),
expected: "foo ",
},
{
name: "margin left",
text: "foo",
style: NewStyle().MarginLeft(1),
expected: " foo",
},
{
name: "empty text margin right",
text: "",
style: NewStyle().MarginRight(1),
expected: " ",
},
{
name: "empty text margin left",
text: "",
style: NewStyle().MarginLeft(1),
expected: " ",
},
}
for i, tc := range tt {
res := tc.style.Render(tc.text)
if res != tc.expected {
t.Errorf("Test %d, expected:\n\n`%s`\n`%s`\n\nActual output:\n\n`%s`\n`%s`\n\n",
i, tc.expected, formatEscapes(tc.expected),
res, formatEscapes(res))
}
}
}
func TestTabConversion(t *testing.T) {
s := NewStyle()
requireEqual(t, "[ ]", s.Render("[\t]"))
s = NewStyle().TabWidth(2)
requireEqual(t, "[ ]", s.Render("[\t]"))
s = NewStyle().TabWidth(0)
requireEqual(t, "[]", s.Render("[\t]"))
s = NewStyle().TabWidth(-1)
requireEqual(t, "[\t]", s.Render("[\t]"))
}
func TestStringTransform(t *testing.T) {
for i, tc := range []struct {
input string
fn func(string) string
expected string
}{
// No-op.
{
"hello",
func(s string) string { return s },
"hello",
},
// Uppercase.
{
"raow",
strings.ToUpper,
"RAOW",
},
// English and Chinese.
{
"The quick brown 狐 jumped over the lazy 犬",
func(s string) string {
n := 0
rune := make([]rune, len(s))
for _, r := range s {
rune[n] = r
n++
}
rune = rune[0:n]
for i := 0; i < n/2; i++ {
rune[i], rune[n-1-i] = rune[n-1-i], rune[i]
}
return string(rune)
},
"犬 yzal eht revo depmuj 狐 nworb kciuq ehT",
},
} {
res := NewStyle().Bold(true).Transform(tc.fn).Render(tc.input)
expected := "\x1b[1m" + tc.expected + "\x1b[0m"
if res != expected {
t.Errorf("Test #%d:\nExpected: %q\nGot: %q", i+1, expected, res)
}
}
}
func BenchmarkStyleRender(b *testing.B) {
s := NewStyle().
Bold(true).
Foreground(Color("#ffffff"))
for i := 0; i < b.N; i++ {
s.Render("Hello world")
}
}
func requireTrue(tb testing.TB, b bool) {
requireEqual(tb, true, b)
}
func requireFalse(tb testing.TB, b bool) {
requireEqual(tb, false, b)
}
func requireEqual(tb testing.TB, a, b interface{}) {
tb.Helper()
if !reflect.DeepEqual(a, b) {
tb.Errorf("%v != %v", a, b)
tb.FailNow()
}
}
func requireNotEqual(tb testing.TB, a, b interface{}) {
tb.Helper()
if reflect.DeepEqual(a, b) {
tb.Errorf("%v == %v", a, b)
tb.FailNow()
}
}