Files
khosra/internal/render/render_test.go
T
Claude Opus 5andbdeshi 09b94d74f2 serve robots.txt and sitemap.xml
Two exact paths a crawler asks for by name, so they are mux entries rather than
resolver cases — no bundle can collide, since a key always sits under a section.

robots.txt at the site root is served verbatim, because a site that ships one has
said something deliberate; otherwise the engine emits the minimum that is true and
points at the sitemap. The sitemap lists every bundle in every language it exists
in, since each variant is separately reachable, with lastmod only where a bundle
has a date. Every URL comes from content.URL like every other path the engine
emits, so a sitemap cannot disagree with what is actually served.

Both need a declared base. Without one the sitemap answers 404 rather than listing
paths no crawler can resolve, and robots omits the Sitemap line rather than
writing a relative one.

write() was setting text/html for every caller, and headers only go out with the
first byte — so a handler setting its own type would have had it silently replaced,
which is how a sitemap gets served as a web page. It now splits into write and
writeAs, and the tests assert the content types rather than only the bodies.
2026-07-31 02:23:26 +06:00

164 lines
4.7 KiB
Go

package render
import (
"strings"
"testing"
"testing/fstest"
"khosra/internal/content"
)
func TestBundleRendersMarkdownIntoTheTheme(t *testing.T) {
r, err := New(nil, content.Settings{}, nil)
if err != nil {
t.Fatal(err)
}
b, err := content.Parse("posts/hello.md", []byte("---\ntitle: Hello\n---\n\n# Heading\n\nSome *prose*.\n"))
if err != nil {
t.Fatal(err)
}
out, err := r.Bundle(b, "en", []string{"en"}, nil)
if err != nil {
t.Fatal(err)
}
got := string(out)
for _, want := range []string{
"<!doctype html>", `<html lang="en">`, "<title>Hello</title>",
"<h1>Hello</h1>", "<em>prose</em>", "<style>",
} {
if !strings.Contains(got, want) {
t.Errorf("output missing %q\n---\n%s", want, got)
}
}
}
func TestBundleWithoutTitleFallsBackToKey(t *testing.T) {
r, err := New(nil, content.Settings{}, nil)
if err != nil {
t.Fatal(err)
}
b, err := content.Parse("status/note.md", []byte("just a note\n"))
if err != nil {
t.Fatal(err)
}
out, err := r.Bundle(b, "en", []string{"en"}, nil)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(out), "<title>status/note</title>") {
t.Errorf("a titleless bundle must still produce a title element:\n%s", out)
}
}
func TestSiteOverridesOneBlockAndInheritsTheRest(t *testing.T) {
siteFS := fstest.MapFS{
"templates/page.html": {Data: []byte(`{{define "main"}}<section class="mine">{{.Title}}</section>{{end}}`)},
}
r, err := New(siteFS, content.Settings{}, nil)
if err != nil {
t.Fatal(err)
}
b, err := content.Parse("pages/about.md", []byte("---\ntitle: About\n---\nbody\n"))
if err != nil {
t.Fatal(err)
}
out, err := r.Bundle(b, "en", []string{"en"}, nil)
if err != nil {
t.Fatal(err)
}
got := string(out)
if !strings.Contains(got, `<section class="mine">About</section>`) {
t.Errorf("the site override should win:\n%s", got)
}
if !strings.Contains(got, "<!doctype html>") || !strings.Contains(got, `<link rel="canonical"`) {
t.Errorf("the document should still come from the embedded base:\n%s", got)
}
if strings.Contains(got, "<article>") {
t.Error("the embedded main should have been replaced, not appended")
}
}
func TestAListingOverrideDoesNotLeakIntoBundlePages(t *testing.T) {
siteFS := fstest.MapFS{
"templates/list.html": {Data: []byte(`{{define "main"}}LISTING ONLY{{end}}`)},
}
r, err := New(siteFS, content.Settings{}, nil)
if err != nil {
t.Fatal(err)
}
b, err := content.Parse("pages/about.md", []byte("---\ntitle: About\n---\nbody\n"))
if err != nil {
t.Fatal(err)
}
out, err := r.Bundle(b, "en", nil, nil)
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(out), "LISTING ONLY") {
t.Error("a listing override must not reach bundle pages — that is why sets are per kind")
}
}
func TestSiteStylesheetReplacesTheReferenceOne(t *testing.T) {
siteFS := fstest.MapFS{"templates/theme.css": {Data: []byte("body{color:rebeccapurple}")}}
r, err := New(siteFS, content.Settings{}, nil)
if err != nil {
t.Fatal(err)
}
b, _ := content.Parse("pages/x.md", []byte("hi\n"))
out, err := r.Bundle(b, "en", nil, nil)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(out), "rebeccapurple") {
t.Error("the site stylesheet should replace the reference one")
}
}
func TestADeclaredBaseMakesMachineReadableURLsAbsolute(t *testing.T) {
// A canonical link and an hreflang are read by machines that resolve neither against the page, so both
// go absolute as soon as the site says where it lives (ADR-0039).
r, err := New(nil, content.Settings{Base: "https://khosra.example", Title: "Khosra"}, nil)
if err != nil {
t.Fatal(err)
}
b, err := content.Parse("posts/hello.md", []byte("---\ntitle: Hello\n---\nhi\n"))
if err != nil {
t.Fatal(err)
}
out, err := r.Bundle(b, "en", []string{"en", "bn"}, nil)
if err != nil {
t.Fatal(err)
}
got := string(out)
for _, want := range []string{
`rel="canonical" href="https://khosra.example/posts/hello/"`,
`hreflang="bn" href="https://khosra.example/bn/posts/hello/"`,
`property="og:url" content="https://khosra.example/posts/hello/"`,
`<title>Hello · Khosra</title>`,
} {
if !strings.Contains(got, want) {
t.Errorf("missing %q:\n%s", want, got)
}
}
}
func TestWithoutABaseEverythingStaysRelative(t *testing.T) {
r, err := New(nil, content.Settings{}, nil)
if err != nil {
t.Fatal(err)
}
b, _ := content.Parse("posts/hello.md", []byte("---\ntitle: Hello\n---\nhi\n"))
out, err := r.Bundle(b, "en", []string{"en"}, nil)
if err != nil {
t.Fatal(err)
}
got := string(out)
if !strings.Contains(got, `rel="canonical" href="/posts/hello/"`) {
t.Errorf("a site that declared no base still links to itself:\n%s", got)
}
if strings.Contains(got, "og:site_name") {
t.Error("no declared title means no site_name tag, rather than an empty one")
}
}