feat(11.1-01): publish every module README as an API reference page
- discover modules/<m> with non-test Go files; a missing README is a readme: problem - one GitHub-compatible slug parser.IDs for heading anchors, passed per page - rewrite links to .md pages and module READMEs to site .html and .md URLs - search-index.json gains one entry per H2 with 300-char plain text - add the api section to docs/site.yaml; docsite.Pages exposes reading order - tests: TestSlugIDs, TestReadmeIngestion, TestEveryModuleInSidebar, TestDocsAIOutputsInSync
This commit is contained in:
149
internal/docsite/docsite_test.go
Normal file
149
internal/docsite/docsite_test.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package docsite
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// writeTree writes files (path relative to root, forward slashes) under a
|
||||
// fresh temp dir and returns it.
|
||||
func writeTree(t *testing.T, files map[string]string) string {
|
||||
t.Helper()
|
||||
root := t.TempDir()
|
||||
for name, body := range files {
|
||||
p := filepath.Join(root, filepath.FromSlash(name))
|
||||
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(p, []byte(body), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
func problemLines(problems []Problem) []string {
|
||||
var out []string
|
||||
for _, p := range problems {
|
||||
out = append(out, p.String())
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const fixtureSite = `title: Acme
|
||||
description: Acme docs.
|
||||
sections:
|
||||
- name: setup
|
||||
title: Setup
|
||||
- name: api
|
||||
title: API reference
|
||||
`
|
||||
|
||||
func page(title, section string, order int, body string) string {
|
||||
return "---\ntitle: " + title + "\ndescription: " + title + " page.\nsection: " + section +
|
||||
"\norder: " + strconv.Itoa(order) + "\n---\n# " + title + "\n\n" + body
|
||||
}
|
||||
|
||||
func TestSlugIDs(t *testing.T) {
|
||||
ids := newSlugIDs()
|
||||
for _, tc := range []struct{ in, want string }{
|
||||
{"Install the CLI", "install-the-cli"},
|
||||
{"Install the CLI", "install-the-cli-1"},
|
||||
{"Install the CLI", "install-the-cli-2"},
|
||||
{"snake_case and dash-case", "snake_case-and-dash-case"},
|
||||
{"What's new? (v2.0)", "whats-new-v20"},
|
||||
{"`bonfire.Call` usage", "bonfirecall-usage"},
|
||||
{"Zażółć gęślą", "zażółć-gęślą"},
|
||||
{"!!!", "section"},
|
||||
} {
|
||||
if got := string(ids.Generate([]byte(tc.in), 0)); got != tc.want {
|
||||
t.Errorf("Generate(%q) = %q, want %q", tc.in, got, tc.want)
|
||||
}
|
||||
}
|
||||
ids.Put([]byte("custom"))
|
||||
if got := string(ids.Generate([]byte("Custom"), 0)); got != "custom-1" {
|
||||
t.Errorf("after Put, Generate(Custom) = %q, want custom-1", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadmeIngestion(t *testing.T) {
|
||||
files := map[string]string{
|
||||
"docs/site.yaml": fixtureSite,
|
||||
"docs/index.md": page("Acme docs", "index", 0, "Read [alpha](../modules/alpha/README.md#usage).\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 well.\n\n## Usage\n\n" +
|
||||
"See [delta](../delta/README.md#api) and [start](../../docs/setup/start.md).\n",
|
||||
"modules/beta/beta.go": "package beta\n",
|
||||
"modules/gamma/gamma_test.go": "package gamma\n",
|
||||
"modules/delta/delta.go": "package delta\n",
|
||||
"modules/delta/README.md": "# delta\n\nDelta does another thing.\n\n## API\n\nText.\n",
|
||||
}
|
||||
root := writeTree(t, files)
|
||||
|
||||
pages, problems, err := Pages(Options{Root: root})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := "modules/beta: readme: package has Go files but no README.md"
|
||||
if got := problemLines(problems); !slices.Equal(got, []string{want}) {
|
||||
t.Fatalf("problems = %q, want [%q]", got, want)
|
||||
}
|
||||
var urls []string
|
||||
for _, p := range pages {
|
||||
urls = append(urls, p.URL)
|
||||
}
|
||||
if !slices.Equal(urls, []string{"index", "setup/start", "api/alpha", "api/delta"}) {
|
||||
t.Fatalf("reading order = %v", urls)
|
||||
}
|
||||
alpha := pages[2]
|
||||
if alpha.Title != "alpha" || alpha.Description != "Alpha does one thing well." || alpha.Section != "api" ||
|
||||
alpha.Module != "alpha" || alpha.Source != "modules/alpha/README.md" {
|
||||
t.Fatalf("alpha page = %+v", alpha)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filepath.Join(root, "modules/beta/README.md"), []byte("# beta\n\nBeta.\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out := filepath.Join(t.TempDir(), "site")
|
||||
res, problems, err := Build(Options{Root: root, Out: out})
|
||||
if err != nil || len(problems) > 0 {
|
||||
t.Fatalf("Build: %v %q", err, problemLines(problems))
|
||||
}
|
||||
if res.Pages != 5 {
|
||||
t.Fatalf("pages = %d, want 5", res.Pages)
|
||||
}
|
||||
read := func(name string) string {
|
||||
t.Helper()
|
||||
b, err := os.ReadFile(filepath.Join(out, name))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
html := read("api/alpha.html")
|
||||
for _, want := range []string{`href="/api/delta.html#api"`, `href="/setup/start.html"`, `<h2 id="usage">Usage</h2>`} {
|
||||
if !strings.Contains(html, want) {
|
||||
t.Errorf("api/alpha.html missing %s", want)
|
||||
}
|
||||
}
|
||||
if strings.Contains(html, "<h1>alpha</h1>\n<p class=\"lead\">Alpha does one thing well.</p>\n<p>Alpha does") {
|
||||
t.Error("summary line rendered twice")
|
||||
}
|
||||
md := read("api/alpha.md")
|
||||
if !strings.HasPrefix(md, "# alpha\n\n> Alpha does one thing well.\n\n## Usage\n") ||
|
||||
!strings.Contains(md, "(/api/delta.md#api)") || !strings.Contains(md, "(/setup/start.md)") {
|
||||
t.Errorf("api/alpha.md = %q", md)
|
||||
}
|
||||
if idx := read("index.html"); !strings.Contains(idx, `href="/api/alpha.html#usage"`) {
|
||||
t.Error("index.html does not link the rewritten module README")
|
||||
}
|
||||
search := read("search-index.json")
|
||||
if !strings.Contains(search, `{"p":2,"a":"usage","h":"Usage","x":"See delta and start."}`) {
|
||||
t.Errorf("search index missing the alpha Usage entry: %s", search)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user