init
This commit is contained in:
240
internal/renderer/svg_test.go
Normal file
240
internal/renderer/svg_test.go
Normal file
@@ -0,0 +1,240 @@
|
||||
package renderer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kevin/go-jdenticon/internal/engine"
|
||||
)
|
||||
|
||||
func TestSVGPath_AddPolygon(t *testing.T) {
|
||||
path := &SVGPath{}
|
||||
points := []engine.Point{
|
||||
{X: 0, Y: 0},
|
||||
{X: 10, Y: 0},
|
||||
{X: 10, Y: 10},
|
||||
{X: 0, Y: 10},
|
||||
}
|
||||
|
||||
path.AddPolygon(points)
|
||||
expected := "M0 0L10 0L10 10L0 10Z"
|
||||
if got := path.DataString(); got != expected {
|
||||
t.Errorf("AddPolygon() = %v, want %v", got, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSVGPath_AddPolygonEmpty(t *testing.T) {
|
||||
path := &SVGPath{}
|
||||
path.AddPolygon([]engine.Point{})
|
||||
|
||||
if got := path.DataString(); got != "" {
|
||||
t.Errorf("AddPolygon([]) = %v, want empty string", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSVGPath_AddCircle(t *testing.T) {
|
||||
path := &SVGPath{}
|
||||
topLeft := engine.Point{X: 25, Y: 25} // Top-left corner to get center at (50, 50)
|
||||
size := 50.0 // Size 50 gives radius 25
|
||||
|
||||
path.AddCircle(topLeft, size, false)
|
||||
|
||||
// Should start at left side of circle and draw two arcs
|
||||
result := path.DataString()
|
||||
if !strings.HasPrefix(result, "M25 50") {
|
||||
t.Errorf("Circle should start at left side, got: %s", result)
|
||||
}
|
||||
if !strings.Contains(result, "a25,25 0 1,1") {
|
||||
t.Errorf("Circle should contain clockwise arc, got: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSVGPath_AddCircleCounterClockwise(t *testing.T) {
|
||||
path := &SVGPath{}
|
||||
topLeft := engine.Point{X: 25, Y: 25} // Top-left corner to get center at (50, 50)
|
||||
size := 50.0 // Size 50 gives radius 25
|
||||
|
||||
path.AddCircle(topLeft, size, true)
|
||||
|
||||
result := path.DataString()
|
||||
if !strings.Contains(result, "a25,25 0 1,0") {
|
||||
t.Errorf("Counter-clockwise circle should have sweep flag 0, got: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSVGRenderer_NewSVGRenderer(t *testing.T) {
|
||||
renderer := NewSVGRenderer(100)
|
||||
|
||||
if renderer.iconSize != 100 {
|
||||
t.Errorf("NewSVGRenderer(100).iconSize = %v, want 100", renderer.iconSize)
|
||||
}
|
||||
if renderer.pathsByColor == nil {
|
||||
t.Error("pathsByColor should be initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSVGRenderer_BeginEndShape(t *testing.T) {
|
||||
renderer := NewSVGRenderer(100)
|
||||
|
||||
renderer.BeginShape("#ff0000")
|
||||
if renderer.currentColor != "#ff0000" {
|
||||
t.Errorf("BeginShape should set currentColor, got %v", renderer.currentColor)
|
||||
}
|
||||
|
||||
if _, exists := renderer.pathsByColor["#ff0000"]; !exists {
|
||||
t.Error("BeginShape should create path for color")
|
||||
}
|
||||
|
||||
renderer.EndShape()
|
||||
// EndShape is a no-op for SVG, just verify it doesn't panic
|
||||
}
|
||||
|
||||
func TestSVGRenderer_AddPolygon(t *testing.T) {
|
||||
renderer := NewSVGRenderer(100)
|
||||
renderer.BeginShape("#ff0000")
|
||||
|
||||
points := []engine.Point{
|
||||
{X: 0, Y: 0},
|
||||
{X: 10, Y: 0},
|
||||
{X: 5, Y: 10},
|
||||
}
|
||||
|
||||
renderer.AddPolygon(points)
|
||||
|
||||
path := renderer.pathsByColor["#ff0000"]
|
||||
expected := "M0 0L10 0L5 10Z"
|
||||
if got := path.DataString(); got != expected {
|
||||
t.Errorf("AddPolygon() = %v, want %v", got, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSVGRenderer_AddCircle(t *testing.T) {
|
||||
renderer := NewSVGRenderer(100)
|
||||
renderer.BeginShape("#00ff00")
|
||||
|
||||
topLeft := engine.Point{X: 30, Y: 30} // Top-left corner to get center at (50, 50)
|
||||
size := 40.0 // Size 40 gives radius 20
|
||||
|
||||
renderer.AddCircle(topLeft, size, false)
|
||||
|
||||
path := renderer.pathsByColor["#00ff00"]
|
||||
result := path.DataString()
|
||||
if !strings.HasPrefix(result, "M30 50") {
|
||||
t.Errorf("Circle should start at correct position, got: %s", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSVGRenderer_ToSVG(t *testing.T) {
|
||||
renderer := NewSVGRenderer(100)
|
||||
renderer.SetBackground("#ffffff", 1.0)
|
||||
|
||||
renderer.BeginShape("#ff0000")
|
||||
points := []engine.Point{
|
||||
{X: 0, Y: 0},
|
||||
{X: 10, Y: 0},
|
||||
{X: 10, Y: 10},
|
||||
}
|
||||
renderer.AddPolygon(points)
|
||||
|
||||
svg := renderer.ToSVG()
|
||||
|
||||
// Check SVG structure
|
||||
if !strings.Contains(svg, `<svg xmlns="http://www.w3.org/2000/svg"`) {
|
||||
t.Error("SVG should contain proper xmlns")
|
||||
}
|
||||
if !strings.Contains(svg, `width="100" height="100"`) {
|
||||
t.Error("SVG should contain correct dimensions")
|
||||
}
|
||||
if !strings.Contains(svg, `viewBox="0 0 100 100"`) {
|
||||
t.Error("SVG should contain correct viewBox")
|
||||
}
|
||||
if !strings.Contains(svg, `<rect width="100%" height="100%" fill="#ffffff"/>`) {
|
||||
t.Error("SVG should contain background rect")
|
||||
}
|
||||
if !strings.Contains(svg, `<path fill="#ff0000" d="M0 0L10 0L10 10Z"/>`) {
|
||||
t.Error("SVG should contain path with correct data")
|
||||
}
|
||||
if !strings.HasSuffix(svg, "</svg>") {
|
||||
t.Error("SVG should end with closing tag")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSVGRenderer_ToSVGWithoutBackground(t *testing.T) {
|
||||
renderer := NewSVGRenderer(50)
|
||||
|
||||
renderer.BeginShape("#0000ff")
|
||||
center := engine.Point{X: 25, Y: 25}
|
||||
renderer.AddCircle(center, 10, false)
|
||||
|
||||
svg := renderer.ToSVG()
|
||||
|
||||
// Should not contain background rect
|
||||
if strings.Contains(svg, "<rect") {
|
||||
t.Error("SVG without background should not contain rect")
|
||||
}
|
||||
// Should contain the circle path
|
||||
if !strings.Contains(svg, `fill="#0000ff"`) {
|
||||
t.Error("SVG should contain circle path")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSVGRenderer_BackgroundWithOpacity(t *testing.T) {
|
||||
renderer := NewSVGRenderer(100)
|
||||
renderer.SetBackground("#cccccc", 0.5)
|
||||
|
||||
svg := renderer.ToSVG()
|
||||
|
||||
if !strings.Contains(svg, `opacity="0.50"`) {
|
||||
t.Error("SVG should contain opacity attribute")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSvgValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
input float64
|
||||
expected string
|
||||
}{
|
||||
{0, "0"},
|
||||
{1.0, "1"},
|
||||
{1.5, "1.5"},
|
||||
{1.23456, "1.2"},
|
||||
{1.26, "1.3"},
|
||||
{10.0, "10"},
|
||||
{10.1, "10.1"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if got := svgValue(test.input); got != test.expected {
|
||||
t.Errorf("svgValue(%v) = %v, want %v", test.input, got, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSvgValueRounding(t *testing.T) {
|
||||
// Test cases to verify "round half up" behavior matches JavaScript implementation
|
||||
testCases := []struct {
|
||||
input float64
|
||||
expected string
|
||||
}{
|
||||
{12.0, "12"},
|
||||
{12.2, "12.2"},
|
||||
{12.25, "12.3"}, // Key case that fails with math.Round (would be "12.2")
|
||||
{12.35, "12.4"}, // Another case to verify consistent behavior
|
||||
{12.45, "12.5"}, // Another key case
|
||||
{12.75, "12.8"},
|
||||
{-12.25, "-12.2"}, // Test negative rounding
|
||||
{-12.35, "-12.3"},
|
||||
{50.45, "50.5"}, // Real-world case from avatar generation
|
||||
{50.55, "50.6"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("Input_%f", tc.input), func(t *testing.T) {
|
||||
got := svgValue(tc.input)
|
||||
if got != tc.expected {
|
||||
t.Errorf("svgValue(%f) = %q; want %q", tc.input, got, tc.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user