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.
109 lines
3.2 KiB
Go
109 lines
3.2 KiB
Go
package render
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"khosra/internal/content"
|
|
)
|
|
|
|
func TestChromeTextIsLocalisedAndNeverEmpty(t *testing.T) {
|
|
cases := []struct{ lang, key, want string }{
|
|
{"en", "newer", "Newer"},
|
|
{"bn", "newer", "নতুন"},
|
|
{"fr", "newer", "Newer"}, // unknown language falls back to the default locale
|
|
{"bn", "nope", "nope"}, // unknown phrase returns its key rather than nothing
|
|
}
|
|
for _, c := range cases {
|
|
if got := text(c.lang, c.key); got != c.want {
|
|
t.Errorf("text(%q, %q) = %q, want %q", c.lang, c.key, got, c.want)
|
|
}
|
|
}
|
|
if got := text("bn", "page-of", numerals("bn", 2), numerals("bn", 11)); got != "পৃষ্ঠা ২ / ১১" {
|
|
t.Errorf("page-of in Bengali = %q", got)
|
|
}
|
|
if got := text("en", "page-of", "2", "11"); got != "Page 2 of 11" {
|
|
t.Errorf("page-of in English = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestEveryPhraseCarriesEveryLanguage(t *testing.T) {
|
|
// A phrase missing a language falls back silently, so nothing else would catch a half-translated table.
|
|
for key, forms := range chrome {
|
|
for lang := range months {
|
|
if forms[lang] == "" {
|
|
t.Errorf("phrase %q has no %s form", key, lang)
|
|
}
|
|
}
|
|
}
|
|
for lang, names := range months {
|
|
if len(names) != 12 {
|
|
t.Errorf("%s has %d month names, want 12", lang, len(names))
|
|
}
|
|
}
|
|
if len(digits["bn"]) != 10 {
|
|
t.Errorf("Bengali digits = %d, want 10", len(digits["bn"]))
|
|
}
|
|
}
|
|
|
|
func TestDatesReadInTheirOwnScript(t *testing.T) {
|
|
when := time.Date(2026, time.March, 8, 0, 0, 0, 0, time.UTC)
|
|
if got := day(content.DefaultLang, when); got != "8 March 2026" {
|
|
t.Errorf("English date = %q", got)
|
|
}
|
|
if got := day("bn", when); got != "৮ মার্চ ২০২৬" {
|
|
t.Errorf("Bengali date = %q, want Bengali digits and month", got)
|
|
}
|
|
if got := day("en", time.Time{}); got != "" {
|
|
t.Errorf("an undated bundle should render nothing, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestTypographerSmoothsProseAndLeavesCodeAlone(t *testing.T) {
|
|
r, err := New(nil, content.Settings{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b, err := content.Parse("posts/x.md", []byte(
|
|
"---\ntitle: X\n---\nShe said \"wait\" -- then left... `\"raw\" -- code`\n"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out, err := r.Bundle(b, "en", nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := string(out)
|
|
for _, want := range []string{"“wait”", "–", "…"} {
|
|
if !strings.Contains(got, want) {
|
|
t.Errorf("prose missing %s:\n%s", want, got)
|
|
}
|
|
}
|
|
if !strings.Contains(got, `<code>"raw" -- code</code>`) {
|
|
t.Errorf("a code span must survive untouched:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestMachineReadableOutputStaysASCII(t *testing.T) {
|
|
r, err := New(nil, content.Settings{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
b, err := content.Parse("posts/x.bn.md", []byte("---\ntitle: এক\ndate: 2026-03-08\n---\n"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out, err := r.Listing("posts", "bn", []content.Bundle{b}, 1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := string(out)
|
|
if !strings.Contains(got, `datetime="2026-03-08"`) {
|
|
t.Errorf("datetime must stay ASCII for parsers (ADR-0034):\n%s", got)
|
|
}
|
|
if !strings.Contains(got, "৮ মার্চ ২০২৬") {
|
|
t.Errorf("the visible date should read in Bengali:\n%s", got)
|
|
}
|
|
}
|