package docsite
import (
"io"
"os"
"slices"
"strings"
"testing"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/text"
)
func TestSlugIDsEdgeCases(t *testing.T) {
for _, tc := range []struct{ in, want string }{
{"Hello 👋 world", "hello--world"},
{" Trimmed ", "trimmed"},
{"Ünïcödé Straße", "ünïcödé-straße"},
{"snake_case_name", "snake_case_name"},
{"a.b/c:d", "abcd"},
{"v1.2 → v2", "v12--v2"},
{"", "section"},
{"🎉", "section"},
} {
if got := string(newSlugIDs().Generate([]byte(tc.in), ast.KindHeading)); got != tc.want {
t.Errorf("Generate(%q) = %q, want %q", tc.in, got, tc.want)
}
}
ids := newSlugIDs()
for i, want := range []string{"section", "section-1", "section-2"} {
if got := string(ids.Generate(nil, ast.KindHeading)); got != want {
t.Errorf("empty heading %d = %q, want %q", i, got, want)
}
}
}
// renderFixture loads a two-section fixture site and returns it with its
// pages by URL, without running the checkers.
func renderFixture(t *testing.T, files map[string]string) (*site, map[string]*Page) {
t.Helper()
tree := map[string]string{
"docs/site.yaml": fixtureSite,
"docs/index.md": page("Acme docs", "index", 0, "Text.\n"),
"docs/setup/start.md": page("Start", "setup", 10, "## First steps\n\nText.\n"),
"modules/alpha/alpha.go": "package alpha\n",
"modules/alpha/README.md": "# alpha\n\nAlpha does one thing.\n\n## Usage\n\n" +
"See [beta](../beta/README.md#api), [start](../../docs/setup/start.md) and [source](alpha.go).\n",
"modules/beta/beta.go": "package beta\n",
"modules/beta/README.md": "# beta\n\nBeta does another.\n\n## API\n\nText.\n",
}
for k, v := range files {
tree[k] = v
}
s, problems, err := load(Options{Root: writeTree(t, tree)})
if err != nil || len(problems) > 0 {
t.Fatalf("load: %v %q", err, problemLines(problems))
}
byURL := map[string]*Page{}
for _, p := range s.pages {
byURL[p.URL] = p
}
return s, byURL
}
func renderHTML(t *testing.T, s *site, p *Page) renderedPage {
t.Helper()
r, err := s.renderPage(newMarkdown(), p)
if err != nil {
t.Fatal(err)
}
return r
}
func TestLinkRewriting(t *testing.T) {
body := strings.Join([]string{
"- [start](setup/start.md)",
"- [steps](setup/start.md#first-steps)",
"- [alpha](../modules/alpha/README.md)",
"- [usage](../modules/alpha/README.md#usage)",
"- [web](https://example.com/x.md)",
"- [mail](mailto:a@example.com)",
"- [here](#local)",
"- [abs](/setup/start.md)",
"- [file](../go.mod)",
"- [gone](setup/gone.md)",
"",
}, "\n")
s, pages := renderFixture(t, map[string]string{"docs/index.md": page("Acme docs", "index", 0, body)})
r := renderHTML(t, s, pages["index"])
html := string(r.html)
for _, want := range []string{
`href="/setup/start.html">start`,
`href="/setup/start.html#first-steps">steps`,
`href="/api/alpha.html">alpha`,
`href="/api/alpha.html#usage">usage`,
`href="https://example.com/x.md">web`,
`href="mailto:a@example.com">mail`,
`href="#local">here`,
`href="/setup/start.md">abs`,
`href="../go.mod">file`,
`href="setup/gone.md">gone`,
} {
if !strings.Contains(html, want) {
t.Errorf("index HTML missing %s\n%s", want, html)
}
}
wantMD := map[string]string{
"setup/start.md": "/setup/start.md",
"setup/start.md#first-steps": "/setup/start.md#first-steps",
"../modules/alpha/README.md": "/api/alpha.md",
"../modules/alpha/README.md#usage": "/api/alpha.md#usage",
}
if len(r.mdLinks) != len(wantMD) {
t.Errorf("mdLinks = %v, want %v", r.mdLinks, wantMD)
}
for k, v := range wantMD {
if r.mdLinks[k] != v {
t.Errorf("mdLinks[%s] = %q, want %q", k, r.mdLinks[k], v)
}
}
// README to README, README to guide, and a README's source-file link.
alpha := string(renderHTML(t, s, pages["api/alpha"]).html)
for _, want := range []string{`href="/api/beta.html#api"`, `href="/setup/start.html"`, `href="alpha.go"`} {
if !strings.Contains(alpha, want) {
t.Errorf("alpha HTML missing %s", want)
}
}
// A base URL prefixes every rewritten link.
s.base = "https://docs.example/v1"
if html := string(renderHTML(t, s, pages["index"]).html); !strings.Contains(html, `href="https://docs.example/v1/setup/start.html"`) {
t.Errorf("base URL not applied:\n%s", html)
}
}
func TestH1StripAndHeadingAnchors(t *testing.T) {
s, pages := renderFixture(t, map[string]string{
"docs/setup/start.md": page("Start", "setup", 10, "## Two\n\n### Three\n\n#### Four\n\n## Two\n"),
})
r := renderHTML(t, s, pages["setup/start"])
html := string(r.html)
if strings.Contains(html, "
Two#`,
`Three#
`,
`Four
`,
`
`) {
t.Errorf("a non-leading H1 was stripped:\n%s", html)
}
}
func TestCalloutRendering(t *testing.T) {
body := "> [!NOTE]\n> A note.\n\n> [!TIP]\n> A tip with `code`.\n\n> [!WARNING]\n> Careful.\n>\n> Second paragraph.\n\n" +
"> [!NOTE]\n\n> [!note]\n> Lowercase stays a quote.\n\n> Plain quote.\n\n> [!NOTE] inline text\n"
s, pages := renderFixture(t, map[string]string{"docs/setup/start.md": page("Start", "setup", 10, body)})
html := string(renderHTML(t, s, pages["setup/start"]).html)
for _, want := range []string{
`