core/cmd/ninja/internal/bnp/mixed_test.go
Alex Dunmow 3ce6f9f4a0 feat(bnp): fold manifest.yaml into wasm builds for mixed-form artifacts (WO-WZ-021)
A wasm plugin.build now folds an optional root manifest.yaml into the
DESCRIBE-derived manifest.pb exactly as BuildCodeless does, so a reduced
"mixed" plugin can keep a minimal guest for genuine logic while shipping the
full declarative surface set host-rendered: theme_presets, bundled_fonts,
master_pages, system/page templates, template_overrides, email_wrappers, css,
required_icon_packs (and the referenced root JSON is embedded into manifest.pb).

applyManifestYAML now only overwrites a scalar/bytes key when manifest.yaml
actually declares it, so folding onto a guest-populated manifest supplements
and overrides but never WIPES a guest DESCRIBE field. A repo with no
manifest.yaml is a no-op — existing pure-wasm plugins build byte-for-byte as
before. Codeless builds are unaffected (empty manifest → identical result).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 19:50:34 +08:00

80 lines
2.9 KiB
Go

package bnp
import (
"testing"
abiv1 "git.dev.alexdunmow.com/block/core/abi/v1"
)
// TestApplyManifestYAMLSupplementsWithoutWiping proves the WO-WZ-021 mixed-form
// builder semantics: folding manifest.yaml onto a manifest that already carries
// guest DESCRIBE output SUPPLEMENTS the declarative surfaces and OVERRIDES only
// the keys it actually declares — an undeclared scalar key never wipes a
// guest-populated field.
func TestApplyManifestYAMLSupplementsWithoutWiping(t *testing.T) {
dir := t.TempDir()
writeFixture(t, dir, map[string]string{
"manifest.yaml": "" +
"bundled_fonts: fonts.json\n" +
"page_templates:\n" +
" - {system: blog, key: listing, title: Listing}\n" +
"email_wrappers: [newsletter]\n",
"fonts.json": `{"fonts":["Inter"]}`,
"templates/blog/listing.ninjatpl": `<main>{{ title }}</main>`,
"templates/email/newsletter.ninjatpl": `<table>{{ body|safe }}</table>`,
})
// Simulate a guest DESCRIBE result: theme_presets set by the guest, plus a
// guest-rendered page template the manifest.yaml does NOT list.
m := &abiv1.PluginManifest{
AbiVersion: 1,
Name: "fixture-mixed",
Version: "0.1.0",
ThemePresets: []byte(`{"guest":true}`),
PageTemplates: []*abiv1.PageTemplateMeta{
{SystemKey: "blog", Key: "guestonly", Title: "Guest Only"},
},
}
if err := applyManifestYAML(dir, m); err != nil {
t.Fatalf("applyManifestYAML: %v", err)
}
// Undeclared theme_presets must be PRESERVED, not wiped to nil.
if string(m.ThemePresets) != `{"guest":true}` {
t.Errorf("theme_presets wiped/altered: %q", string(m.ThemePresets))
}
// Declared bundled_fonts folded in.
if string(m.BundledFonts) != `{"fonts":["Inter"]}` {
t.Errorf("bundled_fonts = %q", string(m.BundledFonts))
}
// Declared page template appended alongside the guest one (2 total).
if len(m.PageTemplates) != 2 {
t.Fatalf("page templates = %d, want 2 (guest + declarative)", len(m.PageTemplates))
}
// Declared email wrapper folded in.
if len(m.EmailWrapperSystemKeys) != 1 || m.EmailWrapperSystemKeys[0] != "newsletter" {
t.Errorf("email wrappers = %v", m.EmailWrapperSystemKeys)
}
}
// TestWasmBuildFoldsManifestYAML end-to-end: a repo with Go source AND a
// manifest.yaml builds a (non-codeless) wasm artifact whose manifest.pb carries
// the folded declarative surfaces. The absence of a manifest.yaml is a no-op —
// covered by every existing wasm build test.
func TestApplyManifestYAMLNoFileIsNoop(t *testing.T) {
dir := t.TempDir() // no manifest.yaml
m := &abiv1.PluginManifest{
AbiVersion: 1,
Name: "fixture-pure",
Version: "0.1.0",
ThemePresets: []byte(`{"guest":true}`),
}
if err := applyManifestYAML(dir, m); err != nil {
t.Fatalf("applyManifestYAML (no file): %v", err)
}
if string(m.ThemePresets) != `{"guest":true}` {
t.Errorf("no-manifest.yaml build mutated the manifest: %q", string(m.ThemePresets))
}
}