internal/render/markdown/parser_test.go

package markdown

import (
	"strings"
	"testing"

	"github.com/yuin/goldmark/ast"
)

func TestParserParse(t *testing.T) {
	t.Parallel()
	cases := []struct {
		name  string
		input string
		want  int
	}{
		{"empty", "", 0},
		{"single_paragraph", "hello world", 1},
		{"two_paragraphs", "hello\n\nworld", 2},
		{"heading", "# title\n\nbody", 2},
		{"list", "- one\n- two\n- three", 1},
		{"fenced_code", "```go\nfmt.Println()\n```\n", 1},
	}
	p := NewParser()
	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			doc, err := p.Parse([]byte(tc.input))
			if err != nil {
				t.Fatalf("Parse returned error: %v", err)
			}
			if doc == nil {
				t.Fatal("Parse returned nil document")
			}
			got := doc.Root.ChildCount()
			if got != tc.want {
				t.Errorf("child count = %d, want %d", got, tc.want)
			}
		})
	}
}

func TestParserParseReader(t *testing.T) {
	t.Parallel()
	p := NewParser()
	doc, err := p.ParseReader(strings.NewReader("hello"))
	if err != nil {
		t.Fatalf("ParseReader: %v", err)
	}
	if doc.Root.ChildCount() != 1 {
		t.Errorf("expected 1 child, got %d", doc.Root.ChildCount())
	}
}

func TestParserWithOptions(t *testing.T) {
	t.Parallel()
	cases := []struct {
		name   string
		opts   []Option
		input  string
		find   string
	}{
		{"gfm_strikethrough", []Option{WithGFM()}, "~~gone~~", "gone"},
		{"footnotes", []Option{WithFootnotes()}, "note[^1]\n\n[^1]: defn", "defn"},
		{"auto_heading_id", []Option{WithAutoHeadingID()}, "# Hello World", "Hello World"},
	}
	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			p := NewParser(tc.opts...)
			doc, err := p.Parse([]byte(tc.input))
			if err != nil {
				t.Fatalf("Parse: %v", err)
			}
			var found bool
			_ = ast.Walk(doc.Root, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
				if !entering {
					return ast.WalkContinue, nil
				}
				if t, ok := n.(*ast.Text); ok {
					if strings.Contains(string(t.Segment.Value(doc.Source)), tc.find) {
						found = true
						return ast.WalkStop, nil
					}
				}
				return ast.WalkContinue, nil
			})
			if !found {
				t.Errorf("text %q not found in parsed tree", tc.find)
			}
		})
	}
}

func TestDocumentTextOf(t *testing.T) {
	t.Parallel()
	p := NewParser()
	doc, err := p.Parse([]byte("a *b* c"))
	if err != nil {
		t.Fatalf("Parse: %v", err)
	}
	var para ast.Node
	_ = ast.Walk(doc.Root, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
		if entering {
			if _, ok := n.(*ast.Paragraph); ok {
				para = n
				return ast.WalkStop, nil
			}
		}
		return ast.WalkContinue, nil
	})
	if para == nil {
		t.Fatal("no paragraph found")
	}
	got := doc.TextOf(para)
	if !strings.Contains(got, "a") {
		t.Errorf("TextOf = %q, expected to contain 'a'", got)
	}
}

func TestParseNilRootIsSafe(t *testing.T) {
	t.Parallel()
	t.Helper()
	p := NewParser()
	doc, err := p.Parse(nil)
	if err != nil {
		t.Fatalf("Parse(nil): %v", err)
	}
	if doc.Root == nil {
		t.Fatal("Root should never be nil")
	}
}