okami.mom/glamour

git clone https://git.nkp.pet/okami.mom/glamour

nekomimi.pettake code block text from Segments.Value and hang list items through IndentWriter052c391

main
3.5 KiB120 linesraw
1package glamour
2
3import (
4	"regexp"
5	"strings"
6	"testing"
7
8	"charm.land/glamour/v2/ansi"
9	"charm.land/glamour/v2/styles"
10	"github.com/yuin/goldmark"
11	"github.com/yuin/goldmark/ast"
12	"github.com/yuin/goldmark/parser"
13	"github.com/yuin/goldmark/text"
14	"github.com/yuin/goldmark/util"
15)
16
17var kindShout = ast.NewNodeKind("Shout")
18
19// shout is %word%, a custom inline node for the extension tests.
20type shout struct {
21	ast.BaseInline
22	word string
23}
24
25func (n *shout) Kind() ast.NodeKind            { return kindShout }
26func (n *shout) Dump(source []byte, level int) { ast.DumpHelper(n, source, level, nil, nil) }
27
28// shoutExtension parses %word% into a shout, or into an ast.String when
29// asString: a kind glamour registers but has no case for.
30type shoutExtension struct{ asString bool }
31
32func (e shoutExtension) Extend(m goldmark.Markdown) {
33	m.Parser().AddOptions(parser.WithInlineParsers(util.Prioritized(shoutParser(e), 500)))
34}
35
36type shoutParser struct{ asString bool }
37
38func (shoutParser) Trigger() []byte { return []byte{'%'} }
39
40func (p shoutParser) Parse(_ ast.Node, block text.Reader, _ parser.Context) ast.Node {
41	line, _ := block.PeekLine()
42	end := strings.IndexByte(string(line[1:]), '%')
43	if end < 1 {
44		return nil
45	}
46	block.Advance(end + 2)
47	if p.asString {
48		return ast.NewString(line[1 : end+1])
49	}
50	return &shout{word: string(line[1 : end+1])}
51}
52
53func renderShout(t *testing.T, in string, extension shoutExtension, element ansi.ElementFunc) string {
54	t.Helper()
55	options := []TermRendererOption{
56		WithStyles(styles.NoTTYStyleConfig), WithWordWrap(40), WithExtensions(extension),
57	}
58	if element != nil {
59		options = append(options, WithElement(kindShout, element))
60	}
61	r, err := NewTermRenderer(options...)
62	if err != nil {
63		t.Fatal(err)
64	}
65	out, err := r.Render(in)
66	if err != nil {
67		t.Fatal(err)
68	}
69	return strings.TrimSpace(out)
70}
71
72func TestWithElementRendersExtensionNodes(t *testing.T) {
73	upper := func(node ast.Node, _ []byte, _ ansi.RenderContext) ansi.Element {
74		return ansi.Element{Renderer: &ansi.BaseElement{Token: strings.ToUpper(node.(*shout).word), Verbatim: true}}
75	}
76	for in, want := range map[string]string{
77		"say %hi% now": "say HI now",
78		"say *%hi%*":   "say *HI*",
79		`%a\[b%`:       `A\[B`,
80	} {
81		if got := renderShout(t, in, shoutExtension{}, upper); got != want {
82			t.Errorf("%q rendered %q, want %q", in, got, want)
83		}
84	}
85}
86
87func TestElementsSeeTheBlockWidth(t *testing.T) {
88	var widths []int
89	width := func(_ ast.Node, _ []byte, ctx ansi.RenderContext) ansi.Element {
90		widths = append(widths, ctx.Width())
91		return ansi.Element{}
92	}
93	renderShout(t, "%a%\n\n> %b%", shoutExtension{}, width)
94	if len(widths) < 2 || widths[0] <= widths[len(widths)-1] {
95		t.Errorf("widths %v: a quote should leave less room than the document", widths)
96	}
97}
98
99func TestUnhandledKindsRenderNothing(t *testing.T) {
100	if got := renderShout(t, "say %hi% now", shoutExtension{asString: true}, nil); got != "say  now" {
101		t.Errorf("rendered %q, want the unknown node dropped", got)
102	}
103}
104
105var trailingSpace = regexp.MustCompile(`(?m) +$`)
106
107func TestListItemsHangTheirRows(t *testing.T) {
108	r, err := NewTermRenderer(WithStyles(styles.NoTTYStyleConfig), WithWordWrap(24))
109	if err != nil {
110		t.Fatal(err)
111	}
112	out, err := r.Render("- one two three four five six\n\n  second paragraph\n\n  ```\n  code\n  ```\n- next")
113	if err != nil {
114		t.Fatal(err)
115	}
116	want := "  • one two three four\n    five six\n\n    second paragraph\n\n      code\n  • next"
117	if got := strings.Trim(trailingSpace.ReplaceAllString(out, ""), "\n"); got != want {
118		t.Errorf("rendered\n%s\nwant\n%s", got, want)
119	}
120}