Replaces the four fixed rules with the model Logger32 uses and the operator asked for: Worked / Confirmed / QSL sent / To be sent, and inside each, which channels count — paper QSL, LoTW, eQSL, QRZ.com. No ticks means every channel, because a rule the operator has not narrowed must not quietly become a rule about nothing. "To be sent" is FIRST in the order, and that is the substantive decision here. A contact can be confirmed on LoTW and still owe a paper card; the colour an operator scans for is the one meaning "something is still owed". Placed after "confirmed" that row goes green and the card never gets printed. "Worked" is the catch-all — nothing sent, nothing requested, nothing back — and is off by default, since turning it on paints every remaining row. R and Q both count as owed: ADIF says requested and queued, and both mean the card has not gone out.
115 lines
4.3 KiB
Go
115 lines
4.3 KiB
Go
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)
|
|
}
|
|
}
|