cli/internal/bnp/screenshots_test.go
Alex Dunmow e8aba45f21 feat(bnp): pack preview.png + screenshots/ into artifacts; multi-page capture
The .bnp packers never carried preview.png (the .so-era source archives did
implicitly), so any republish silently wiped a theme's registry preview.
Both builders now pack an optional root preview.png and a screenshots/ dir
(png/jpg/jpeg/webp, <=8MB each, <=12 files, validated), reported in the
build/verify summaries. ninja theme screenshot gains --pages/--screenshots-dir
to capture NN-<slug>.png per gallery page for the registry gallery.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 08:21:35 +08:00

153 lines
4.3 KiB
Go

package bnp
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
)
// presentationFixture extends the codeless fixture with a root preview.png
// and a screenshots/ dir.
func presentationFixture(t *testing.T) string {
t.Helper()
dir := codelessFixture(t)
writeFixture(t, dir, map[string]string{
"preview.png": "not-really-png-but-bytes",
"screenshots/01-home.png": "home-bytes",
"screenshots/02-blog.jpg": "blog-bytes",
"screenshots/03-page.webp": "page-bytes",
})
return dir
}
func TestCodelessBuildPacksPresentationMembers(t *testing.T) {
dir := presentationFixture(t)
out := filepath.Join(t.TempDir(), "fixture-theme-0.1.0.bnp")
res, err := BuildCodeless(context.Background(), BuildOptions{Dir: dir, Output: out})
if err != nil {
t.Fatalf("BuildCodeless: %v", err)
}
if !res.PreviewIncluded || res.ScreenshotCount != 3 {
t.Errorf("result preview=%t screenshots=%d; want true/3", res.PreviewIncluded, res.ScreenshotCount)
}
found := false
for _, d := range res.IncludedDirs {
if d == dirScreenshots {
found = true
}
}
if !found {
t.Errorf("IncludedDirs %v missing %q", res.IncludedDirs, dirScreenshots)
}
v, err := Verify(out)
if err != nil {
t.Fatalf("Verify: %v", err)
}
if !v.HasPreview || v.ScreenshotCount != 3 {
t.Errorf("verify preview=%t screenshots=%d; want true/3", v.HasPreview, v.ScreenshotCount)
}
}
func TestCodelessBuildWithoutPresentationMembers(t *testing.T) {
dir := codelessFixture(t)
out := filepath.Join(t.TempDir(), "fixture-theme-0.1.0.bnp")
res, err := BuildCodeless(context.Background(), BuildOptions{Dir: dir, Output: out})
if err != nil {
t.Fatalf("BuildCodeless: %v", err)
}
if res.PreviewIncluded || res.ScreenshotCount != 0 {
t.Errorf("result preview=%t screenshots=%d; want false/0", res.PreviewIncluded, res.ScreenshotCount)
}
v, err := Verify(out)
if err != nil {
t.Fatalf("Verify: %v", err)
}
if v.HasPreview || v.ScreenshotCount != 0 {
t.Errorf("verify preview=%t screenshots=%d; want false/0", v.HasPreview, v.ScreenshotCount)
}
}
func TestValidateScreenshotsDirRejections(t *testing.T) {
build := func(t *testing.T, mutate func(dir string)) error {
t.Helper()
dir := codelessFixture(t)
mutate(dir)
out := filepath.Join(t.TempDir(), "out.bnp")
_, err := BuildCodeless(context.Background(), BuildOptions{Dir: dir, Output: out})
return err
}
t.Run("bad extension", func(t *testing.T) {
err := build(t, func(dir string) {
writeFixture(t, dir, map[string]string{"screenshots/readme.txt": "nope"})
})
if err == nil || !strings.Contains(err.Error(), "unsupported image type") {
t.Fatalf("err = %v; want unsupported image type", err)
}
})
t.Run("subdirectory", func(t *testing.T) {
err := build(t, func(dir string) {
writeFixture(t, dir, map[string]string{"screenshots/sub/x.png": "nope"})
})
if err == nil || !strings.Contains(err.Error(), "subdirectories are not allowed") {
t.Fatalf("err = %v; want subdirectory rejection", err)
}
})
t.Run("too many", func(t *testing.T) {
err := build(t, func(dir string) {
files := map[string]string{}
for i := range maxScreenshotFiles + 1 {
files[fmt.Sprintf("screenshots/%02d.png", i)] = "x"
}
writeFixture(t, dir, files)
})
if err == nil || !strings.Contains(err.Error(), "gallery cap") {
t.Fatalf("err = %v; want gallery cap rejection", err)
}
})
t.Run("oversized image", func(t *testing.T) {
err := build(t, func(dir string) {
big := filepath.Join(dir, dirScreenshots, "big.png")
if mkErr := os.MkdirAll(filepath.Dir(big), 0o755); mkErr != nil {
t.Fatal(mkErr)
}
f, cErr := os.Create(big)
if cErr != nil {
t.Fatal(cErr)
}
if tErr := f.Truncate(maxScreenshotBytes + 1); tErr != nil {
t.Fatal(tErr)
}
_ = f.Close()
})
if err == nil || !strings.Contains(err.Error(), "per-image cap") {
t.Fatalf("err = %v; want per-image cap rejection", err)
}
})
t.Run("oversized preview", func(t *testing.T) {
err := build(t, func(dir string) {
big := filepath.Join(dir, filePreview)
f, cErr := os.Create(big)
if cErr != nil {
t.Fatal(cErr)
}
if tErr := f.Truncate(maxScreenshotBytes + 1); tErr != nil {
t.Fatal(tErr)
}
_ = f.Close()
})
if err == nil || !strings.Contains(err.Error(), "per-image cap") {
t.Fatalf("err = %v; want per-image cap rejection", err)
}
})
}