package rgb import ( "strings" "testing" ) // The color notation is documented in README.md: a bare or rgb:-prefixed hex // value, and hsv:,, in the firmware's own 0-255 units. Only the HSV // form carries a value component, and it is not a color component: the keyboard // has no value register, so the caller must be able to tell "no value given" // from "value 255". func TestParseColorSpec(t *testing.T) { tests := []struct { name string spec string want HSV // hasValue is false for the hex forms: nothing in the notation asks // for a brightness, so the caller must not write one. hasValue bool }{ {"bare hex red", "ff0000", HSV{H: 0, S: 255}, false}, {"bare hex green", "00ff00", HSV{H: 85, S: 255}, false}, {"bare hex gray", "d0d0d0", HSV{H: 0, S: 0}, false}, {"bare hex black", "000000", HSV{H: 0, S: 0}, false}, {"bare hex white", "ffffff", HSV{H: 0, S: 0}, false}, {"rgb prefixed", "rgb:00ff00", HSV{H: 85, S: 255}, false}, {"rgb prefixed red", "rgb:ff0000", HSV{H: 0, S: 255}, false}, {"hsv full", "hsv:0,255,255", HSV{H: 0, S: 255, V: 255}, true}, {"hsv partial brightness", "hsv:85,255,128", HSV{H: 85, S: 255, V: 128}, true}, {"hsv zero value", "hsv:85,255,0", HSV{H: 85, S: 255, V: 0}, true}, {"hsv zero saturation", "hsv:120,0,200", HSV{H: 120, S: 0, V: 200}, true}, {"hsv hue 254", "hsv:254,255,255", HSV{H: 254, S: 255, V: 255}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ParseColorSpec(tt.spec) if err != nil { t.Fatalf("ParseColorSpec(%q) unexpected error: %v", tt.spec, err) } // Compare the components the notation can carry. V is only // meaningful together with HasValue, so it is checked here // rather than through struct equality. if got.H != tt.want.H || got.S != tt.want.S || got.V != tt.want.V { t.Errorf("ParseColorSpec(%q) = hue %d sat %d value %d, want hue %d sat %d value %d", tt.spec, got.H, got.S, got.V, tt.want.H, tt.want.S, tt.want.V) } if got.HasValue() != tt.hasValue { t.Errorf("ParseColorSpec(%q).HasValue() = %t, want %t", tt.spec, got.HasValue(), tt.hasValue) } }) } } func TestParseColorSpecRejectsInvalidInput(t *testing.T) { tests := []struct { name string spec string want string }{ {"unknown prefix", "xyz:ff0000", "unknown color notation"}, {"hsv with two components", "hsv:0,255", "want hsv:,,"}, {"hsv with four components", "hsv:0,255,255,255", "want hsv:,,"}, {"hsv without components", "hsv:", "want hsv:,,"}, {"hsv bare", "hsv", "unknown color notation"}, {"hsv component above range", "hsv:0,256,255", "invalid hsv component"}, {"hsv negative component", "hsv:-1,255,255", "invalid hsv component"}, {"hsv non numeric component", "hsv:0,ff,255", "invalid hsv component"}, {"hsv empty component", "hsv:0,,255", "invalid hsv component"}, {"hex too short", "ff00", "invalid hex color"}, {"hex with hash", "#ff0000", "invalid hex color"}, {"empty", "", "invalid hex color"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := ParseColorSpec(tt.spec) if err == nil { t.Fatalf("ParseColorSpec(%q) expected error, got nil", tt.spec) } if !strings.Contains(err.Error(), tt.want) { t.Errorf("ParseColorSpec(%q) error = %q, want it to contain %q", tt.spec, err, tt.want) } }) } } // The error message is the only place a caller learns which notations exist, so // it has to name them. func TestUnknownNotationErrorNamesTheThreeForms(t *testing.T) { _, err := ParseColorSpec("nope:1") if err == nil { t.Fatal("ParseColorSpec(\"nope:1\") expected error, got nil") } for _, form := range []string{"ff0000", "rgb:", "hsv:"} { if !strings.Contains(err.Error(), form) { t.Errorf("error %q does not mention the %q form", err, form) } } }