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"`, `

Usage

`} { if !strings.Contains(html, want) { t.Errorf("api/alpha.html missing %s", want) } } if strings.Contains(html, "

alpha

\n

Alpha does one thing well.

\n

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) } }