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 = s.UnsetBold() requireFalse(t, s.GetBold()) s = NewStyle().Italic(true) requireTrue(t, s.GetItalic()) s = s.UnsetItalic() requireFalse(t, s.GetItalic()) s = NewStyle().Underline(true) requireTrue(t, s.GetUnderline()) s = s.UnsetUnderline() requireFalse(t, s.GetUnderline()) s = NewStyle().Strikethrough(true) requireTrue(t, s.GetStrikethrough()) s = s.UnsetStrikethrough() requireFalse(t, s.GetStrikethrough()) s = NewStyle().Reverse(true) requireTrue(t, s.GetReverse()) s = s.UnsetReverse() requireFalse(t, s.GetReverse()) s = NewStyle().Blink(true) requireTrue(t, s.GetBlink()) s = s.UnsetBlink() requireFalse(t, s.GetBlink()) s = NewStyle().Faint(true) requireTrue(t, s.GetFaint()) s = s.UnsetFaint() requireFalse(t, s.GetFaint()) s = NewStyle().Inline(true) requireTrue(t, s.GetInline()) s = s.UnsetInline() requireFalse(t, s.GetInline()) // colors col := Color("#ffffff") s = NewStyle().Foreground(col) requireEqual(t, col, s.GetForeground()) s = s.UnsetForeground() requireNotEqual(t, col, s.GetForeground()) s = NewStyle().Background(col) requireEqual(t, col, s.GetBackground()) s = s.UnsetBackground() requireNotEqual(t, col, s.GetBackground()) // margins s = NewStyle().Margin(1, 2, 3, 4) requireEqual(t, 1, s.GetMarginTop()) s = s.UnsetMarginTop() requireEqual(t, 0, s.GetMarginTop()) requireEqual(t, 2, s.GetMarginRight()) s = s.UnsetMarginRight() requireEqual(t, 0, s.GetMarginRight()) requireEqual(t, 3, s.GetMarginBottom()) s = s.UnsetMarginBottom() requireEqual(t, 0, s.GetMarginBottom()) requireEqual(t, 4, s.GetMarginLeft()) s = s.UnsetMarginLeft() requireEqual(t, 0, s.GetMarginLeft()) // padding s = NewStyle().Padding(1, 2, 3, 4) requireEqual(t, 1, s.GetPaddingTop()) s = s.UnsetPaddingTop() requireEqual(t, 0, s.GetPaddingTop()) requireEqual(t, 2, s.GetPaddingRight()) s = s.UnsetPaddingRight() requireEqual(t, 0, s.GetPaddingRight()) requireEqual(t, 3, s.GetPaddingBottom()) s = s.UnsetPaddingBottom() requireEqual(t, 0, s.GetPaddingBottom()) requireEqual(t, 4, s.GetPaddingLeft()) s = s.UnsetPaddingLeft() requireEqual(t, 0, s.GetPaddingLeft()) // border s = NewStyle().Border(normalBorder, true, true, true, true) requireTrue(t, s.GetBorderTop()) s = s.UnsetBorderTop() requireFalse(t, s.GetBorderTop()) requireTrue(t, s.GetBorderRight()) s = s.UnsetBorderRight() requireFalse(t, s.GetBorderRight()) requireTrue(t, s.GetBorderBottom()) s = s.UnsetBorderBottom() requireFalse(t, s.GetBorderBottom()) requireTrue(t, s.GetBorderLeft()) s = s.UnsetBorderLeft() requireFalse(t, s.GetBorderLeft()) // tab width s = NewStyle().TabWidth(2) requireEqual(t, s.GetTabWidth(), 2) s = 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) { tb.Helper() requireEqual(tb, true, b) } func requireFalse(tb testing.TB, b bool) { tb.Helper() 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() } }