Check the entry boxes by the rules the log window uses

The entry window took any text and only the log window checked it, so the
same value was accepted in one place and refused in the other. The rules
now live in one file, ExchangeCheck, which both windows call.

OperatingPosition.EntryProblem names the first box that cannot be logged
and why. LogContact and SaveQuickEdit throw on one, so a caller that has
not checked cannot write nonsense to the log. The entry window checks
before it logs, puts the reason in the status line and the cursor in the
box to fix, which covers Enter, the Log It button, ESM and {LOG}.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015NZkFxHde1iifAQemFTHxf
This commit is contained in:
2026-09-10 11:13:38 +00:00
co-authored by Claude Opus 5
parent 95e1405be1
commit ab9730b26d
6 changed files with 175 additions and 33 deletions
@@ -598,4 +598,54 @@ public class OperatingPositionTests
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());
}