Former/facetemplate_test.go

294 lines
7.0 KiB
Go

package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
)
func svgOnly(files []string) []string {
var out []string
for _, f := range files {
if filepath.Ext(f) == ".svg" {
out = append(out, f)
}
}
return out
}
func TestCubeOnOnePageSmall(t *testing.T) {
dir := t.TempDir()
cube := cubeFaces(25)
cfg := FaceTemplateConfig{NumFaces: 6, LongestSide: 25}
files, err := generateFaceTemplateSVGsWithShapes(cfg, dir, cube)
if err != nil {
t.Fatal(err)
}
// 25mm cube, 6 faces on letter paper — should pack onto 1 SVG page
svgs := svgOnly(files)
if len(svgs) != 1 {
t.Errorf("25mm cube should fit on 1 page, got %d", len(svgs))
}
data, _ := os.ReadFile(svgs[0])
svg := string(data)
polyCount := strings.Count(svg, "<polygon")
if polyCount != 6 {
t.Errorf("expected 6 cube face polygons, got %d", polyCount)
}
// All face labels present
for face := 1; face <= 6; face++ {
if !strings.Contains(svg, fmt.Sprintf(">%d<", face)) {
t.Errorf("missing face label %d", face)
}
}
// Bullseye fiducial markers
if !strings.Contains(svg, `fill="black"`) {
t.Error("missing fiducial markers")
}
// Calibration barcodes
if strings.Count(svg, "mm</text>") < 3 {
t.Error("missing calibration bar labels")
}
// Cell borders (dashed)
if !strings.Contains(svg, `stroke-dasharray="4,2"`) {
t.Error("missing cell border dashes")
}
}
func TestCubeMultiPageLarge(t *testing.T) {
dir := t.TempDir()
cube := cubeFaces(120)
cfg := FaceTemplateConfig{NumFaces: 6, LongestSide: 120}
files, err := generateFaceTemplateSVGsWithShapes(cfg, dir, cube)
if err != nil {
t.Fatal(err)
}
svgs := svgOnly(files)
if len(svgs) < 2 {
t.Errorf("120mm faces should need multiple pages, got %d", len(svgs))
}
for _, f := range svgs {
data, _ := os.ReadFile(f)
if !strings.Contains(string(data), "<polygon") {
t.Errorf("%s missing cube face polygon", f)
}
}
// PDF should also be generated
hasPDF := false
for _, f := range files {
if filepath.Ext(f) == ".pdf" {
hasPDF = true
info, _ := os.Stat(f)
if info.Size() < 100 {
t.Error("PDF file is suspiciously small")
}
}
}
if !hasPDF {
t.Error("multi-page output should include a combined PDF")
}
}
func TestCubeMediumPacking(t *testing.T) {
dir := t.TempDir()
cube := cubeFaces(60)
cfg := FaceTemplateConfig{NumFaces: 6, LongestSide: 60}
files, err := generateFaceTemplateSVGsWithShapes(cfg, dir, cube)
if err != nil {
t.Fatal(err)
}
svgs := svgOnly(files)
if len(svgs) >= 6 {
t.Errorf("60mm faces should pack onto fewer than 6 pages, got %d", len(svgs))
}
totalPolys := 0
for _, f := range svgs {
data, _ := os.ReadFile(f)
totalPolys += strings.Count(string(data), "<polygon")
}
if totalPolys != 6 {
t.Errorf("expected 6 total cube face polygons across all pages, got %d", totalPolys)
}
}
func TestNoShapes(t *testing.T) {
dir := t.TempDir()
cfg := FaceTemplateConfig{NumFaces: 4, LongestSide: 80}
files, err := GenerateFaceTemplateSVGs(cfg, dir)
if err != nil {
t.Fatal(err)
}
if len(files) < 1 {
t.Fatal("expected at least 1 file")
}
for _, f := range svgOnly(files) {
data, _ := os.ReadFile(f)
if strings.Contains(string(data), "<polygon") {
t.Errorf("%s has polygon but no shapes were provided", f)
}
}
}
func TestDefaults(t *testing.T) {
dir := t.TempDir()
cfg := FaceTemplateConfig{}
files, err := GenerateFaceTemplateSVGs(cfg, dir)
if err != nil {
t.Fatal(err)
}
if len(files) < 1 {
t.Fatal("default config should produce at least 1 file")
}
for _, f := range svgOnly(files) {
data, _ := os.ReadFile(f)
svg := string(data)
if !strings.HasPrefix(svg, "<?xml") {
t.Errorf("%s missing XML header", f)
}
if !strings.Contains(svg, "</svg>") {
t.Errorf("%s missing closing SVG tag", f)
}
}
}
func TestGridSpacingAuto(t *testing.T) {
tests := []struct {
longest float64
expected float64
}{
{15, 5},
{30, 5},
{50, 10},
{100, 10},
{150, 20},
{250, 20},
{300, 25},
}
for _, tt := range tests {
got := pickGridSpacing(tt.longest)
if got != tt.expected {
t.Errorf("pickGridSpacing(%.0f) = %.0f, want %.0f", tt.longest, got, tt.expected)
}
}
}
func TestCubeFaces(t *testing.T) {
faces := cubeFaces(50)
if len(faces) != 6 {
t.Fatalf("cube should have 6 faces, got %d", len(faces))
}
for i, f := range faces {
if f.FaceNum != i+1 {
t.Errorf("face %d has FaceNum %d", i, f.FaceNum)
}
if len(f.Outline) != 4 {
t.Errorf("cube face should have 4 vertices, got %d", len(f.Outline))
}
for _, pt := range f.Outline {
if pt[0] != 25 && pt[0] != -25 {
t.Errorf("unexpected x coordinate: %f", pt[0])
}
if pt[1] != 25 && pt[1] != -25 {
t.Errorf("unexpected y coordinate: %f", pt[1])
}
}
}
}
func TestPageLayout(t *testing.T) {
// Verify computePageLayout packs correctly
cfg := FaceTemplateConfig{NumFaces: 6, LongestSide: 30, PageWidth: 215.9, PageHeight: 279.4}
pages := computePageLayout(cfg)
totalCells := 0
for _, pg := range pages {
totalCells += len(pg.Cells)
for _, cell := range pg.Cells {
if cell.W < cfg.LongestSide {
t.Errorf("cell width %.1f < longest side %.1f", cell.W, cfg.LongestSide)
}
if cell.H < cfg.LongestSide {
t.Errorf("cell height %.1f < longest side %.1f", cell.H, cfg.LongestSide)
}
}
}
if totalCells != cfg.NumFaces {
t.Errorf("total cells %d != numFaces %d", totalCells, cfg.NumFaces)
}
}
func TestSingleFacePage(t *testing.T) {
dir := t.TempDir()
cfg := FaceTemplateConfig{NumFaces: 1, LongestSide: 100}
files, err := GenerateFaceTemplateSVGs(cfg, dir)
if err != nil {
t.Fatal(err)
}
svgs := svgOnly(files)
if len(svgs) != 1 {
t.Fatalf("1 face should produce 1 SVG, got %d", len(svgs))
}
}
func TestManyFaces(t *testing.T) {
dir := t.TempDir()
cfg := FaceTemplateConfig{NumFaces: 12, LongestSide: 40}
files, err := GenerateFaceTemplateSVGs(cfg, dir)
if err != nil {
t.Fatal(err)
}
svgs := svgOnly(files)
totalCells := 0
for _, f := range svgs {
data, _ := os.ReadFile(f)
totalCells += strings.Count(string(data), `font-weight="bold"`)
}
totalCells -= len(svgs) // subtract page headers
if totalCells != 12 {
t.Errorf("expected 12 face cells across all pages, got %d", totalCells)
}
}
func TestPDFStructure(t *testing.T) {
dir := t.TempDir()
cube := cubeFaces(120)
cfg := FaceTemplateConfig{NumFaces: 6, LongestSide: 120, PageWidth: 215.9, PageHeight: 279.4}
path, err := GenerateFaceTemplatePDF(cfg, dir, cube)
if err != nil {
t.Fatal(err)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if len(data) < 1000 {
t.Errorf("PDF too small: %d bytes", len(data))
}
if !strings.HasPrefix(string(data), "%PDF-") {
t.Error("missing PDF header")
}
if !strings.Contains(string(data), "%%EOF") {
t.Error("missing PDF EOF marker")
}
t.Logf("PDF: %d bytes, %s", len(data), path)
}
func TestPDFVisual(t *testing.T) {
dir := t.TempDir()
cube := cubeFaces(120)
cfg := FaceTemplateConfig{NumFaces: 6, LongestSide: 120, PageWidth: 215.9, PageHeight: 279.4}
path, err := GenerateFaceTemplatePDF(cfg, dir, cube)
if err != nil {
t.Fatal(err)
}
t.Logf("PDF written to: %s", path)
}