package jdenticon import ( "testing" ) func TestDefaultConfig(t *testing.T) { config := DefaultConfig() // Test default values match jdenticon-js if len(config.HueRestrictions) != 0 { t.Errorf("Expected no hue restrictions, got %v", config.HueRestrictions) } expectedColorRange := [2]float64{0.4, 0.8} if config.ColorLightnessRange != expectedColorRange { t.Errorf("Expected color lightness range %v, got %v", expectedColorRange, config.ColorLightnessRange) } expectedGrayscaleRange := [2]float64{0.3, 0.9} if config.GrayscaleLightnessRange != expectedGrayscaleRange { t.Errorf("Expected grayscale lightness range %v, got %v", expectedGrayscaleRange, config.GrayscaleLightnessRange) } if config.ColorSaturation != 0.5 { t.Errorf("Expected color saturation 0.5, got %f", config.ColorSaturation) } if config.GrayscaleSaturation != 0.0 { t.Errorf("Expected grayscale saturation 0.0, got %f", config.GrayscaleSaturation) } if config.BackgroundColor != "" { t.Errorf("Expected empty background color, got %s", config.BackgroundColor) } if config.Padding != 0.08 { t.Errorf("Expected padding 0.08, got %f", config.Padding) } } func TestWithHueRestrictions(t *testing.T) { tests := []struct { name string hues []float64 wantErr bool }{ {"valid hues", []float64{0, 90, 180, 270}, false}, {"single hue", []float64{120}, false}, {"empty slice", []float64{}, false}, {"negative hue", []float64{-10}, true}, {"hue too large", []float64{360}, true}, {"mixed valid/invalid", []float64{120, 400}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config, err := Configure(WithHueRestrictions(tt.hues)) if tt.wantErr { if err == nil { t.Errorf("Expected error for hues %v, got none", tt.hues) } return } if err != nil { t.Errorf("Unexpected error: %v", err) return } if len(config.HueRestrictions) != len(tt.hues) { t.Errorf("Expected %d hue restrictions, got %d", len(tt.hues), len(config.HueRestrictions)) } for i, hue := range tt.hues { if config.HueRestrictions[i] != hue { t.Errorf("Expected hue %f at index %d, got %f", hue, i, config.HueRestrictions[i]) } } }) } } func TestWithLightnessRanges(t *testing.T) { tests := []struct { name string min float64 max float64 wantErr bool }{ {"valid range", 0.2, 0.8, false}, {"full range", 0.0, 1.0, false}, {"equal values", 0.5, 0.5, false}, {"min > max", 0.8, 0.2, true}, {"negative min", -0.1, 0.5, true}, {"max > 1", 0.5, 1.1, true}, {"both invalid", -0.1, 1.1, true}, } for _, tt := range tests { t.Run("color_"+tt.name, func(t *testing.T) { config, err := Configure(WithColorLightnessRange(tt.min, tt.max)) if tt.wantErr { if err == nil { t.Errorf("Expected error for range [%f, %f], got none", tt.min, tt.max) } return } if err != nil { t.Errorf("Unexpected error: %v", err) return } expected := [2]float64{tt.min, tt.max} if config.ColorLightnessRange != expected { t.Errorf("Expected range %v, got %v", expected, config.ColorLightnessRange) } }) t.Run("grayscale_"+tt.name, func(t *testing.T) { config, err := Configure(WithGrayscaleLightnessRange(tt.min, tt.max)) if tt.wantErr { if err == nil { t.Errorf("Expected error for range [%f, %f], got none", tt.min, tt.max) } return } if err != nil { t.Errorf("Unexpected error: %v", err) return } expected := [2]float64{tt.min, tt.max} if config.GrayscaleLightnessRange != expected { t.Errorf("Expected range %v, got %v", expected, config.GrayscaleLightnessRange) } }) } } func TestWithSaturation(t *testing.T) { tests := []struct { name string saturation float64 wantErr bool }{ {"valid saturation", 0.5, false}, {"zero saturation", 0.0, false}, {"max saturation", 1.0, false}, {"negative saturation", -0.1, true}, {"saturation > 1", 1.1, true}, } for _, tt := range tests { t.Run("color_"+tt.name, func(t *testing.T) { config, err := Configure(WithColorSaturation(tt.saturation)) if tt.wantErr { if err == nil { t.Errorf("Expected error for saturation %f, got none", tt.saturation) } return } if err != nil { t.Errorf("Unexpected error: %v", err) return } if config.ColorSaturation != tt.saturation { t.Errorf("Expected saturation %f, got %f", tt.saturation, config.ColorSaturation) } }) t.Run("grayscale_"+tt.name, func(t *testing.T) { config, err := Configure(WithGrayscaleSaturation(tt.saturation)) if tt.wantErr { if err == nil { t.Errorf("Expected error for saturation %f, got none", tt.saturation) } return } if err != nil { t.Errorf("Unexpected error: %v", err) return } if config.GrayscaleSaturation != tt.saturation { t.Errorf("Expected saturation %f, got %f", tt.saturation, config.GrayscaleSaturation) } }) } } func TestWithBackgroundColor(t *testing.T) { tests := []struct { name string color string wantErr bool expected string }{ {"empty color", "", false, ""}, {"3-char hex", "#fff", false, "#fff"}, {"4-char hex with alpha", "#ffff", false, "#ffff"}, {"6-char hex", "#ffffff", false, "#ffffff"}, {"8-char hex with alpha", "#ffffff80", false, "#ffffff80"}, {"lowercase", "#abc123", false, "#abc123"}, {"uppercase", "#ABC123", false, "#ABC123"}, {"invalid format", "ffffff", true, ""}, {"invalid chars", "#gggggg", true, ""}, {"too short", "#ff", true, ""}, {"5-char hex", "#fffff", true, ""}, {"7-char hex", "#fffffff", true, ""}, {"9-char hex", "#fffffffff", true, ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config, err := Configure(WithBackgroundColor(tt.color)) if tt.wantErr { if err == nil { t.Errorf("Expected error for color %s, got none", tt.color) } return } if err != nil { t.Errorf("Unexpected error: %v", err) return } if config.BackgroundColor != tt.expected { t.Errorf("Expected color %s, got %s", tt.expected, config.BackgroundColor) } }) } } func TestWithPadding(t *testing.T) { tests := []struct { name string padding float64 wantErr bool }{ {"valid padding", 0.08, false}, {"zero padding", 0.0, false}, {"max padding", 0.5, false}, {"negative padding", -0.1, true}, {"padding > 0.5", 0.6, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config, err := Configure(WithPadding(tt.padding)) if tt.wantErr { if err == nil { t.Errorf("Expected error for padding %f, got none", tt.padding) } return } if err != nil { t.Errorf("Unexpected error: %v", err) return } if config.Padding != tt.padding { t.Errorf("Expected padding %f, got %f", tt.padding, config.Padding) } }) } } func TestConfigureMultipleOptions(t *testing.T) { config, err := Configure( WithHueRestrictions([]float64{120, 240}), WithColorSaturation(0.7), WithPadding(0.1), WithBackgroundColor("#ff0000"), ) if err != nil { t.Errorf("Unexpected error: %v", err) return } if len(config.HueRestrictions) != 2 || config.HueRestrictions[0] != 120 || config.HueRestrictions[1] != 240 { t.Errorf("Expected hue restrictions [120, 240], got %v", config.HueRestrictions) } if config.ColorSaturation != 0.7 { t.Errorf("Expected color saturation 0.7, got %f", config.ColorSaturation) } if config.Padding != 0.1 { t.Errorf("Expected padding 0.1, got %f", config.Padding) } if config.BackgroundColor != "#ff0000" { t.Errorf("Expected background color #ff0000, got %s", config.BackgroundColor) } // Check that other values are still default expectedColorRange := [2]float64{0.4, 0.8} if config.ColorLightnessRange != expectedColorRange { t.Errorf("Expected default color lightness range %v, got %v", expectedColorRange, config.ColorLightnessRange) } } func TestParseColor(t *testing.T) { tests := []struct { name string input string expected string wantErr bool }{ {"empty", "", "", false}, {"3-char to 6-char", "#abc", "#aabbcc", false}, {"4-char to 8-char", "#abcd", "#aabbccdd", false}, {"6-char unchanged", "#abcdef", "#abcdef", false}, {"8-char unchanged", "#abcdef80", "#abcdef80", false}, {"invalid format", "abcdef", "", true}, {"invalid length", "#abcde", "", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result, err := ParseColor(tt.input) if tt.wantErr { if err == nil { t.Errorf("Expected error for input %s, got none", tt.input) } return } if err != nil { t.Errorf("Unexpected error: %v", err) return } if result != tt.expected { t.Errorf("Expected %s, got %s", tt.expected, result) } }) } } func TestConfigGetHue(t *testing.T) { tests := []struct { name string restrictions []float64 input float64 expected float64 }{ {"no restrictions", nil, 0.5, 0.5}, {"single restriction", []float64{180}, 0.5, 0.5}, {"multiple restrictions", []float64{0, 120, 240}, 0.0, 0.0}, {"multiple restrictions mid", []float64{0, 120, 240}, 0.5, 120.0 / 360.0}, {"multiple restrictions high", []float64{0, 120, 240}, 0.99, 240.0 / 360.0}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config := DefaultConfig() config.HueRestrictions = tt.restrictions result := config.GetHue(tt.input) // Allow small floating point differences if abs(result-tt.expected) > 0.001 { t.Errorf("Expected hue %f, got %f", tt.expected, result) } }) } } func TestConfigGetLightness(t *testing.T) { config := DefaultConfig() // Test color lightness colorLight := config.GetColorLightness(0.0) if colorLight != 0.4 { t.Errorf("Expected min color lightness 0.4, got %f", colorLight) } colorLight = config.GetColorLightness(1.0) if colorLight != 0.8 { t.Errorf("Expected max color lightness 0.8, got %f", colorLight) } colorLight = config.GetColorLightness(0.5) expected := 0.4 + 0.5*(0.8-0.4) if abs(colorLight-expected) > 0.001 { t.Errorf("Expected mid color lightness %f, got %f", expected, colorLight) } // Test grayscale lightness grayLight := config.GetGrayscaleLightness(0.0) if grayLight != 0.3 { t.Errorf("Expected min grayscale lightness 0.3, got %f", grayLight) } grayLight = config.GetGrayscaleLightness(1.0) if abs(grayLight-0.9) > 0.001 { t.Errorf("Expected max grayscale lightness 0.9, got %f", grayLight) } } func TestConfigureFailing(t *testing.T) { // Test that Configure fails on invalid options _, err := Configure( WithHueRestrictions([]float64{400}), // Invalid hue WithColorSaturation(0.5), // Valid option after invalid ) if err == nil { t.Error("Expected Configure to fail with invalid hue restriction") } } // Helper function for floating point comparison func abs(x float64) float64 { if x < 0 { return -x } return x }