Initial release: Go Jdenticon library v0.1.0
- Core library with SVG and PNG generation - CLI tool with generate and batch commands - Cross-platform path handling for Windows compatibility - Comprehensive test suite with integration tests
This commit is contained in:
@@ -7,7 +7,7 @@ import (
|
||||
"image/png"
|
||||
"testing"
|
||||
|
||||
"github.com/kevin/go-jdenticon/internal/engine"
|
||||
"github.com/ungluedlabs/go-jdenticon/internal/engine"
|
||||
)
|
||||
|
||||
// TestPNGRenderer_VisualRegression tests that PNG output matches expected characteristics
|
||||
@@ -95,7 +95,10 @@ func TestPNGRenderer_VisualRegression(t *testing.T) {
|
||||
renderer.EndShape()
|
||||
}
|
||||
|
||||
pngData := renderer.ToPNG()
|
||||
pngData, err := renderer.ToPNG()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate PNG: %v", err)
|
||||
}
|
||||
|
||||
// Verify PNG is valid
|
||||
reader := bytes.NewReader(pngData)
|
||||
@@ -188,7 +191,10 @@ func TestPNGRenderer_ComplexIcon(t *testing.T) {
|
||||
renderer.AddCircle(engine.Point{X: 50, Y: 50}, 15, false)
|
||||
renderer.EndShape()
|
||||
|
||||
pngData := renderer.ToPNG()
|
||||
pngData, err := renderer.ToPNG()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate PNG: %v", err)
|
||||
}
|
||||
|
||||
// Verify the complex icon renders successfully
|
||||
reader := bytes.NewReader(pngData)
|
||||
@@ -210,7 +216,7 @@ func TestPNGRenderer_ComplexIcon(t *testing.T) {
|
||||
t.Logf("Complex icon PNG size: %d bytes", len(pngData))
|
||||
}
|
||||
|
||||
// TestRendererInterface_Consistency tests that both SVG and PNG renderers
|
||||
// TestRendererInterface_Consistency tests that both SVG and PNG renderers
|
||||
// implement the Renderer interface consistently
|
||||
func TestRendererInterface_Consistency(t *testing.T) {
|
||||
testCases := []struct {
|
||||
@@ -229,11 +235,11 @@ func TestRendererInterface_Consistency(t *testing.T) {
|
||||
r.BeginShape("#ff0000")
|
||||
r.AddRectangle(10, 10, 30, 30)
|
||||
r.EndShape()
|
||||
|
||||
|
||||
r.BeginShape("#00ff00")
|
||||
r.AddCircle(engine.Point{X: 70, Y: 70}, 15, false)
|
||||
r.EndShape()
|
||||
|
||||
|
||||
r.BeginShape("#0000ff")
|
||||
r.AddTriangle(
|
||||
engine.Point{X: 20, Y: 80},
|
||||
@@ -294,43 +300,46 @@ func TestRendererInterface_Consistency(t *testing.T) {
|
||||
if tc.bgOp > 0 {
|
||||
renderer.SetBackground(tc.bg, tc.bgOp)
|
||||
}
|
||||
|
||||
|
||||
tc.testFunc(renderer)
|
||||
|
||||
|
||||
// Verify PNG output
|
||||
pngData := renderer.ToPNG()
|
||||
pngData, err := renderer.ToPNG()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate PNG: %v", err)
|
||||
}
|
||||
if len(pngData) == 0 {
|
||||
t.Error("PNG renderer produced no data")
|
||||
}
|
||||
|
||||
|
||||
reader := bytes.NewReader(pngData)
|
||||
img, err := png.Decode(reader)
|
||||
if err != nil {
|
||||
t.Fatalf("PNG decode failed: %v", err)
|
||||
}
|
||||
|
||||
|
||||
bounds := img.Bounds()
|
||||
if bounds.Max.X != tc.size || bounds.Max.Y != tc.size {
|
||||
t.Errorf("PNG size = %dx%d, want %dx%d",
|
||||
t.Errorf("PNG size = %dx%d, want %dx%d",
|
||||
bounds.Max.X, bounds.Max.Y, tc.size, tc.size)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// Test with SVG renderer
|
||||
t.Run("svg", func(t *testing.T) {
|
||||
renderer := NewSVGRenderer(tc.size)
|
||||
if tc.bgOp > 0 {
|
||||
renderer.SetBackground(tc.bg, tc.bgOp)
|
||||
}
|
||||
|
||||
|
||||
tc.testFunc(renderer)
|
||||
|
||||
|
||||
// Verify SVG output
|
||||
svgData := renderer.ToSVG()
|
||||
if len(svgData) == 0 {
|
||||
t.Error("SVG renderer produced no data")
|
||||
}
|
||||
|
||||
|
||||
// Basic SVG validation
|
||||
if !bytes.Contains([]byte(svgData), []byte("<svg")) {
|
||||
t.Error("SVG output missing opening tag")
|
||||
@@ -338,7 +347,7 @@ func TestRendererInterface_Consistency(t *testing.T) {
|
||||
if !bytes.Contains([]byte(svgData), []byte("</svg>")) {
|
||||
t.Error("SVG output missing closing tag")
|
||||
}
|
||||
|
||||
|
||||
// Check size attributes
|
||||
expectedWidth := fmt.Sprintf(`width="%d"`, tc.size)
|
||||
expectedHeight := fmt.Sprintf(`height="%d"`, tc.size)
|
||||
@@ -366,12 +375,12 @@ func TestRendererInterface_BaseRendererMethods(t *testing.T) {
|
||||
for _, r := range renderers {
|
||||
t.Run(r.name, func(t *testing.T) {
|
||||
renderer := r.renderer
|
||||
|
||||
|
||||
// Test size getter
|
||||
if renderer.GetSize() != 50 {
|
||||
t.Errorf("GetSize() = %d, want 50", renderer.GetSize())
|
||||
}
|
||||
|
||||
|
||||
// Test background setting
|
||||
renderer.SetBackground("#123456", 0.75)
|
||||
if svgRenderer, ok := renderer.(*SVGRenderer); ok {
|
||||
@@ -384,7 +393,7 @@ func TestRendererInterface_BaseRendererMethods(t *testing.T) {
|
||||
t.Errorf("PNG GetBackground() = %s, %f, want #123456, 0.75", bg, op)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Test shape management
|
||||
renderer.BeginShape("#ff0000")
|
||||
if svgRenderer, ok := renderer.(*SVGRenderer); ok {
|
||||
@@ -397,7 +406,7 @@ func TestRendererInterface_BaseRendererMethods(t *testing.T) {
|
||||
t.Errorf("PNG GetCurrentColor() = %s, want #ff0000", color)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Test clearing
|
||||
renderer.Clear()
|
||||
if svgRenderer, ok := renderer.(*SVGRenderer); ok {
|
||||
@@ -418,11 +427,11 @@ func TestRendererInterface_BaseRendererMethods(t *testing.T) {
|
||||
func TestRendererInterface_CompatibilityWithJavaScript(t *testing.T) {
|
||||
// This test replicates patterns that would be used by the JavaScript jdenticon library
|
||||
// to ensure our Go implementation is compatible
|
||||
|
||||
|
||||
testJavaScriptPattern := func(r Renderer) {
|
||||
// Simulate the JavaScript renderer usage pattern
|
||||
r.SetBackground("#f0f0f0", 1.0)
|
||||
|
||||
|
||||
// Pattern similar to what iconGenerator.js would create
|
||||
shapes := []struct {
|
||||
color string
|
||||
@@ -466,20 +475,20 @@ func TestRendererInterface_CompatibilityWithJavaScript(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for _, shape := range shapes {
|
||||
r.BeginShape(shape.color)
|
||||
shape.actions()
|
||||
r.EndShape()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
t.Run("svg_javascript_pattern", func(t *testing.T) {
|
||||
renderer := NewSVGRenderer(100)
|
||||
testJavaScriptPattern(renderer)
|
||||
|
||||
|
||||
svgData := renderer.ToSVG()
|
||||
|
||||
|
||||
// Should contain multiple paths with different colors
|
||||
for _, color := range []string{"#4a90e2", "#7fc383", "#e94b3c"} {
|
||||
expected := fmt.Sprintf(`fill="%s"`, color)
|
||||
@@ -487,26 +496,29 @@ func TestRendererInterface_CompatibilityWithJavaScript(t *testing.T) {
|
||||
t.Errorf("SVG missing expected color: %s", color)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Should contain background
|
||||
if !bytes.Contains([]byte(svgData), []byte("#f0f0f0")) {
|
||||
t.Error("SVG missing background color")
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
t.Run("png_javascript_pattern", func(t *testing.T) {
|
||||
renderer := NewPNGRenderer(100)
|
||||
testJavaScriptPattern(renderer)
|
||||
|
||||
pngData := renderer.ToPNG()
|
||||
|
||||
|
||||
pngData, err := renderer.ToPNG()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate PNG: %v", err)
|
||||
}
|
||||
|
||||
// Verify valid PNG
|
||||
reader := bytes.NewReader(pngData)
|
||||
img, err := png.Decode(reader)
|
||||
if err != nil {
|
||||
t.Fatalf("PNG decode failed: %v", err)
|
||||
}
|
||||
|
||||
|
||||
bounds := img.Bounds()
|
||||
if bounds.Max.X != 100 || bounds.Max.Y != 100 {
|
||||
t.Errorf("PNG size = %dx%d, want 100x100", bounds.Max.X, bounds.Max.Y)
|
||||
@@ -522,7 +534,10 @@ func TestPNGRenderer_EdgeCases(t *testing.T) {
|
||||
renderer.AddPolygon([]engine.Point{{X: 0, Y: 0}, {X: 1, Y: 0}, {X: 1, Y: 1}, {X: 0, Y: 1}})
|
||||
renderer.EndShape()
|
||||
|
||||
pngData := renderer.ToPNG()
|
||||
pngData, err := renderer.ToPNG()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate PNG: %v", err)
|
||||
}
|
||||
if len(pngData) == 0 {
|
||||
t.Error("1x1 PNG should generate data")
|
||||
}
|
||||
@@ -535,7 +550,10 @@ func TestPNGRenderer_EdgeCases(t *testing.T) {
|
||||
renderer.AddCircle(engine.Point{X: 256, Y: 256}, 200, false)
|
||||
renderer.EndShape()
|
||||
|
||||
pngData := renderer.ToPNG()
|
||||
pngData, err := renderer.ToPNG()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate PNG: %v", err)
|
||||
}
|
||||
if len(pngData) == 0 {
|
||||
t.Error("512x512 PNG should generate data")
|
||||
}
|
||||
@@ -556,9 +574,12 @@ func TestPNGRenderer_EdgeCases(t *testing.T) {
|
||||
renderer.EndShape()
|
||||
|
||||
// Should not panic and should produce valid PNG
|
||||
pngData := renderer.ToPNG()
|
||||
pngData, err := renderer.ToPNG()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to generate PNG: %v", err)
|
||||
}
|
||||
reader := bytes.NewReader(pngData)
|
||||
_, err := png.Decode(reader)
|
||||
_, err = png.Decode(reader)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to decode PNG with out-of-bounds shapes: %v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user