internal/site/render_test.go
Ref: Size: 6.3 KiB History
package site
import (
"strings"
"testing"
)
var testTargets = map[string]string{
"README.md": "/docs/",
"ssh-access.md": "/docs/ssh-access/",
"../ROADMAP.md": "/docs/roadmap/",
"shape.html": "/docs/shape.html",
}
func TestRenderRewritesInternalLinks(t *testing.T) {
got, _, err := render([]byte("see [ssh](ssh-access.md) and [index](README.md)"), testTargets)
if err != nil {
t.Fatal(err)
}
html := string(got)
for _, want := range []string{`href="/docs/ssh-access/"`, `href="/docs/"`} {
if !strings.Contains(html, want) {
t.Errorf("output missing %s:\n%s", want, html)
}
}
}
func TestRenderPreservesFragments(t *testing.T) {
got, _, err := render([]byte("[a](ssh-access.md#gate)"), testTargets)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(got), `href="/docs/ssh-access/#gate"`) {
t.Errorf("fragment lost:\n%s", got)
}
}
func TestRenderLeavesExternalAndAbsoluteAlone(t *testing.T) {
src := "[x](https://eitri.sh) [y](/dl/) [z](#local)"
got, _, err := render([]byte(src), testTargets)
if err != nil {
t.Fatal(err)
}
html := string(got)
for _, want := range []string{`href="https://eitri.sh"`, `href="/dl/"`, `href="#local"`} {
if !strings.Contains(html, want) {
t.Errorf("output missing %s:\n%s", want, html)
}
}
}
func TestRenderFailsOnUnknownInternalLink(t *testing.T) {
_, _, err := render([]byte("[gone](no-such-doc.md)"), testTargets)
if err == nil || !strings.Contains(err.Error(), "no-such-doc.md") {
t.Fatalf("want broken-link error naming the target, got %v", err)
}
}
func TestRenderTables(t *testing.T) {
got, _, err := render([]byte("| a | b |\n|---|---|\n| 1 | 2 |"), testTargets)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(got), "<table>") {
t.Errorf("GFM tables not rendered:\n%s", got)
}
}
func TestRenderRewritesParentRelativeLinks(t *testing.T) {
got, _, err := render([]byte("[r](../ROADMAP.md)"), testTargets)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(got), `href="/docs/roadmap/"`) {
t.Errorf("parent-relative link not rewritten:\n%s", got)
}
}
func TestRenderNormalizesDotSlashPrefix(t *testing.T) {
got, _, err := render([]byte("[s](./ssh-access.md)"), testTargets)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(got), `href="/docs/ssh-access/"`) {
t.Errorf("./-prefixed link not rewritten:\n%s", got)
}
}
func TestRenderRewritesImageDestinations(t *testing.T) {
got, _, err := render([]byte(""), testTargets)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(got), `src="/docs/shape.html"`) {
t.Errorf("image destination not rewritten:\n%s", got)
}
}
func TestSummaryTakesTheOpeningParagraph(t *testing.T) {
md := "# quickstart\n\nZero to a first VM on one host, then a second.\n\nA later paragraph.\n"
if got, want := summary([]byte(md)), "Zero to a first VM on one host, then a second."; got != want {
t.Errorf("summary = %q, want %q", got, want)
}
}
func TestSummaryStripsMarkupAndJoinsLines(t *testing.T) {
md := "# t\n\n*The ease* of a [cloud provider](https://x),\nbacked by `what` you own\n"
if got, want := summary([]byte(md)), "The ease of a cloud provider, backed by what you own"; got != want {
t.Errorf("summary = %q, want %q", got, want)
}
}
func TestSummarySkipsNonProseOpeners(t *testing.T) {
md := "# t\n\n- a list item\n\n```\ncode\n```\n\nThe real opening line.\n"
if got, want := summary([]byte(md)), "The real opening line."; got != want {
t.Errorf("summary = %q, want %q", got, want)
}
}
// A description longer than the crawlers show is cut at a word boundary, so
// no preview ends mid-word.
func TestSummaryClampsAtAWordBoundary(t *testing.T) {
long := strings.Repeat("alpha bravo charlie delta echo ", 20)
got := summary([]byte("# t\n\n" + long))
if len([]rune(got)) > summaryLimit+1 {
t.Errorf("summary is %d runes, want at most %d plus an ellipsis", len([]rune(got)), summaryLimit)
}
if !strings.HasSuffix(got, "…") {
t.Errorf("clamped summary should end in an ellipsis: %q", got)
}
if strings.Contains(got, " ") || strings.HasSuffix(strings.TrimSuffix(got, "…"), " ") {
t.Errorf("clamped summary has ragged whitespace: %q", got)
}
}
func TestSummaryOfProselessDocIsEmpty(t *testing.T) {
if got := summary([]byte("# just a heading\n")); got != "" {
t.Errorf("summary = %q, want empty", got)
}
}
// A long, many-sectioned page gets a contents list in front of its first
// section — inside the content, because the page's own title is in there too.
func TestWithTOCListsSectionsOfALongPage(t *testing.T) {
var md strings.Builder
md.WriteString("# page\n\nopening prose\n")
for _, name := range []string{"Join a Linux host", "Join a Mac", "Sharp edges", "Related"} {
md.WriteString("\n## " + name + "\n\n" + strings.Repeat("filler prose. ", 200) + "\n")
}
html, secs, err := render([]byte(md.String()), testTargets)
if err != nil {
t.Fatal(err)
}
if len(secs) != 4 {
t.Fatalf("found %d sections, want 4: %v", len(secs), secs)
}
out := string(withTOC(html, secs))
toc := strings.Index(out, `<nav class="toc">`)
if toc < 0 {
t.Fatalf("no table of contents:\n%s", out[:400])
}
if h2 := strings.Index(out, "<h2"); toc > h2 {
t.Errorf("contents list sits after the first section (%d > %d)", toc, h2)
}
if h1 := strings.Index(out, "<h1"); h1 > toc {
t.Error("contents list sits above the page title")
}
for _, want := range []string{`href="#join-a-linux-host"`, `href="#join-a-mac"`, "Sharp edges"} {
if !strings.Contains(out, want) {
t.Errorf("contents list missing %s:\n%s", want, out[toc:toc+400])
}
}
}
func TestWithTOCLeavesShortPagesAlone(t *testing.T) {
md := "# page\n\n## one\n\nprose\n\n## two\n\nprose\n\n## three\n\nprose\n\n## four\n\nprose\n"
html, secs, err := render([]byte(md), testTargets)
if err != nil {
t.Fatal(err)
}
if got := string(withTOC(html, secs)); strings.Contains(got, "toc") {
t.Errorf("short page got a contents list:\n%s", got)
}
}
// Sections are what the reader can jump to, so a heading with markup in it
// still has to produce plain link text.
func TestSectionsFlattenHeadingMarkup(t *testing.T) {
_, secs, err := render([]byte("# p\n\n## The `eitri` *client*\n\nprose\n"), testTargets)
if err != nil {
t.Fatal(err)
}
if len(secs) != 1 || secs[0].Title != "The eitri client" {
t.Errorf("sections = %v, want one titled \"The eitri client\"", secs)
}
}