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

`, `

TwoLater

`) { 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{ `