Files
khosra/internal/render/chrome_test.go
T
Claude Opus 5andbdeshi c16bf4bd9d add shortcodes as the first internal/ext feature
A call is `{{< name key="value" >}}` alone on a line, parsed by a goldmark block
parser into an AST node and rendered by executing a theme template of that name
(ADR-0036). `figure` ships; `include` and `gallery` need the including bundle's
directory, which the parser does not carry yet, so they wait.

The layering did the design work here. internal/render may not import
internal/ext, so render.New takes a callback that receives a Partial and returns
Markdown extensions, and cmd/khosra/wire.go holds the only list of enabled
features. Empty that list and the engine still builds and serves — which is the
property extensions.md says the contract should have.

Raw HTML stays disabled. An author's text reaches a page only as arguments that
html/template escapes in context, which the real binary shows: a hostile alt
becomes &lt;script&gt; and src="javascript:…" becomes #ZgotmplZ. Getting
contextual escaping from the standard library rather than writing it is the whole
reason a fragment renders this instead of the feature.

parseSet became variadic so the fragment set reuses it rather than growing a
second copy of the overlay logic; `Partial` takes map[string]string after the
advisory correctly flagged `any` as generality nothing had asked for.
2026-07-30 10:29:00 +06:00

109 lines
3.1 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, 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{"&ldquo;wait&rdquo;", "&ndash;", "&hellip;"} {
if !strings.Contains(got, want) {
t.Errorf("prose missing %s:\n%s", want, got)
}
}
if !strings.Contains(got, `<code>&quot;raw&quot; -- code</code>`) {
t.Errorf("a code span must survive untouched:\n%s", got)
}
}
func TestMachineReadableOutputStaysASCII(t *testing.T) {
r, err := New(nil, 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)
}
}