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, "%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") < 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), "= 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), "") { 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) }