Starting Run stores the frequency in the position. The bandmap draws a purple, bold "CQ Frequency" pseudospot there, which is not added to the shared bandmap, and a click on it tunes back and sets Run. A move of more than 3 kHz (a setting on the telnet Filters tab) while running switches to search and pounce. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
877 lines
27 KiB
C#
877 lines
27 KiB
C#
using Nonemm.Contests;
|
|
using Nonemm.Contests.Rules;
|
|
using Nonemm.Core;
|
|
using Nonemm.Core.Calls;
|
|
using Nonemm.Core.Country;
|
|
using Nonemm.Storage;
|
|
|
|
namespace Nonemm.Session.Tests;
|
|
|
|
public class OperatingPositionTests
|
|
{
|
|
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",
|
|
};
|
|
|
|
[Fact]
|
|
public void RunningRemembersTheFrequencyItStartedOn()
|
|
{
|
|
OperatingPosition position = Session();
|
|
position.Tune(Frequency.FromKilohertz(14_025));
|
|
|
|
position.IsRunning = true;
|
|
position.Tune(Frequency.FromKilohertz(14_026));
|
|
|
|
Assert.Equal(Frequency.FromKilohertz(14_025), position.CqFrequency);
|
|
Assert.True(position.IsRunning);
|
|
}
|
|
|
|
[Fact]
|
|
public void MovingFarOffTheCqFrequencyGoesBackToSearching()
|
|
{
|
|
OperatingPosition position = Session();
|
|
position.Tune(Frequency.FromKilohertz(14_025));
|
|
position.IsRunning = true;
|
|
|
|
position.Tune(Frequency.FromKilohertz(14_028.5));
|
|
|
|
Assert.False(position.IsRunning);
|
|
Assert.Equal(Frequency.FromKilohertz(14_025), position.CqFrequency);
|
|
}
|
|
|
|
[Fact]
|
|
public void TheDistanceThatEndsRunningIsSet()
|
|
{
|
|
OperatingPosition position = Session();
|
|
position.CqLeaveHertz = 1_000;
|
|
position.Tune(Frequency.FromKilohertz(14_025));
|
|
position.IsRunning = true;
|
|
|
|
position.Tune(Frequency.FromKilohertz(14_026.5));
|
|
|
|
Assert.False(position.IsRunning);
|
|
}
|
|
|
|
/// N1MM's `{FORCELOG}`: a contact the checks refuse still goes in the log,
|
|
/// because one that was never logged cannot be fixed afterwards.
|
|
[Fact]
|
|
public void AForcedContactIsLoggedWithTheExchangeShort()
|
|
{
|
|
OperatingPosition session = Session();
|
|
session.Entry.Call = "JA1XYZ";
|
|
|
|
Assert.False(session.IsEntryComplete);
|
|
Assert.Equal("JA1XYZ", session.LogContactAnyway().Call.Text);
|
|
Assert.Single(session.Log.Qsos);
|
|
}
|
|
|
|
[Fact]
|
|
public void AForcedContactStillNeedsACallsign()
|
|
{
|
|
OperatingPosition session = Session();
|
|
|
|
Assert.Throws<InvalidOperationException>(() => session.LogContactAnyway());
|
|
}
|
|
|
|
private static OperatingPosition Session(Contest? contest = null) =>
|
|
new(Shared(new FakeLogStore(), contest));
|
|
|
|
private static ContestSession Shared(FakeLogStore store, Contest? contest = null)
|
|
{
|
|
ContestInstance instance = store.AddContest(new ContestInstance
|
|
{
|
|
ContestNumber = 0,
|
|
ContestName = "CQWW",
|
|
});
|
|
return new ContestSession(
|
|
store,
|
|
contest ?? new CqWorldWide(ModeCategory.Cw),
|
|
instance,
|
|
Me,
|
|
CountryFile.Parse(Countries));
|
|
}
|
|
|
|
private static void Type(OperatingPosition session, string call, string zone)
|
|
{
|
|
session.Entry.Call = call;
|
|
session.Entry.Set(ExchangeSlot.ReceivedReport, "599");
|
|
session.Entry.Set(ExchangeSlot.Zone, zone);
|
|
}
|
|
|
|
/// N1MM shows what this station sends beside what it copies, keeps both
|
|
/// filled in and steps over them.
|
|
[Fact]
|
|
public void TheSentBoxesAreFilledInAndSteppedOver()
|
|
{
|
|
OperatingPosition session = Session(new CqWpx(ModeCategory.Cw));
|
|
|
|
Assert.Equal("599", session.Entry.ValueOf(ExchangeSlot.SentReport));
|
|
Assert.Equal("1", session.Entry.ValueOf(ExchangeSlot.SentNumber));
|
|
|
|
// the boxes are the call, the two reports, the sent serial and the
|
|
// serial being copied
|
|
session.Entry.Advance();
|
|
Assert.Equal(4, session.Entry.Focus);
|
|
}
|
|
|
|
[Fact]
|
|
public void TheSentSerialGoesUpWhenAContactIsLogged()
|
|
{
|
|
OperatingPosition session = Session(new CqWpx(ModeCategory.Cw));
|
|
session.Entry.Call = "JA1XYZ";
|
|
session.Entry.Set(ExchangeSlot.ReceivedReport, "599");
|
|
session.Entry.Set(ExchangeSlot.SerialNumber, "12");
|
|
|
|
Assert.Equal(1, session.LogContact().SentNumber);
|
|
Assert.Equal("2", session.Entry.ValueOf(ExchangeSlot.SentNumber));
|
|
}
|
|
|
|
/// The BARTG contest sends the time of the exchange, and logs the time it
|
|
/// sent rather than the time the contact was logged.
|
|
[Fact]
|
|
public void TheTimeTheExchangeWentOutIsLoggedForBartg()
|
|
{
|
|
OperatingPosition session = Session(new BartgSprint());
|
|
session.Entry.Call = "JA1XYZ";
|
|
session.Entry.Set(ExchangeSlot.ReceivedReport, "599");
|
|
session.Entry.Set(ExchangeSlot.SerialNumber, "12");
|
|
session.Entry.Set(ExchangeSlot.Exchange1, "1646");
|
|
session.StampSentTime();
|
|
string stamped = session.SentTime;
|
|
|
|
Qso logged = session.LogContact();
|
|
|
|
Assert.Equal(4, stamped.Length);
|
|
Assert.Equal(stamped, logged.MiscText);
|
|
Assert.Equal("", session.SentTime);
|
|
}
|
|
|
|
/// A contest that does not exchange a time keeps the column empty.
|
|
[Fact]
|
|
public void NothingIsStampedForAContestThatSendsNoTime()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
session.StampSentTime();
|
|
|
|
Assert.Equal("", session.SentTime);
|
|
Assert.Equal("", session.LogContact().MiscText);
|
|
}
|
|
|
|
[Fact]
|
|
public void LoggingAContactPutsItInTheLogAndClearsTheEntry()
|
|
{
|
|
OperatingPosition 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()
|
|
{
|
|
OperatingPosition 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()
|
|
{
|
|
OperatingPosition session = Session();
|
|
session.Entry.Call = "JA1XYZ";
|
|
Assert.Throws<InvalidOperationException>(() => session.LogContact());
|
|
}
|
|
|
|
[Fact]
|
|
public void TheSameStationOnTheSameBandReadsAsADupe()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
session.LogContact();
|
|
Type(session, "JA1XYZ", "25");
|
|
Assert.True(session.Verdict()?.IsDupe);
|
|
}
|
|
|
|
[Fact]
|
|
public void MovingBandClearsTheDupe()
|
|
{
|
|
OperatingPosition 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()
|
|
{
|
|
OperatingPosition 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 SpaceStepsOverTheReportAndComesBackToTheCall()
|
|
{
|
|
// CQ WW's boxes are the call, the two reports and the zone
|
|
OperatingPosition session = Session();
|
|
|
|
Assert.Equal(0, session.Entry.Focus);
|
|
session.Entry.Advance();
|
|
Assert.Equal(3, session.Entry.Focus);
|
|
session.Entry.Advance();
|
|
Assert.Equal(0, session.Entry.Focus);
|
|
}
|
|
|
|
[Fact]
|
|
public void GoingBackStepsOverTheReportToo()
|
|
{
|
|
OperatingPosition session = Session();
|
|
session.Entry.FocusOn(3);
|
|
|
|
session.Entry.Retreat();
|
|
|
|
Assert.Equal(0, session.Entry.Focus);
|
|
}
|
|
|
|
[Fact]
|
|
public void TheReportBoxesAreFilledInWithWhatWouldBeSentAnyway()
|
|
{
|
|
OperatingPosition session = Session();
|
|
|
|
Assert.True(session.FillReports());
|
|
|
|
Assert.Equal("599", session.Entry.ValueOf(ExchangeSlot.ReceivedReport));
|
|
Assert.False(session.FillReports());
|
|
}
|
|
|
|
[Fact]
|
|
public void AReportTheOperatorTypedIsLeftAlone()
|
|
{
|
|
OperatingPosition session = Session();
|
|
session.Entry.Set(ExchangeSlot.ReceivedReport, "559");
|
|
|
|
Assert.False(session.FillReports());
|
|
Assert.Equal("559", session.Entry.ValueOf(ExchangeSlot.ReceivedReport));
|
|
}
|
|
|
|
[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)
|
|
{
|
|
OperatingPosition session = Session();
|
|
session.Entry.Call = typed;
|
|
Assert.Equal(hertz, session.PendingQsy()?.Hertz);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("JA1XYZ")]
|
|
[InlineData("12000")]
|
|
public void ACallOrAnOutOfBandNumberIsNotAQsy(string typed)
|
|
{
|
|
OperatingPosition session = Session();
|
|
session.Entry.Call = typed;
|
|
Assert.Null(session.PendingQsy());
|
|
}
|
|
|
|
[Fact]
|
|
public void AContactDeletedFromTheLogGivesItsMultiplierBack()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
Qso first = session.LogContact();
|
|
Type(session, "JA2XYZ", "25");
|
|
session.LogContact();
|
|
Assert.Equal(2, session.Log.Tally.TotalMultipliers);
|
|
|
|
session.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 AContactWorkedSplitRecordsWhereWeTransmitted()
|
|
{
|
|
OperatingPosition session = Session();
|
|
session.Tune(
|
|
Frequency.FromKilohertz(14_025),
|
|
Modes.Cw,
|
|
Frequency.FromKilohertz(14_200));
|
|
Type(session, "JA1XYZ", "25");
|
|
|
|
Qso logged = session.LogContact();
|
|
|
|
Assert.Equal(14_025_000, logged.Frequency.Hertz);
|
|
Assert.Equal(14_200_000, logged.QsxFrequency.Hertz);
|
|
}
|
|
|
|
[Fact]
|
|
public void AContactRecordsWhichRadioMadeIt()
|
|
{
|
|
OperatingPosition session = new(Shared(new FakeLogStore()), 2);
|
|
Type(session, "JA1XYZ", "25");
|
|
|
|
Assert.Equal(2, session.LogContact().RadioNumber);
|
|
}
|
|
|
|
/// Tuning without saying anything about split leaves it where it was, so a
|
|
/// frequency typed by hand does not clear what the radio reported.
|
|
[Fact]
|
|
public void TuningWithoutASplitLeavesTheOneAlreadySet()
|
|
{
|
|
OperatingPosition session = Session();
|
|
session.Tune(Frequency.FromKilohertz(14_025), null, Frequency.FromKilohertz(14_200));
|
|
|
|
session.Tune(Frequency.FromKilohertz(14_030));
|
|
|
|
Assert.Equal(14_200_000, session.TransmitFrequency.Hertz);
|
|
}
|
|
|
|
[Fact]
|
|
public void EditingAContactRescoresTheLog()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
Qso logged = session.LogContact();
|
|
Assert.Equal(3, session.Log.Tally.Points);
|
|
|
|
QsoEdit edit = session.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()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
Qso logged = session.LogContact();
|
|
|
|
QsoEdit edit = session.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()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
Qso logged = session.LogContact();
|
|
QsoChange? change = null;
|
|
session.Session.Edited += (_, c) => change = c;
|
|
|
|
session.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);
|
|
}
|
|
|
|
/// The country file gets a call wrong now and then. Correcting the country
|
|
/// by hand has to change the score, or the correction is decoration.
|
|
[Fact]
|
|
public void CorrectingTheCountryChangesTheScore()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
Qso logged = session.LogContact();
|
|
Assert.Equal(3, session.Log.Tally.Points);
|
|
|
|
session.Session.Edit(logged.Id, QsoField.CountryPrefix, "DL");
|
|
|
|
Assert.Equal(0, session.Log.Tally.Points);
|
|
}
|
|
|
|
[Fact]
|
|
public void ADeleteReportsTheContactThatWent()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
Qso logged = session.LogContact();
|
|
Qso? gone = null;
|
|
session.Session.Deleted += (_, q) => gone = q;
|
|
|
|
session.Session.Delete(logged.Id);
|
|
|
|
Assert.Equal("JA1XYZ", gone?.Call.Text);
|
|
}
|
|
|
|
[Fact]
|
|
public void StoredContactsAreReadBackWhenTheSessionStarts()
|
|
{
|
|
FakeLogStore store = new();
|
|
OperatingPosition first = new(Shared(store));
|
|
Type(first, "JA1XYZ", "25");
|
|
first.LogContact();
|
|
|
|
OperatingPosition second = new(new ContestSession(
|
|
store,
|
|
new CqWorldWide(ModeCategory.Cw),
|
|
first.Instance,
|
|
Me,
|
|
CountryFile.Parse(Countries)));
|
|
Assert.Single(second.Log.Qsos);
|
|
Assert.Equal(3, second.Log.Tally.Points);
|
|
}
|
|
|
|
private static OperatingPosition WithHistory(Contest contest, string file)
|
|
{
|
|
FakeLogStore store = new();
|
|
ContestInstance instance = store.AddContest(new ContestInstance
|
|
{
|
|
ContestNumber = 0,
|
|
ContestName = contest.Name,
|
|
});
|
|
return new OperatingPosition(new ContestSession(
|
|
store,
|
|
contest,
|
|
instance,
|
|
Me,
|
|
CountryFile.Parse(Countries),
|
|
CallHistory.Parse(file)));
|
|
}
|
|
|
|
[Fact]
|
|
public void TheCallHistoryFillsTheExchangeIn()
|
|
{
|
|
OperatingPosition session = WithHistory(
|
|
new Sweepstakes(ModeCategory.Cw),
|
|
"!!Order!!,Call,Sect,CK\nW3LPL,MDC,56");
|
|
session.Entry.Call = "W3LPL";
|
|
|
|
Assert.True(session.FillFromHistory());
|
|
Assert.Equal("56MDC", session.Entry.ValueOf(ExchangeSlot.Exchange1));
|
|
}
|
|
|
|
/// What the other station actually sends beats what was published, so a box
|
|
/// with something in it is left alone.
|
|
[Fact]
|
|
public void WhatIsAlreadyTypedIsNotOverwritten()
|
|
{
|
|
OperatingPosition session = WithHistory(
|
|
new Sweepstakes(ModeCategory.Cw),
|
|
"!!Order!!,Call,Sect\nW3LPL,MDC");
|
|
session.Entry.Call = "W3LPL";
|
|
session.Entry.Set(ExchangeSlot.Exchange1, "72A 74EPA");
|
|
|
|
session.FillFromHistory();
|
|
|
|
Assert.Equal("72A 74EPA", session.Entry.ValueOf(ExchangeSlot.Exchange1));
|
|
}
|
|
|
|
[Fact]
|
|
public void ACallTheHistoryDoesNotHoldFillsNothing()
|
|
{
|
|
OperatingPosition session = WithHistory(
|
|
new Sweepstakes(ModeCategory.Cw),
|
|
"!!Order!!,Call,Sect\nW3LPL,MDC");
|
|
session.Entry.Call = "JA1XYZ";
|
|
|
|
Assert.False(session.FillFromHistory());
|
|
Assert.Equal("", session.Entry.ValueOf(ExchangeSlot.Exchange1));
|
|
}
|
|
|
|
/// CQ WW exchanges the CQ zone, so the CQ zone column is the one to use.
|
|
[Fact]
|
|
public void TheZoneComesFromTheColumnTheContestAsksFor()
|
|
{
|
|
OperatingPosition session = WithHistory(
|
|
new CqWorldWide(ModeCategory.Cw),
|
|
"!!Order!!,Call,CqZone,ITUZone\nJA1XYZ,25,45");
|
|
session.Entry.Call = "JA1XYZ";
|
|
|
|
session.FillFromHistory();
|
|
|
|
Assert.Equal("25", session.Entry.ValueOf(ExchangeSlot.Zone));
|
|
}
|
|
|
|
/// N1MM suggests the country's zone when the call is settled.
|
|
[Fact]
|
|
public void TheCountryFileZoneFillsTheZoneBox()
|
|
{
|
|
OperatingPosition session = Session();
|
|
session.Entry.Call = "JA1XYZ";
|
|
|
|
Assert.True(session.FillFromHistory());
|
|
Assert.Equal("25", session.Entry.ValueOf(ExchangeSlot.Zone));
|
|
}
|
|
|
|
/// A zone copied on an earlier contact beats the country's own.
|
|
[Fact]
|
|
public void TheZoneLoggedBeforeBeatsTheCountryFileZone()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "DL1ABC", "40");
|
|
session.LogContact();
|
|
session.Wipe();
|
|
session.Entry.Call = "DL1ABC";
|
|
|
|
session.FillFromHistory();
|
|
|
|
Assert.Equal("40", session.Entry.ValueOf(ExchangeSlot.Zone));
|
|
}
|
|
|
|
/// A file for a section contest often carries the state instead.
|
|
[Fact]
|
|
public void TheStateFillsASectionWhenTheFileHasNoSection()
|
|
{
|
|
OperatingPosition session = WithHistory(
|
|
new Sweepstakes(ModeCategory.Cw),
|
|
"!!Order!!,Call,Sect,State\nK1TTT,,CT");
|
|
session.Entry.Call = "K1TTT";
|
|
|
|
session.FillFromHistory();
|
|
|
|
Assert.Equal("00CT", session.Entry.ValueOf(ExchangeSlot.Exchange1));
|
|
}
|
|
|
|
[Fact]
|
|
public void TheNameAndCommentBoxesReachTheLog()
|
|
{
|
|
OperatingPosition session = Session();
|
|
session.Entry.Call = "JA1XYZ";
|
|
session.Entry.Set(ExchangeSlot.ReceivedReport, "599");
|
|
session.Entry.Set(ExchangeSlot.Zone, "25");
|
|
session.OtherName = "Ken";
|
|
session.Comment = "up 2";
|
|
|
|
Qso logged = session.LogContact();
|
|
|
|
Assert.Equal("Ken", logged.Name);
|
|
Assert.Equal("up 2", logged.Comment);
|
|
Assert.Equal("", session.OtherName);
|
|
Assert.Equal("", session.Comment);
|
|
}
|
|
|
|
[Fact]
|
|
public void WipingClearsTheNameAndTheComment()
|
|
{
|
|
OperatingPosition session = Session();
|
|
session.OtherName = "Ken";
|
|
session.Comment = "up 2";
|
|
|
|
session.Wipe();
|
|
|
|
Assert.Equal("", session.OtherName);
|
|
Assert.Equal("", session.Comment);
|
|
}
|
|
|
|
[Fact]
|
|
public void CqWwRttyStepsOverTheStateBoxForAStationOutsideTheUsAndCanada()
|
|
{
|
|
OperatingPosition session = Session(new CqWorldWide(ModeCategory.Digital));
|
|
session.Entry.Call = "JA1XYZ";
|
|
|
|
// the boxes are the call, the two reports, the zone and the state
|
|
session.MoveFocus(forward: true);
|
|
Assert.Equal(3, session.Entry.Focus);
|
|
session.MoveFocus(forward: true);
|
|
Assert.Equal(0, session.Entry.Focus);
|
|
}
|
|
|
|
[Fact]
|
|
public void CqWwRttyStopsOnTheStateBoxForAUsStation()
|
|
{
|
|
OperatingPosition session = Session(new CqWorldWide(ModeCategory.Digital));
|
|
session.Entry.Call = "K1ABC";
|
|
|
|
session.MoveFocus(forward: true);
|
|
Assert.Equal(3, session.Entry.Focus);
|
|
session.MoveFocus(forward: true);
|
|
Assert.Equal(4, session.Entry.Focus);
|
|
}
|
|
|
|
[Fact]
|
|
public void QuickEditLoadsTheLastContactAndSavesTheChangeBack()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
Qso logged = session.LogContact();
|
|
Type(session, "DL9ZZZ", "14");
|
|
|
|
Assert.True(session.QuickEdit(forward: false));
|
|
Assert.Equal(logged.Id, session.Editing?.Id);
|
|
Assert.Equal("JA1XYZ", session.Entry.Call);
|
|
Assert.Equal("25", session.Entry.ValueOf(ExchangeSlot.Zone));
|
|
|
|
session.Entry.Set(ExchangeSlot.Zone, "24");
|
|
Qso? saved = session.SaveQuickEdit();
|
|
|
|
Assert.Equal(24, saved?.Zone);
|
|
Assert.Equal(24, session.Log.Qsos[0].Zone);
|
|
Assert.Null(session.Editing);
|
|
}
|
|
|
|
[Fact]
|
|
public void LeavingQuickEditPutsBackWhatWasBeingTyped()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
session.LogContact();
|
|
Type(session, "DL9ZZZ", "14");
|
|
|
|
session.QuickEdit(forward: false);
|
|
session.LeaveQuickEdit();
|
|
|
|
Assert.Null(session.Editing);
|
|
Assert.Equal("DL9ZZZ", session.Entry.Call);
|
|
Assert.Equal("14", session.Entry.ValueOf(ExchangeSlot.Zone));
|
|
}
|
|
|
|
[Fact]
|
|
public void QuickEditForwardPastTheNewestContactLeavesQuickEdit()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
session.LogContact();
|
|
Type(session, "DL9ZZZ", "14");
|
|
|
|
session.QuickEdit(forward: false);
|
|
Assert.True(session.QuickEdit(forward: true));
|
|
|
|
Assert.Null(session.Editing);
|
|
Assert.Equal("DL9ZZZ", session.Entry.Call);
|
|
}
|
|
|
|
[Fact]
|
|
public void QuickEditStepsBackThroughTheLog()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
session.LogContact();
|
|
Type(session, "DL9ZZZ", "14");
|
|
session.LogContact();
|
|
|
|
session.QuickEdit(forward: false);
|
|
Assert.Equal("DL9ZZZ", session.Entry.Call);
|
|
session.QuickEdit(forward: false);
|
|
Assert.Equal("JA1XYZ", session.Entry.Call);
|
|
Assert.False(session.QuickEdit(forward: false));
|
|
}
|
|
|
|
/// The entry boxes and the log window take the same values. A zone that
|
|
/// does not exist is refused in both, and the box holding it is named so
|
|
/// the entry window can put the cursor back in it.
|
|
[Fact]
|
|
public void AZoneThatDoesNotExistCannotBeLogged()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "41");
|
|
|
|
EntryProblem? problem = session.EntryProblem();
|
|
|
|
Assert.Equal("CQ zones run from 1 to 40", problem!.Message);
|
|
Assert.Equal("41", session.Entry[problem.Field]);
|
|
Assert.Throws<InvalidOperationException>(session.LogContact);
|
|
}
|
|
|
|
[Fact]
|
|
public void ArbitraryTextInTheReportBoxCannotBeLogged()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
session.Entry.Set(ExchangeSlot.ReceivedReport, "good copy");
|
|
|
|
Assert.Contains("signal report", session.EntryProblem()!.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void ACallThatIsNotShapedLikeOneCannotBeLogged()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "12345", "25");
|
|
|
|
Assert.Equal(0, session.EntryProblem()!.Field);
|
|
}
|
|
|
|
[Fact]
|
|
public void AnExchangeTheContestTakesGivesNoProblem()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "ja1xyz", "25");
|
|
|
|
Assert.Null(session.EntryProblem());
|
|
}
|
|
|
|
/// Nothing is typed yet when the operator is between contacts, and that is
|
|
/// not something to complain about.
|
|
[Fact]
|
|
public void EmptyBoxesGiveNoProblem() =>
|
|
Assert.Null(Session().EntryProblem());
|
|
|
|
/// N1MM's Alt+W goes both ways: it wipes the boxes, and pressed again on
|
|
/// empty boxes it puts back what it wiped.
|
|
[Fact]
|
|
public void AltWWipesAndThenPutsTheBoxesBack()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
|
|
session.WipeOrRestore();
|
|
Assert.Equal("", session.Entry.Call);
|
|
|
|
session.WipeOrRestore();
|
|
Assert.Equal("JA1XYZ", session.Entry.Call);
|
|
Assert.Equal("25", session.Entry.ValueOf(ExchangeSlot.Zone));
|
|
}
|
|
|
|
/// A wipe keeps what was in the boxes and Alt+W puts it back.
|
|
[Fact]
|
|
public void WhatAWipeClearedComesBack()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
session.Comment = "loud";
|
|
|
|
session.Wipe();
|
|
Assert.Equal("", session.Entry.Call);
|
|
|
|
Assert.True(session.RestoreWiped());
|
|
Assert.Equal("JA1XYZ", session.Entry.Call);
|
|
Assert.Equal("25", session.Entry.ValueOf(ExchangeSlot.Zone));
|
|
Assert.Equal("loud", session.Comment);
|
|
}
|
|
|
|
[Fact]
|
|
public void ThereIsNothingToPutBackBeforeAWipe() =>
|
|
Assert.False(Session().RestoreWiped());
|
|
|
|
/// Wiping empty boxes would otherwise throw away what the wipe before it
|
|
/// kept.
|
|
[Fact]
|
|
public void ASecondWipeKeepsTheFirstOne()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
|
|
session.Wipe();
|
|
session.Wipe();
|
|
|
|
Assert.True(session.RestoreWiped());
|
|
Assert.Equal("JA1XYZ", session.Entry.Call);
|
|
}
|
|
|
|
/// N1MM puts the boxes back only when they are empty, so typing is never
|
|
/// overwritten by a restore.
|
|
[Fact]
|
|
public void NothingIsPutBackOverSomethingTyped()
|
|
{
|
|
OperatingPosition session = Session();
|
|
Type(session, "JA1XYZ", "25");
|
|
session.Wipe();
|
|
session.Entry.Call = "DL2XYZ";
|
|
|
|
Assert.False(session.RestoreWiped());
|
|
Assert.Equal("DL2XYZ", session.Entry.Call);
|
|
}
|
|
|
|
/// A call put on the stack has not gone anywhere, so there is nothing to
|
|
/// put back and Alt+W would only make a second copy of it.
|
|
[Fact]
|
|
public void AStackedCallIsNotWaitingToBePutBack()
|
|
{
|
|
OperatingPosition session = Session();
|
|
session.IsRunning = true;
|
|
Type(session, "JA1XYZ", "25");
|
|
|
|
Assert.True(session.StackAnother());
|
|
Assert.False(session.RestoreWiped());
|
|
}
|
|
|
|
/// N1MM takes the serial number at the first character of the callsign, not
|
|
/// at logging, so the number is on the network while the contact is still
|
|
/// being worked. A second position takes the next one.
|
|
[Fact]
|
|
public void TypingACallTakesTheSerialNumber()
|
|
{
|
|
ContestSession shared = Shared(new FakeLogStore(), new CqWpx(ModeCategory.Cw));
|
|
OperatingPosition run = new(shared, 1);
|
|
OperatingPosition mult = new(shared, 2);
|
|
|
|
run.Entry.Call = "J";
|
|
|
|
Assert.Equal(1, run.SentNumber);
|
|
Assert.Equal(2, mult.SentNumber);
|
|
}
|
|
|
|
/// Without the pool both positions showed the same number until one of them
|
|
/// logged. The number the other station takes counts here as well.
|
|
[Fact]
|
|
public void ANumberAnotherStationTookIsNotSentHere()
|
|
{
|
|
OperatingPosition session = Session(new CqWpx(ModeCategory.Cw));
|
|
|
|
session.Session.Numbers.Note(5, Bands.Band20M);
|
|
|
|
Assert.Equal(6, session.SentNumber);
|
|
}
|
|
|
|
/// The number is held until the contact is logged, so a wipe keeps it and
|
|
/// the next contact takes a fresh one.
|
|
[Fact]
|
|
public void TheNumberIsHeldUntilTheContactIsLogged()
|
|
{
|
|
OperatingPosition session = Session(new CqWpx(ModeCategory.Cw));
|
|
session.Entry.Call = "JA1XYZ";
|
|
Assert.Equal(1, session.SentNumber);
|
|
|
|
session.Wipe();
|
|
Assert.Equal(1, session.SentNumber);
|
|
|
|
session.Entry.Call = "JA2XYZ";
|
|
session.Entry.Set(ExchangeSlot.ReceivedReport, "599");
|
|
session.Entry.Set(ExchangeSlot.SerialNumber, "12");
|
|
Assert.Equal(1, session.LogContact().SentNumber);
|
|
|
|
session.Entry.Call = "JA3XYZ";
|
|
Assert.Equal(2, session.SentNumber);
|
|
}
|
|
|
|
/// A contest without serial numbers has nothing to take.
|
|
[Fact]
|
|
public void NothingIsTakenInAContestThatSendsNoNumbers()
|
|
{
|
|
OperatingPosition session = Session();
|
|
|
|
session.Entry.Call = "JA1XYZ";
|
|
|
|
Assert.Equal(1, session.Session.Numbers.Next(Bands.Band20M));
|
|
}
|
|
}
|