package svg import ( "strings" "testing" ) const sample = `# test scene canvas 100 80 bg #112233 def accent #4f9cf9 rect 10 10 30 20 fill=$accent rx=3 circle 70 30 15 fill=#3fca7c stroke=white stroke-width=2 line 0 70 100 70 stroke=red width=3 polygon 10,60 30,40 50,60 fill=#e0a63f path M60,60 L80,70 L90,50 Z stroke=white text 50 25 "hi & " size=10 fill=white anchor=middle group stroke=gray line 5 5 15 15 end ` func TestParseAndEmit(t *testing.T) { xml, doc, err := Build(sample) if err != nil { t.Fatal(err) } if doc.W != 100 || doc.H != 80 || doc.Bg != "#112233" { t.Errorf("canvas/bg wrong: %+v", doc) } for _, want := range []string{ `viewBox="0 0 100 80"`, ``, } { if !strings.Contains(xml, want) { t.Errorf("emitted SVG missing %q\n%s", want, xml) } } } func TestParseErrorsCarryLineNumbers(t *testing.T) { cases := map[string]string{ "canvas 100 100\nrect 1 2 3": "line 2", "canvas 100 100\nrect 1 2 3 four": "not a number", "canvas 100 100\ncircle 1 2 3 glow=yes": "unknown attribute", "canvas 100 100\nrect 1 2 3 4 fill=$missing": "undefined color variable", "canvas 100 100\nblob 1 2": "unknown element", "canvas 100 100\ngroup\nline 1 2 3 4": "never closed", "canvas 100 100\nend": "end without group", "canvas 100 100\npath X10,10": "path", "canvas 100 100\nrect 1 2 3 4 fill=#zzz": "non-hex", "rect 1 2 3 4": "missing canvas", } for src, want := range cases { _, _, err := Build(src) if err == nil { t.Errorf("no error for %q", src) continue } if !strings.Contains(err.Error(), want) { t.Errorf("error for %q = %q, want substring %q", src, err, want) } } } func TestVisibilityDefaults(t *testing.T) { xml, _, err := Build("canvas 10 10\nline 0 0 10 10\npolyline 0,0 5,5 10,0") if err != nil { t.Fatal(err) } if !strings.Contains(xml, ``) { t.Errorf("line did not get default stroke:\n%s", xml) } if !strings.Contains(xml, `fill="none"`) { t.Errorf("polyline did not get fill=none:\n%s", xml) } } func TestFlattenPath(t *testing.T) { subs, err := flattenPath("M0,0 L10,0 V10 H0 Z") if err != nil { t.Fatal(err) } if len(subs) != 1 { t.Fatalf("want 1 subpath, got %d", len(subs)) } pts := subs[0] last := pts[len(pts)-1] if last[0] != 0 || last[1] != 0 { t.Errorf("Z should close back to start, ended at %v", last) } // curves flatten into many segments subs, err = flattenPath("M0,0 Q50,100 100,0") if err != nil { t.Fatal(err) } if len(subs[0]) < 10 { t.Errorf("quadratic should flatten to many points, got %d", len(subs[0])) } // relative commands subs, err = flattenPath("m10,10 l10,0 l0,10 z") if err != nil { t.Fatal(err) } if got := subs[0][2]; got[0] != 20 || got[1] != 20 { t.Errorf("relative path point = %v, want 20,20", got) } if _, err := flattenPath("L10,10"); err == nil { t.Error("path not starting with M should fail") } if _, err := flattenPath("M0,0 A5,5 0 0 1 10,10"); err == nil { t.Error("unsupported arc command should fail") } } func TestRasterShapes(t *testing.T) { doc, err := Parse("canvas 100 100\nbg black\ncircle 50 50 30 fill=red") if err != nil { t.Fatal(err) } doc.normalize() r := RenderGrid(doc, 50) if r.W != 50 || r.H != 50 { t.Fatalf("grid %dx%d, want 50x50", r.W, r.H) } center := r.Pix[25*r.W+25] if center[0] < 200 || center[1] > 50 { t.Errorf("center should be red, got %v", center) } corner := r.Pix[0] if corner[0] > 50 && corner[1] > 50 && corner[2] > 50 { t.Errorf("corner should be black, got %v", corner) } } func TestRasterPolygonFill(t *testing.T) { doc, err := Parse("canvas 100 100\npolygon 0,0 100,0 100,100 0,100 fill=#00ff00") if err != nil { t.Fatal(err) } doc.normalize() r := RenderGrid(doc, 20) mid := r.Pix[10*r.W+10] if mid[1] < 200 { t.Errorf("polygon interior not filled: %v", mid) } } func TestANSIPreviewShape(t *testing.T) { doc, _ := Parse("canvas 40 20\nbg #000000\nrect 0 0 40 20 fill=white") doc.normalize() out := RenderGrid(doc, 40).ANSI() lines := strings.Split(strings.TrimRight(out, "\n"), "\n") if len(lines) != 10 { // 20 pixel rows / 2 per text row t.Errorf("ANSI preview has %d rows, want 10", len(lines)) } if !strings.Contains(out, "▀") { t.Error("preview contains no half-block characters") } } func TestInfoWarnsOutsideCanvas(t *testing.T) { doc, err := Parse("canvas 50 50\ncircle 25 25 10 fill=red\nrect 100 100 20 20 fill=blue\nline 40 40 60 60") if err != nil { t.Fatal(err) } doc.normalize() info := Info(doc) for _, want := range []string{ "canvas: 50x50", "circle:1", "entirely outside", "sticks outside", } { if !strings.Contains(info, want) { t.Errorf("info missing %q:\n%s", want, info) } } } func TestGroupAttrInheritanceInRaster(t *testing.T) { doc, err := Parse("canvas 10 10\ngroup fill=#ff0000\nrect 0 0 10 10\nend") if err != nil { t.Fatal(err) } doc.normalize() r := RenderGrid(doc, 10) if p := r.Pix[5*r.W+5]; p[0] < 200 { t.Errorf("group fill not inherited: %v", p) } }