using Nonemm.Core; namespace Nonemm.Session.Tests; public class StatisticsPivotTests { private static readonly DateTime Start = new(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc); private static Qso Contact( string call = "DL1ABC", double kilohertz = 14_030, Mode? mode = null, int minute = 0, int points = 1, string operatorCall = "OM3ABC") { return new Qso { Id = Qso.NewId(), TimestampUtc = Start.AddMinutes(minute), Call = Callsign.Parse(call), Frequency = Frequency.FromKilohertz(kilohertz), Mode = mode ?? Modes.Cw, ContestName = "CQWW", Points = points, Operator = operatorCall, }; } private static StatisticsPivot Of( IReadOnlyList qsos, string row, string column, bool includeDupes = false) { return StatisticsPivot.Of( qsos, StatisticsFields.Find(StatisticsFields.Rows, row), StatisticsFields.Find(StatisticsFields.Columns, column), Start, includeDupes); } [Fact] public void CountsTheContactsUnderEachPairOfValues() { List log = [ Contact(kilohertz: 14_030), Contact(kilohertz: 14_040), Contact(kilohertz: 7_030), Contact(kilohertz: 7_040, mode: Modes.Usb), ]; StatisticsPivot pivot = Of(log, "Band", "Mode"); Assert.Equal(["40M", "20M"], pivot.Rows.Select(r => r.Label)); Assert.Equal(["CW", "USB"], pivot.Columns.Select(c => c.Label)); Assert.Equal(1, pivot.Count(0, 0)); Assert.Equal(1, pivot.Count(0, 1)); Assert.Equal(2, pivot.Count(1, 0)); Assert.Equal(0, pivot.Count(1, 1)); } [Fact] public void TotalsTheRowsTheColumnsAndTheWholeTable() { List log = [ Contact(kilohertz: 7_030), Contact(kilohertz: 14_030), Contact(kilohertz: 14_040), Contact(kilohertz: 14_050, mode: Modes.Usb), ]; StatisticsPivot pivot = Of(log, "Band", "Mode"); Assert.Equal([1, 3], pivot.RowTotals); Assert.Equal([1, 4], pivot.Accumulated); Assert.Equal([3, 1], pivot.ColumnTotals); Assert.Equal(4, pivot.Total); } [Fact] public void SortsANumericFieldByItsNumberAndNotByItsText() { List log = [Contact(points: 10), Contact(points: 2), Contact(points: 1)]; StatisticsPivot pivot = Of(log, "Points", "Mode"); Assert.Equal(["1", "2", "10"], pivot.Rows.Select(r => r.Label)); } [Fact] public void SortsABandByItsFrequencyAndNotByItsName() { List log = [Contact(kilohertz: 14_030), Contact(kilohertz: 3_530), Contact(kilohertz: 7_030)]; StatisticsPivot pivot = Of(log, "Band", "Mode"); Assert.Equal(["80M", "40M", "20M"], pivot.Rows.Select(r => r.Label)); } [Fact] public void PutsTheDayAndTheHourInTwoLabelColumns() { List log = [Contact(minute: 5), Contact(minute: 70), Contact(minute: 75)]; StatisticsPivot pivot = Of(log, "Hour", "Mode"); Assert.Equal(["Day", "Hr"], pivot.Row.Headings); Assert.Equal([("2026-01-01", "00"), ("2026-01-01", "01")], pivot.Rows.Select(r => (r.First, r.Second))); Assert.Equal([1, 2], pivot.RowTotals); } [Fact] public void CountsTheMinuteFieldsFromTheStartOfTheContest() { List log = [Contact(minute: 0), Contact(minute: 4), Contact(minute: 6), Contact(minute: 12)]; StatisticsPivot pivot = Of(log, "5 Minute", "Mode"); Assert.Equal(["0", "5", "10"], pivot.Rows.Select(r => r.Label)); Assert.Equal([2, 1, 1], pivot.RowTotals); } [Fact] public void LeavesOutTheContactsNotInTheClaimedScoreUnlessTheyAreAskedFor() { List log = [ Contact(), Contact() with { IsClaimed = false }, Contact() with { ContactType = "D" }, ]; Assert.Equal(1, Of(log, "Band", "Mode").Total); Assert.Equal(3, Of(log, "Band", "Mode", includeDupes: true).Total); } [Fact] public void KeepsAFieldWithNoValueAsARowOfItsOwn() { List log = [Contact(operatorCall: "OM3ABC"), Contact(operatorCall: "")]; StatisticsPivot pivot = Of(log, "Operator", "Mode"); Assert.Equal(["", "OM3ABC"], pivot.Rows.Select(r => r.Label)); } [Fact] public void SaysTheTableIsEmptyWhenNothingIsCounted() { Assert.True(Of([], "Band", "Mode").IsEmpty); Assert.Equal(0, Of([], "Band", "Mode").Total); } [Fact] public void OffersEveryRowFieldAsAColumnExceptTheOnesThatWouldNotFit() { Assert.Equal(29, StatisticsFields.Rows.Count); Assert.Equal(20, StatisticsFields.Columns.Count); Assert.DoesNotContain(StatisticsFields.Columns, f => f.Name == "Call"); Assert.Contains(StatisticsFields.Rows, f => f.Name == "Call"); } }