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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
package main
import (
"os/exec"
"strings"
"testing"
)
// Heading anchors (headingAnchorTransformer): every heading gets a slug id and
// becomes a link to itself, except when it already contains a link.
func TestRenderMarkdownHeadingAnchors(t *testing.T) {
for _, tc := range []struct {
name string
md string
want []string
deny []string
}{
{
name: "simple heading links to itself",
md: "# Hello World\n\ntext\n",
want: []string{
`<h1 id="hello-world">`,
`<a href="#hello-world" class="permalink">Hello World</a>`,
},
},
{
name: "every level is anchored",
md: "# One\n\n## Two\n\n### Three\n",
want: []string{
`<h1 id="one"><a href="#one" class="permalink">One</a></h1>`,
`<h2 id="two"><a href="#two" class="permalink">Two</a></h2>`,
`<h3 id="three"><a href="#three" class="permalink">Three</a></h3>`,
},
},
{
name: "colliding headings get distinct ids",
md: "## Usage\n\na\n\n## Usage\n\nb\n",
want: []string{`id="usage"`, `id="usage-1"`},
},
{
name: "inline markup inside a heading is preserved",
md: "## The `foo` flag\n",
want: []string{
`<h2 id="the-foo-flag">`,
`<a href="#the-foo-flag" class="permalink">The <code>foo</code> flag</a>`,
},
},
{
// Wrapping would nest <a> inside <a>, which is invalid HTML. Such
// a heading still gets an id, though goldmark slugifies the raw
// source line, so the destination leaks into it — stock goldmark
// behaviour, and only ever for the rare heading holding a link.
name: "heading that already contains a link is left alone",
md: "## See [the docs](https://example.com)\n",
want: []string{`<h2 id="see-the-docshttpsexamplecom">`, `href="https://example.com"`},
deny: []string{`permalink`},
},
{
name: "heading that is entirely a link is left alone",
md: "## [Docs](https://example.com)\n",
deny: []string{`permalink`},
},
{
name: "autolink in a heading is left alone",
md: "## <https://example.com>\n",
deny: []string{`permalink`},
},
{
// The anchor must not disturb the relative-URL rewriting that
// relURLTransformer does for ordinary links.
name: "relative links still resolve against the file's directory",
md: "# Title\n\n[x](sub/file.md)\n",
want: []string{`href="/proj/dir/sub/file.md"`, `class="permalink"`},
},
} {
t.Run(tc.name, func(t *testing.T) {
got, err := renderMarkdown([]byte(tc.md), "proj", "dir/README.md")
if err != nil {
t.Fatalf("renderMarkdown: %v", err)
}
for _, w := range tc.want {
if !strings.Contains(got, w) {
t.Errorf("missing %q in:\n%s", w, got)
}
}
for _, d := range tc.deny {
if strings.Contains(got, d) {
t.Errorf("unexpected %q in:\n%s", d, got)
}
}
})
}
}
// The same anchoring must apply to manpages, which reach goldmark via mandoc's
// markdown backend rather than from a .md file.
func TestRenderManpageHeadingAnchors(t *testing.T) {
if _, err := exec.LookPath("mandoc"); err != nil {
t.Skip("mandoc not on PATH")
}
man := `.Dd January 1, 2026
.Dt TEST 1
.Os
.Sh NAME
.Nm test
.Nd a test page
.Sh IMPLEMENTATION NOTES
Some prose.
.Ss A Subsection
More prose.
`
got, err := renderManpage([]byte(man), "proj", "test.1")
if err != nil {
t.Fatalf("renderManpage: %v", err)
}
for _, w := range []string{
`<h1 id="name"><a href="#name" class="permalink">NAME</a></h1>`,
`<h1 id="implementation-notes"><a href="#implementation-notes" class="permalink">IMPLEMENTATION NOTES</a></h1>`,
`<h2 id="a-subsection"><a href="#a-subsection" class="permalink">A Subsection</a></h2>`,
} {
if !strings.Contains(got, w) {
t.Errorf("missing %q in:\n%s", w, got)
}
}
}
|