internal/render/markdown/extensions/toc_test.go

package extensions

import (
	"strings"
	"testing"

	"github.com/yuin/goldmark"
	"github.com/yuin/goldmark/parser"
	"github.com/yuin/goldmark/text"
)

func parseForTest(t *testing.T, src string) (any, []byte) {
	t.Helper()
	gm := goldmark.New(goldmark.WithParserOptions(parser.WithAutoHeadingID()))
	b := []byte(src)
	return gm.Parser().Parse(text.NewReader(b)), b
}

func TestBuildTOCLevels(t *testing.T) {
	t.Parallel()
	cases := []struct {
		name     string
		src      string
		min, max int
		wantN    int
	}{
		{"empty", "", 1, 6, 0},
		{"single_h1", "# One", 1, 6, 1},
		{"flat_three", "# A\n\n# B\n\n# C", 1, 6, 3},
		{"min_level_skip", "# Skipped\n\n## Kept", 2, 6, 1},
		{"max_level_skip", "# Kept\n\n###### Skipped", 1, 2, 1},
	}
	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			root, src := parseForTest(t, tc.src)
			rootNode := root.(interface {
				// goldmark's ast.Document satisfies ast.Node; we use the
				// any return to avoid the cyclic import at the test seam.
			})
			_ = rootNode
			toc := BuildTOC(root.(interface{ Kind() }) .(interface{}).(interface{ Kind() }).(interface{}).(interface{ Kind() }))
			_ = toc
			_ = src
		})
	}
}

func TestSlugify(t *testing.T) {
	t.Parallel()
	cases := map[string]string{
		"Hello":          "hello",
		"  Trim me  ":    "trim-me",
		"A B C":          "a-b-c",
		"with--dashes":   "with-dashes",
		"punct!?.":       "punct",
	}
	for in, want := range cases {
		in, want := in, want
		t.Run(in, func(t *testing.T) {
			t.Parallel()
			got := slugify(in)
			if got != want {
				t.Errorf("slugify(%q) = %q, want %q", in, got, want)
			}
		})
	}
}

func TestTOCRenderString(t *testing.T) {
	t.Parallel()
	toc := &TOC{Root: TOCEntry{Children: []*TOCEntry{
		{Level: 1, Text: "Intro", ID: "intro"},
		{Level: 1, Text: "Body", ID: "body", Children: []*TOCEntry{
			{Level: 2, Text: "Sub", ID: "sub"},
		}},
	}}}
	got := toc.RenderString()
	for _, want := range []string{`href="#intro"`, `>Intro<`, `href="#body"`, `href="#sub"`} {
		if !strings.Contains(got, want) {
			t.Errorf("TOC output missing %q:\n%s", want, got)
		}
	}
}

func TestTOCFlatten(t *testing.T) {
	t.Parallel()
	toc := &TOC{Root: TOCEntry{Children: []*TOCEntry{
		{Level: 1, Text: "A"},
		{Level: 1, Text: "B", Children: []*TOCEntry{{Level: 2, Text: "B.1"}}},
	}}}
	flat := toc.Flatten()
	if len(flat) != 3 {
		t.Fatalf("flat len = %d, want 3", len(flat))
	}
	got := []string{flat[0].Text, flat[1].Text, flat[2].Text}
	want := []string{"A", "B", "B.1"}
	for i := range got {
		if got[i] != want[i] {
			t.Errorf("flat[%d] = %s, want %s", i, got[i], want[i])
		}
	}
}