1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
package preview
import (
"reflect"
"regexp"
"strings"
"testing"
"github.com/charmbracelet/glamour/styles"
)
func TestLegibleTextContrasts(t *testing.T) {
// Dark/mid accents → light text; very light bg → dark text.
if got := legibleText("#4385BE"); got != "#FFFCF0" {
t.Errorf("legibleText(blue) = %q, want light", got)
}
if got := legibleText("#FF5FAF"); got != "#FFFCF0" {
t.Errorf("legibleText(pink) = %q, want light", got)
}
if got := legibleText("#F0E6BE"); got != "#100F0F" {
t.Errorf("legibleText(pale yellow) = %q, want dark", got)
}
}
func TestApplyThemeClearsBackgroundsAndH1(t *testing.T) {
cfg := styles.DarkStyleConfig
applyTheme(&cfg, Colors{Background: "#100F0F", Text: "#CECDC3", Heading: "#4385BE", Code: "#879A39", Link: "#3AA99F"})
// No element keeps glamour's dark code/table panel: every BackgroundColor is the bg.
var bad []string
var walk func(v reflect.Value, path string)
walk = func(v reflect.Value, path string) {
switch v.Kind() {
case reflect.Pointer:
if !v.IsNil() {
walk(v.Elem(), path)
}
case reflect.Struct:
tp := v.Type()
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
name := tp.Field(i).Name
if name == "BackgroundColor" && f.Type() == reflect.TypeOf((*string)(nil)) && !f.IsNil() {
if *f.Interface().(*string) != "#100F0F" && !strings.HasPrefix(path, ".H1") {
bad = append(bad, path)
}
continue
}
walk(f, path+"."+name)
}
}
}
walk(reflect.ValueOf(cfg), "")
if len(bad) > 0 {
t.Errorf("backgrounds not cleared to theme bg at: %v", bad)
}
// H1 is the heading-color bar with legible text.
if cfg.H1.BackgroundColor == nil || *cfg.H1.BackgroundColor != "#4385BE" {
t.Errorf("H1 background not the heading color")
}
if cfg.H1.Color == nil || *cfg.H1.Color != "#FFFCF0" {
t.Errorf("H1 text not legible color, got %v", cfg.H1.Color)
}
}
// Headings below H1 should sit directly above their body — no blank line.
func TestRenderNoBlankLineAfterSubheadings(t *testing.T) {
m := New("")
m.SetColors(Colors{Background: "#100F0F", Text: "#CECDC3", Heading: "#4385BE", Code: "#879A39", Link: "#3AA99F"})
m.SetSize(80, 40)
if err := m.Render("# Title\n\nintro\n\n## Section\n\nbody\n\n### Sub\n\nmore\n"); err != nil {
t.Fatal(err)
}
lines := stripANSI(m.vp.View())
idx := func(want string) int {
for i, ln := range lines {
if strings.TrimSpace(ln) == want {
return i
}
}
t.Fatalf("line %q not found in:\n%s", want, strings.Join(lines, "\n"))
return -1
}
// H2/H3 are immediately followed by their body — no intervening blank line.
if got := strings.TrimSpace(lines[idx("## Section")+1]); got != "body" {
t.Errorf("expected body directly after H2, got %q", got)
}
if got := strings.TrimSpace(lines[idx("### Sub")+1]); got != "more" {
t.Errorf("expected body directly after H3, got %q", got)
}
// H1 keeps its trailing blank line.
if got := strings.TrimSpace(lines[idx("Title")+1]); got != "" {
t.Errorf("expected blank line after H1, got %q", got)
}
}
var ansiRE = regexp.MustCompile("\x1b\\[[0-9;]*m")
func stripANSI(s string) []string {
return strings.Split(ansiRE.ReplaceAllString(s, ""), "\n")
}
|