package main import "testing" // The colour is interpolated into a CSS color-mix() by the grid, so anything // that is not plainly a hex colour has to be refused rather than passed on. func TestRowColorsRefuseAnythingButHex(t *testing.T) { in := RowColorSettings{Enabled: true, Rules: []RowColorRule{ {ID: "confirmed", Color: "#123abc", Enabled: true}, {ID: "sent", Color: "red; background:url(x)", Enabled: true}, {ID: "to_send", Color: "", Enabled: true}, }} got := normRowColors(in) byID := map[string]RowColorRule{} for _, r := range got.Rules { byID[r.ID] = r } if byID["confirmed"].Color != "#123abc" { t.Errorf("a valid colour was rewritten: %q", byID["confirmed"].Color) } if byID["sent"].Color != rowColorDefaults["sent"] { t.Errorf("an injection attempt survived: %q", byID["sent"].Color) } if byID["to_send"].Color != rowColorDefaults["to_send"] { t.Errorf("an empty colour was kept: %q", byID["to_send"].Color) } } // Priority lives in the data, not in a chain of ifs: a contact confirmed on // LoTW AND by card is confirmed, not "sent, awaiting reply". The panel shows // the rules in the order they apply, so that order must survive a round trip. func TestRowColorsKeepPriorityOrder(t *testing.T) { // Saved in a jumbled order, as a hand-edited settings row could be. got := normRowColors(RowColorSettings{Rules: []RowColorRule{ {ID: "to_send", Color: "#111111"}, {ID: "confirmed", Color: "#222222"}, }}) if len(got.Rules) != len(rowColorOrder) { t.Fatalf("got %d rules, want every one present", len(got.Rules)) } for i, id := range rowColorOrder { if got.Rules[i].ID != id { t.Errorf("rule %d is %q, want %q", i, got.Rules[i].ID, id) } } // The saved colours survived the reordering: each stayed with its own rule. byID := map[string]string{} for _, r := range got.Rules { byID[r.ID] = r.Color } if byID["to_send"] != "#111111" || byID["confirmed"] != "#222222" { t.Errorf("colours moved between rules: %v", byID) } } // Style and strength are normalised, not rejected: they only shape a colour, and // a value that got away is a slider, not a fault worth resetting the operator's // whole choice for. func TestRowColorsNormaliseStyleAndIntensity(t *testing.T) { for _, tc := range []struct { inStyle string inPct int wantStyle string wantPct int }{ {"bar", 12, "bar", 12}, {"tint", 30, "tint", 30}, {"both", 45, "both", 45}, {"", 0, "bar", 12}, // never configured {"wallpaper", 12, "bar", 12}, // not a style we draw {"tint", 900, "tint", 45}, // clamped, not reset {"tint", -5, "tint", 12}, } { got := normRowColors(RowColorSettings{Style: tc.inStyle, Intensity: tc.inPct}) if got.Style != tc.wantStyle || got.Intensity != tc.wantPct { t.Errorf("(%q,%d) -> (%q,%d), want (%q,%d)", tc.inStyle, tc.inPct, got.Style, got.Intensity, tc.wantStyle, tc.wantPct) } } } // Channels are normalised to the canonical order, and anything unknown is // dropped. Empty stays empty, because empty means "every channel" — a rule the // operator has not narrowed must not silently become a rule about nothing. func TestRowColorChannelsNormalise(t *testing.T) { got := normRowColors(RowColorSettings{Rules: []RowColorRule{ {ID: "confirmed", Color: "#111111", Channels: []string{"LOTW", "pigeon", "qsl", "lotw"}}, {ID: "sent", Color: "#222222"}, }}) byID := map[string][]string{} for _, r := range got.Rules { byID[r.ID] = r.Channels } if len(byID["confirmed"]) != 2 || byID["confirmed"][0] != "qsl" || byID["confirmed"][1] != "lotw" { t.Errorf("confirmed channels = %v, want [qsl lotw] — canonical order, deduped, unknown dropped", byID["confirmed"]) } if len(byID["sent"]) != 0 { t.Errorf("sent channels = %v, want empty (meaning every channel)", byID["sent"]) } } // The order IS the priority, and "to send" leads. A contact confirmed on LoTW // that still owes a paper card must read as owing the card — put after // "confirmed" it would go green and never be printed. func TestToSendOutranksConfirmed(t *testing.T) { got := normRowColors(RowColorSettings{}) if got.Rules[0].ID != "to_send" { t.Errorf("first rule is %q, want to_send", got.Rules[0].ID) } if got.Rules[len(got.Rules)-1].ID != "worked" { t.Errorf("last rule is %q, want worked as the catch-all", got.Rules[len(got.Rules)-1].ID) } }