ZDDC/zddc/internal/convert/templatesync_test.go
ZDDC 1d816ae43a feat(server): multi-template MD→HTML with .zddc.d/templates cascade
The convert engine renders markdown→HTML/PDF through named doctype templates
selected by the document's `template:` front matter, with per-project/per-party
overrides.

convert package:
- embed.go now embeds the whole templates/ dir (all: prefix so _-prefixed
  partials are included) as an embed.FS; drop the single viewer-template.html +
  custom.css embeds. New TemplateSet type + DefaultTemplateSet(name) returning the
  chosen doctype + its partials.
- ToHTML/ToPDF take a TemplateSet; writeTemplateSetToScratch materialises the
  template + partials flat into the per-call scratch dir (pandoc resolves
  $partial()$ from the template's own directory).

handler:
- converttemplate.go: templateNameFromFrontMatter (YAML front-matter scan,
  sanitized to a bare basename) + resolveTemplateSet, which overlays
  <level>/.zddc.d/templates/<name>.html overrides onto the embedded defaults,
  walking docDir→fsRoot so a party dir beats the project-global dir. An override
  may replace a doctype, a partial, or add a brand-new doctype.
- buildAndStore threads fsRoot + source into the html/pdf paths.

build: pandoc/templates/ is the single source of truth; shared/build-lib.sh
sync_pandoc_templates mirrors it into the embed dir on every build (cmp-guarded,
stale-pruning). convert.TestEmbeddedTemplatesMatchSource fails on drift.

Tests: drift + DefaultTemplateSet (convert); front-matter parse + cascade
override precedence (handler). Full ./... suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 14:18:40 -05:00

71 lines
2.5 KiB
Go

package convert
import (
"os"
"path/filepath"
"testing"
)
// canonicalTemplatesDir is the single source of truth for the conversion
// templates: /pandoc/templates/, relative to this package
// (zddc/internal/convert → ../../../pandoc/templates).
const canonicalTemplatesDir = "../../../pandoc/templates"
// TestEmbeddedTemplatesMatchSource guards against drift between the embedded
// templates/ (a build artifact, synced by shared/build-lib.sh:
// sync_pandoc_templates) and the canonical pandoc/templates/. If this fails,
// re-run ./build (or copy pandoc/templates/* into this package's templates/).
func TestEmbeddedTemplatesMatchSource(t *testing.T) {
srcEntries, err := os.ReadDir(canonicalTemplatesDir)
if err != nil {
t.Fatalf("read canonical templates dir %q: %v", canonicalTemplatesDir, err)
}
embedded := embeddedTemplateFiles()
srcCount := 0
for _, e := range srcEntries {
if e.IsDir() || filepath.Ext(e.Name()) != ".html" {
continue
}
srcCount++
want, err := os.ReadFile(filepath.Join(canonicalTemplatesDir, e.Name()))
if err != nil {
t.Fatalf("read %s: %v", e.Name(), err)
}
got, ok := embedded[e.Name()]
if !ok {
t.Errorf("embedded templates/ is missing %s (run ./build to sync)", e.Name())
continue
}
if string(got) != string(want) {
t.Errorf("embedded %s differs from pandoc/templates/%s (run ./build to sync)", e.Name(), e.Name())
}
}
if srcCount != len(embedded) {
t.Errorf("template count mismatch: canonical=%d embedded=%d (stale file in one tree?)", srcCount, len(embedded))
}
}
// TestDefaultTemplateSet checks the doctype fallback + that partials ride along.
func TestDefaultTemplateSet(t *testing.T) {
for _, name := range EmbeddedTemplateNames() {
ts := DefaultTemplateSet(name)
if ts.Name != name+".html" {
t.Errorf("DefaultTemplateSet(%q).Name = %q, want %q.html", name, ts.Name, name)
}
if ts.Files[ts.Name] == nil {
t.Errorf("DefaultTemplateSet(%q) Files missing primary %q", name, ts.Name)
}
if ts.Files["_head.html"] == nil {
t.Errorf("DefaultTemplateSet(%q) Files missing _head.html partial", name)
}
}
// Unknown / empty fall back to the default doctype.
if ts := DefaultTemplateSet("nope"); ts.Name != DefaultTemplateName+".html" {
t.Errorf("unknown doctype fell back to %q, want %q.html", ts.Name, DefaultTemplateName)
}
if ts := DefaultTemplateSet(""); ts.Name != DefaultTemplateName+".html" {
t.Errorf("empty doctype fell back to %q, want %q.html", ts.Name, DefaultTemplateName)
}
}