Some checks failed
CI / Test (Go 1.24.x, ubuntu-latest) (push) Successful in 1m53s
CI / Code Quality (push) Failing after 26s
CI / Security Scan (push) Failing after 11s
CI / Test Coverage (push) Successful in 1m13s
CI / Benchmarks (push) Failing after 10m22s
CI / Build CLI (push) Failing after 8s
Benchmarks / Run Benchmarks (push) Failing after 10m13s
Release / Test (push) Successful in 55s
Release / Build (amd64, darwin, ) (push) Failing after 12s
Release / Build (amd64, linux, ) (push) Failing after 6s
Release / Build (amd64, windows, .exe) (push) Failing after 12s
Release / Build (arm64, darwin, ) (push) Failing after 12s
Release / Build (arm64, linux, ) (push) Failing after 12s
Release / Release (push) Has been skipped
CI / Test (Go 1.24.x, macos-latest) (push) Has been cancelled
CI / Test (Go 1.24.x, windows-latest) (push) Has been cancelled
Move hosting from GitHub to private Gitea instance.
241 lines
6.1 KiB
Go
241 lines
6.1 KiB
Go
package renderer
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dockr.co/kev/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" opacity="1.00"/>`) {
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|