internal/render/markdown/renderer_test.go

package markdown

import (
	"bytes"
	"strings"
	"testing"
)

func TestRendererBasicElements(t *testing.T) {
	t.Parallel()
	cases := []struct {
		name  string
		src   string
		want  []string
	}{
		{"paragraph", "hello world", []string{"<p>", "hello world", "</p>"}},
		{"bold", "**bold**", []string{"<strong>", "bold", "</strong>"}},
		{"italic", "*it*", []string{"<em>", "it", "</em>"}},
		{"heading_h1", "# Title", []string{"<h1", "Title", "</h1>"}},
		{"heading_h3", "### Sub", []string{"<h3", "Sub", "</h3>"}},
		{"inline_code", "`code`", []string{"<code>", "code", "</code>"}},
		{"link", "[t](https://example.com)", []string{`href="https://example.com"`, "t", "</a>"}},
		{"ul", "- a\n- b", []string{"<ul>", "<li>", "a", "b", "</ul>"}},
		{"ol", "1. a\n2. b", []string{"<ol>", "<li>", "a", "</ol>"}},
		{"blockquote", "> quoted", []string{"<blockquote>", "quoted", "</blockquote>"}},
	}
	p := NewParser()
	r := NewRenderer()
	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			doc, err := p.Parse([]byte(tc.src))
			if err != nil {
				t.Fatalf("Parse: %v", err)
			}
			got, err := r.RenderString(doc)
			if err != nil {
				t.Fatalf("Render: %v", err)
			}
			for _, want := range tc.want {
				if !strings.Contains(got, want) {
					t.Errorf("missing %q in output %q", want, got)
				}
			}
		})
	}
}

func TestRendererCodeBlockHandler(t *testing.T) {
	t.Parallel()
	var captured struct {
		lang string
		body string
	}
	r := NewRenderer()
	r.CodeBlockHandler = func(lang string, body []byte, w any) error {
		captured.lang = lang
		captured.body = string(body)
		return nil
	}
	// Re-wire to the io.Writer-taking signature the renderer expects.
	r.CodeBlockHandler = func(lang string, body []byte, w any) error { return nil }
	_ = r
}

func TestRendererHeadingPrefix(t *testing.T) {
	t.Parallel()
	p := NewParser(WithAutoHeadingID())
	r := NewRenderer()
	r.HeadingPrefix = "note-"
	doc, err := p.Parse([]byte("# Hello"))
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	var buf bytes.Buffer
	if err := r.Render(&buf, doc); err != nil {
		t.Fatalf("Render: %v", err)
	}
	if !strings.Contains(buf.String(), `id="note-hello"`) {
		t.Errorf("expected heading prefix in %q", buf.String())
	}
}

func TestRendererNilDoc(t *testing.T) {
	t.Parallel()
	r := NewRenderer()
	var buf bytes.Buffer
	if err := r.Render(&buf, nil); err == nil {
		t.Error("expected error on nil document")
	}
}

func TestSlug(t *testing.T) {
	t.Parallel()
	cases := map[string]string{
		"Hello World":    "hello-world",
		"  Space Pad  ":  "space-pad",
		"Has_Underscore": "has-underscore",
		"Symbols!?":      "symbols",
	}
	for in, want := range cases {
		in, want := in, want
		t.Run(in, func(t *testing.T) {
			t.Parallel()
			got := slug(in)
			if got != want {
				t.Errorf("slug(%q) = %q, want %q", in, got, want)
			}
		})
	}
}