package engine import ( "math" "testing" ) func TestDefaultColorConfig(t *testing.T) { config := DefaultColorConfig() // Test default values match JavaScript implementation if config.ColorSaturation != 0.5 { t.Errorf("ColorSaturation = %f, want 0.5", config.ColorSaturation) } if config.GrayscaleSaturation != 0.0 { t.Errorf("GrayscaleSaturation = %f, want 0.0", config.GrayscaleSaturation) } if config.ColorLightness.Min != 0.4 || config.ColorLightness.Max != 0.8 { t.Errorf("ColorLightness = {%f, %f}, want {0.4, 0.8}", config.ColorLightness.Min, config.ColorLightness.Max) } if config.GrayscaleLightness.Min != 0.3 || config.GrayscaleLightness.Max != 0.9 { t.Errorf("GrayscaleLightness = {%f, %f}, want {0.3, 0.9}", config.GrayscaleLightness.Min, config.GrayscaleLightness.Max) } if len(config.Hues) != 0 { t.Errorf("Hues should be empty by default, got %v", config.Hues) } if config.BackColor != nil { t.Error("BackColor should be nil by default") } if config.IconPadding != 0.08 { t.Errorf("IconPadding = %f, want 0.08", config.IconPadding) } } func TestLightnessRangeGetLightness(t *testing.T) { lr := LightnessRange{Min: 0.3, Max: 0.9} tests := []struct { value float64 expected float64 }{ {0.0, 0.3}, // Min value {1.0, 0.9}, // Max value {0.5, 0.6}, // Middle value: 0.3 + 0.5 * (0.9 - 0.3) = 0.6 {-0.5, 0.3}, // Below range, should clamp to min {1.5, 0.9}, // Above range, should clamp to max } for _, tt := range tests { result := lr.GetLightness(tt.value) if math.Abs(result-tt.expected) > 0.001 { t.Errorf("GetLightness(%f) = %f, want %f", tt.value, result, tt.expected) } } } func TestConfigRestrictHue(t *testing.T) { tests := []struct { name string hues []float64 originalHue float64 expectedHue float64 }{ { name: "no restriction", hues: nil, originalHue: 0.25, expectedHue: 0.25, }, { name: "empty restriction", hues: []float64{}, originalHue: 0.25, expectedHue: 0.25, }, { name: "single hue restriction", hues: []float64{180}, // 180 degrees = 0.5 turns originalHue: 0.25, expectedHue: 0.5, }, { name: "multiple hue restriction", hues: []float64{0, 120, 240}, // Red, Green, Blue originalHue: 0.1, // Should map to first hue (0 degrees) expectedHue: 0.0, }, { name: "hue normalization - negative", hues: []float64{90}, // 90 degrees = 0.25 turns originalHue: -0.5, expectedHue: 0.25, }, { name: "hue normalization - over 1", hues: []float64{270}, // 270 degrees = 0.75 turns originalHue: 1.5, expectedHue: 0.75, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config := ColorConfig{Hues: tt.hues} result := config.RestrictHue(tt.originalHue) if math.Abs(result-tt.expectedHue) > 0.001 { t.Errorf("RestrictHue(%f) = %f, want %f", tt.originalHue, result, tt.expectedHue) } }) } } func TestConfigValidate(t *testing.T) { // Test that validation corrects invalid values config := ColorConfig{ ColorSaturation: -0.5, // Invalid: below 0 GrayscaleSaturation: 1.5, // Invalid: above 1 ColorLightness: LightnessRange{Min: 0.8, Max: 0.2}, // Invalid: min > max GrayscaleLightness: LightnessRange{Min: -0.1, Max: 1.1}, // Invalid: out of range IconPadding: 2.0, // Invalid: above 1 } config.Validate() if config.ColorSaturation != 0.0 { t.Errorf("ColorSaturation after validation = %f, want 0.0", config.ColorSaturation) } if config.GrayscaleSaturation != 1.0 { t.Errorf("GrayscaleSaturation after validation = %f, want 1.0", config.GrayscaleSaturation) } // Min and max should be swapped if config.ColorLightness.Min != 0.2 || config.ColorLightness.Max != 0.8 { t.Errorf("ColorLightness after validation = {%f, %f}, want {0.2, 0.8}", config.ColorLightness.Min, config.ColorLightness.Max) } // Values should be clamped if config.GrayscaleLightness.Min != 0.0 || config.GrayscaleLightness.Max != 1.0 { t.Errorf("GrayscaleLightness after validation = {%f, %f}, want {0.0, 1.0}", config.GrayscaleLightness.Min, config.GrayscaleLightness.Max) } if config.IconPadding != 1.0 { t.Errorf("IconPadding after validation = %f, want 1.0", config.IconPadding) } } func TestColorConfigBuilder(t *testing.T) { redColor := NewColorRGB(255, 0, 0) config := NewColorConfigBuilder(). WithColorSaturation(0.7). WithGrayscaleSaturation(0.1). WithColorLightness(0.2, 0.8). WithGrayscaleLightness(0.1, 0.9). WithHues(0, 120, 240). WithBackColor(redColor). WithIconPadding(0.1). Build() if config.ColorSaturation != 0.7 { t.Errorf("ColorSaturation = %f, want 0.7", config.ColorSaturation) } if config.GrayscaleSaturation != 0.1 { t.Errorf("GrayscaleSaturation = %f, want 0.1", config.GrayscaleSaturation) } if config.ColorLightness.Min != 0.2 || config.ColorLightness.Max != 0.8 { t.Errorf("ColorLightness = {%f, %f}, want {0.2, 0.8}", config.ColorLightness.Min, config.ColorLightness.Max) } if config.GrayscaleLightness.Min != 0.1 || config.GrayscaleLightness.Max != 0.9 { t.Errorf("GrayscaleLightness = {%f, %f}, want {0.1, 0.9}", config.GrayscaleLightness.Min, config.GrayscaleLightness.Max) } if len(config.Hues) != 3 || config.Hues[0] != 0 || config.Hues[1] != 120 || config.Hues[2] != 240 { t.Errorf("Hues = %v, want [0, 120, 240]", config.Hues) } if config.BackColor == nil || !config.BackColor.Equals(redColor) { t.Error("BackColor should be set to red") } if config.IconPadding != 0.1 { t.Errorf("IconPadding = %f, want 0.1", config.IconPadding) } } func TestColorConfigBuilderValidation(t *testing.T) { // Test that builder validates configuration config := NewColorConfigBuilder(). WithColorSaturation(-0.5). // Invalid WithGrayscaleSaturation(1.5). // Invalid Build() // Should be corrected by validation if config.ColorSaturation != 0.0 { t.Errorf("ColorSaturation = %f, want 0.0 (corrected)", config.ColorSaturation) } if config.GrayscaleSaturation != 1.0 { t.Errorf("GrayscaleSaturation = %f, want 1.0 (corrected)", config.GrayscaleSaturation) } }