Double-click a cell in the log window to change it. The columns follow the contest exchange instead of being fixed in the XAML, so CQ WW gets a Zone column and Sweepstakes gets Nr, Prec, Ck and Sec. QsoEditor holds the column list and applies one field edit. Validation comes from the ExchangeFieldKind the contest declared: a CQ zone is 1 to 40, an ITU zone 1 to 90, a section is looked up in the ARRL list, a grid must parse. A refused edit returns the reason and leaves the log alone, so the cell reverts. Changing the callsign runs the country lookup again. Delete, or the right-click menu, removes the selected contact after a confirmation. Either way the log is rescored, so a multiplier the removed contact held passes to the next contact that claims it. Both go out to the other stations in N1MM's own messages: contactreplace carries oldcall and oldtimestamp, contactdelete names the contact. An incoming edit or delete is matched by contact id first, falling back to call plus timestamp because N1MM does not know our ids. Country, continent and the two prefixes were filled in twice, once when logging and once when editing. They now come from CountryFields. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
240 lines
7.3 KiB
C#
240 lines
7.3 KiB
C#
using Nonemm.Contests;
|
|
using Nonemm.Contests.Rules;
|
|
using Nonemm.Core;
|
|
using Nonemm.Core.Country;
|
|
using Nonemm.Storage;
|
|
|
|
namespace Nonemm.Session.Tests;
|
|
|
|
public class LoggingSessionTests
|
|
{
|
|
private const string Countries = """
|
|
Germany: 14: 28: EU: 51.00: -10.00: -1.0: DL:
|
|
DL,DK,DJ;
|
|
Japan: 25: 45: AS: 36.40: -138.38: -9.0: JA:
|
|
JA,JH,JR;
|
|
""";
|
|
|
|
private static readonly StationInfo Me = new()
|
|
{
|
|
Callsign = "DL1ABC",
|
|
CqZone = 14,
|
|
ItuZone = 28,
|
|
Continent = "EU",
|
|
CountryPrefix = "DL",
|
|
};
|
|
|
|
private static LoggingSession Session(Contest? contest = null)
|
|
{
|
|
FakeLogStore store = new();
|
|
ContestInstance instance = store.AddContest(new ContestInstance
|
|
{
|
|
ContestNumber = 0,
|
|
ContestName = "CQWW",
|
|
});
|
|
return new LoggingSession(
|
|
store,
|
|
contest ?? new CqWorldWide(ModeCategory.Cw),
|
|
instance,
|
|
Me,
|
|
CountryFile.Parse(Countries));
|
|
}
|
|
|
|
private static void Type(LoggingSession session, string call, string zone)
|
|
{
|
|
session.Entry.Call = call;
|
|
session.Entry.Set(ExchangeSlot.ReceivedReport, "599");
|
|
session.Entry.Set(ExchangeSlot.Zone, zone);
|
|
}
|
|
|
|
[Fact]
|
|
public void LoggingAContactPutsItInTheLogAndClearsTheEntry()
|
|
{
|
|
LoggingSession session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
Qso logged = session.LogContact();
|
|
|
|
Assert.Equal("JA1XYZ", logged.Call.Text);
|
|
Assert.Equal(25, logged.Zone);
|
|
Assert.Equal(3, logged.Points);
|
|
Assert.Equal("", session.Entry.Call);
|
|
Assert.Single(session.Log.Qsos);
|
|
}
|
|
|
|
[Fact]
|
|
public void CountryAndPrefixAreFilledInFromTheCountryFile()
|
|
{
|
|
LoggingSession session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
Qso logged = session.LogContact();
|
|
|
|
Assert.Equal("JA", logged.CountryPrefix);
|
|
Assert.Equal("AS", logged.Continent);
|
|
Assert.Equal("JA1", logged.WpxPrefix);
|
|
}
|
|
|
|
[Fact]
|
|
public void AnIncompleteExchangeIsNotLogged()
|
|
{
|
|
LoggingSession session = Session();
|
|
session.Entry.Call = "JA1XYZ";
|
|
Assert.Throws<InvalidOperationException>(() => session.LogContact());
|
|
}
|
|
|
|
[Fact]
|
|
public void TheSameStationOnTheSameBandReadsAsADupe()
|
|
{
|
|
LoggingSession session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
session.LogContact();
|
|
Type(session, "JA1XYZ", "25");
|
|
Assert.True(session.Verdict()?.IsDupe);
|
|
}
|
|
|
|
[Fact]
|
|
public void MovingBandClearsTheDupe()
|
|
{
|
|
LoggingSession session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
session.LogContact();
|
|
session.Tune(Frequency.FromKilohertz(7_025));
|
|
Type(session, "JA1XYZ", "25");
|
|
Assert.False(session.Verdict()?.IsDupe);
|
|
}
|
|
|
|
[Fact]
|
|
public void SerialNumbersCountUp()
|
|
{
|
|
LoggingSession session = Session(new CqWpx(ModeCategory.Cw));
|
|
Assert.Equal(1, session.SentNumber);
|
|
session.Entry.Call = "JA1XYZ";
|
|
session.Entry.Set(ExchangeSlot.ReceivedReport, "599");
|
|
session.Entry.Set(ExchangeSlot.SerialNumber, "12");
|
|
session.LogContact();
|
|
Assert.Equal(2, session.SentNumber);
|
|
}
|
|
|
|
[Fact]
|
|
public void SpaceMovesThroughTheBoxesAndBackToTheCall()
|
|
{
|
|
LoggingSession session = Session();
|
|
Assert.Equal(0, session.Entry.Focus);
|
|
session.Entry.Advance();
|
|
Assert.Equal(1, session.Entry.Focus);
|
|
session.Entry.Advance();
|
|
session.Entry.Advance();
|
|
Assert.Equal(0, session.Entry.Focus);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("14025", 14_025_000)]
|
|
[InlineData("14.025", 14_025_000)]
|
|
[InlineData("7025.5", 7_025_500)]
|
|
public void AFrequencyTypedIntoTheCallBoxIsRecognised(string typed, long hertz)
|
|
{
|
|
LoggingSession session = Session();
|
|
session.Entry.Call = typed;
|
|
Assert.Equal(hertz, session.PendingQsy()?.Hertz);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("JA1XYZ")]
|
|
[InlineData("12000")]
|
|
public void ACallOrAnOutOfBandNumberIsNotAQsy(string typed)
|
|
{
|
|
LoggingSession session = Session();
|
|
session.Entry.Call = typed;
|
|
Assert.Null(session.PendingQsy());
|
|
}
|
|
|
|
[Fact]
|
|
public void AContactDeletedFromTheLogGivesItsMultiplierBack()
|
|
{
|
|
LoggingSession session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
Qso first = session.LogContact();
|
|
Type(session, "JA2XYZ", "25");
|
|
session.LogContact();
|
|
Assert.Equal(2, session.Log.Tally.TotalMultipliers);
|
|
|
|
session.Delete(first.Id);
|
|
Assert.Single(session.Log.Qsos);
|
|
Assert.Equal(2, session.Log.Tally.TotalMultipliers);
|
|
Assert.True(session.Log.Qsos.Single().IsMultiplier1);
|
|
}
|
|
|
|
[Fact]
|
|
public void EditingAContactRescoresTheLog()
|
|
{
|
|
LoggingSession session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
Qso logged = session.LogContact();
|
|
Assert.Equal(3, session.Log.Tally.Points);
|
|
|
|
QsoEdit edit = session.Edit(logged.Id, QsoField.Call, "DL2XYZ");
|
|
|
|
Assert.True(edit.IsAccepted);
|
|
Assert.Equal("DL2XYZ", session.Log.Qsos.Single().Call.Text);
|
|
Assert.Equal(0, session.Log.Tally.Points);
|
|
}
|
|
|
|
[Fact]
|
|
public void ARefusedEditLeavesTheLogAlone()
|
|
{
|
|
LoggingSession session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
Qso logged = session.LogContact();
|
|
|
|
QsoEdit edit = session.Edit(logged.Id, QsoField.Zone, "41");
|
|
|
|
Assert.False(edit.IsAccepted);
|
|
Assert.Equal(25, session.Log.Qsos.Single().Zone);
|
|
}
|
|
|
|
/// The other stations need the call and time the contact had before, because
|
|
/// that is how they find their own copy of it.
|
|
[Fact]
|
|
public void AnEditReportsWhatTheContactUsedToBe()
|
|
{
|
|
LoggingSession session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
Qso logged = session.LogContact();
|
|
QsoChange? change = null;
|
|
session.Edited += (_, c) => change = c;
|
|
|
|
session.Edit(logged.Id, QsoField.Call, "JA2XYZ");
|
|
|
|
Assert.Equal("JA1XYZ", change?.OldCall);
|
|
Assert.Equal(logged.TimestampUtc, change?.OldTimestampUtc);
|
|
Assert.Equal("JA2XYZ", change?.Qso.Call.Text);
|
|
}
|
|
|
|
[Fact]
|
|
public void ADeleteReportsTheContactThatWent()
|
|
{
|
|
LoggingSession session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
Qso logged = session.LogContact();
|
|
Qso? gone = null;
|
|
session.Deleted += (_, q) => gone = q;
|
|
|
|
session.Delete(logged.Id);
|
|
|
|
Assert.Equal("JA1XYZ", gone?.Call.Text);
|
|
}
|
|
|
|
[Fact]
|
|
public void StoredContactsAreReadBackWhenTheSessionStarts()
|
|
{
|
|
FakeLogStore store = new();
|
|
ContestInstance instance = store.AddContest(new ContestInstance { ContestNumber = 0, ContestName = "CQWW" });
|
|
LoggingSession first = new(store, new CqWorldWide(ModeCategory.Cw), instance, Me, CountryFile.Parse(Countries));
|
|
Type(first, "JA1XYZ", "25");
|
|
first.LogContact();
|
|
|
|
LoggingSession second = new(store, new CqWorldWide(ModeCategory.Cw), instance, Me, CountryFile.Parse(Countries));
|
|
Assert.Single(second.Log.Qsos);
|
|
Assert.Equal(3, second.Log.Tally.Points);
|
|
}
|
|
}
|