127 lines
4.9 KiB
Go
127 lines
4.9 KiB
Go
package main
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"hamlog/internal/qso"
|
|
)
|
|
|
|
// idx builds a worked-grid index the way GridWorkedIndex does, so the test
|
|
// exercises the real key shape rather than a convenient one.
|
|
func idx(entries ...struct {
|
|
grid, band, mode string
|
|
confirmed bool
|
|
}) map[string]bool {
|
|
m := map[string]bool{}
|
|
for _, e := range entries {
|
|
for _, cls := range gridClassesOf(e.mode) {
|
|
for _, k := range []string{qso.GridKey(e.grid, e.band, cls), qso.GridKey(e.grid, "", cls)} {
|
|
m[k] = m[k] || e.confirmed
|
|
}
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
|
|
type entry = struct {
|
|
grid, band, mode string
|
|
confirmed bool
|
|
}
|
|
|
|
// The six scopes are six different questions, and the whole point of offering
|
|
// them is that the same square answers differently.
|
|
func TestGridScopes(t *testing.T) {
|
|
// JN36 worked once: 20 m, FT8, not confirmed.
|
|
i := idx(entry{"JN36", "20m", "FT8", false})
|
|
|
|
for _, tc := range []struct {
|
|
scope string
|
|
band string
|
|
mode string
|
|
want bool
|
|
why string
|
|
}{
|
|
{"band_ftx", "20m", "FT8", false, "same band, same family — already mine"},
|
|
{"band_ftx", "15m", "FT8", true, "another band, and the scope counts bands"},
|
|
{"mix_ftx", "15m", "FT8", false, "another band, but this scope does not care"},
|
|
{"band_mode", "20m", "FT4", true, "same band, but FT4 is not FT8 under an exact-mode scope"},
|
|
{"band_ftx", "20m", "FT4", false, "same band, and FT4 is in the same FT family"},
|
|
{"band_digi", "20m", "RTTY", false, "digital counts every digital mode"},
|
|
{"band_ftx", "20m", "RTTY", false, "RTTY is outside an FTx scope — it is not evidence either way, so nothing is flagged"},
|
|
{"mix_digi", "15m", "FT4", false, "the widest scope: worked anywhere, any digital"},
|
|
} {
|
|
got := gridIsWanted(i, lookupGridScope(tc.scope), "new", "JN36", tc.band, tc.mode)
|
|
if got != tc.want {
|
|
t.Errorf("scope %s, %s %s → wanted=%v, want %v — %s", tc.scope, tc.band, tc.mode, got, tc.want, tc.why)
|
|
}
|
|
}
|
|
}
|
|
|
|
// "New + unconfirmed" chases a square that is worked but not yet confirmed: it
|
|
// is still missing from the award, which is the only sense in which it is done.
|
|
func TestGridHuntUnconfirmed(t *testing.T) {
|
|
unconf := idx(entry{"JN36", "20m", "FT8", false})
|
|
conf := idx(entry{"IO91", "20m", "FT8", true})
|
|
|
|
if gridIsWanted(unconf, lookupGridScope("band_ftx"), "new", "JN36", "20m", "FT8") {
|
|
t.Error("worked-but-unconfirmed must NOT be wanted when hunting only what is new")
|
|
}
|
|
if !gridIsWanted(unconf, lookupGridScope("band_ftx"), "new_unconfirmed", "JN36", "20m", "FT8") {
|
|
t.Error("worked-but-unconfirmed must BE wanted when hunting unconfirmed too")
|
|
}
|
|
if gridIsWanted(conf, lookupGridScope("band_ftx"), "new_unconfirmed", "IO91", "20m", "FT8") {
|
|
t.Error("a confirmed square is finished under either hunt")
|
|
}
|
|
}
|
|
|
|
// A mode the scope cannot judge flags nothing rather than guessing: a phone
|
|
// contact under an FTx scope is not evidence either way.
|
|
func TestGridScopeIgnoresModesItCannotJudge(t *testing.T) {
|
|
i := idx(entry{"JN36", "20m", "FT8", false})
|
|
for _, mode := range []string{"SSB", "CW", ""} {
|
|
if gridIsWanted(i, lookupGridScope("band_ftx"), "new", "KO01", "20m", mode) {
|
|
t.Errorf("mode %q is outside an FTx scope — nothing should be flagged", mode)
|
|
}
|
|
}
|
|
}
|
|
|
|
// An unknown or unset scope falls back rather than answering "wanted" for
|
|
// everything, which is what an empty class would have done.
|
|
func TestGridScopeFallsBack(t *testing.T) {
|
|
if got := lookupGridScope("").Key; got != defaultGridScope {
|
|
t.Errorf("unset scope = %q, want the default %q", got, defaultGridScope)
|
|
}
|
|
if got := lookupGridScope("nonsense").Key; got != defaultGridScope {
|
|
t.Errorf("unknown scope = %q, want the default %q", got, defaultGridScope)
|
|
}
|
|
if got := gridHunt("nonsense"); got != "new" {
|
|
t.Errorf("unknown hunt = %q, want new", got)
|
|
}
|
|
}
|
|
|
|
// The two wanted states must be told apart, not merged. A square worked an hour
|
|
// ago and awaiting a QSL is a different job from one never worked, and reporting
|
|
// both as plain NEW GRID is what made a station the log plainly shows as worked
|
|
// still wear the badge.
|
|
func TestGridStateSeparatesNewFromUnconfirmed(t *testing.T) {
|
|
i := idx(
|
|
entry{"JN74", "20m", "FT8", false}, // worked, no confirmation yet
|
|
entry{"IO91", "20m", "FT8", true}, // worked and confirmed
|
|
)
|
|
s := lookupGridScope("band_ftx")
|
|
|
|
if got := gridStateFor(i, s, "new_unconfirmed", "KO99", "20m", "FT8"); got != gridStateNew {
|
|
t.Errorf("never worked → %q, want %q", got, gridStateNew)
|
|
}
|
|
if got := gridStateFor(i, s, "new_unconfirmed", "JN74", "20m", "FT8"); got != gridStateUnconf {
|
|
t.Errorf("worked but unconfirmed → %q, want %q", got, gridStateUnconf)
|
|
}
|
|
if got := gridStateFor(i, s, "new_unconfirmed", "IO91", "20m", "FT8"); got != "" {
|
|
t.Errorf("confirmed → %q, want nothing to chase", got)
|
|
}
|
|
// Under the plain "new" hunt an unconfirmed square is finished business.
|
|
if got := gridStateFor(i, s, "new", "JN74", "20m", "FT8"); got != "" {
|
|
t.Errorf("unconfirmed under the new-only hunt → %q, want nothing", got)
|
|
}
|
|
}
|