Ctrl+Y from the entry window, or Enter on a row in the log, opens the whole contact in one dialog. It reaches the fields the log has no column for: QSX frequency, name, QTH, comment, grid, power, radio number, run position, and the country and WPX prefixes. Previous and Next walk the log without closing and offer to save first. The dialog checks values through the same QsoEditor the log columns use, so a zone the contest will not take is refused the same way in both places. QsoEdit now names the field that was refused, so the dialog can put the cursor back in the right box, and QsoEditor.ApplyAll changes several fields at once. Points and the multiplier flags are shown but not editable. They are worked out from the rules every time the log changes, so a typed value would be lost on the next edit. N1MM lets you type them; we do not, rather than offering a box that does nothing. A contact's own country prefix now wins over the country file when scoring. Without this, correcting the country in the dialog would change what the log shows and what Cabrillo says, but not the score. LoggingSession.Update now broadcasts the change like Edit does, so a contact saved from the dialog reaches the other stations. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
186 lines
5.9 KiB
C#
186 lines
5.9 KiB
C#
using Nonemm.Contests.Rules;
|
|
using Nonemm.Core;
|
|
using Nonemm.Core.Country;
|
|
|
|
namespace Nonemm.Session.Tests;
|
|
|
|
public class QsoEditorTests
|
|
{
|
|
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 QsoEditor CqWw() =>
|
|
new(new CqWorldWide(ModeCategory.Cw), Me, CountryFile.Parse(Countries));
|
|
|
|
private static QsoEditor Sweeps() =>
|
|
new(new Sweepstakes(ModeCategory.Cw), Me, CountryFile.Parse(Countries));
|
|
|
|
private static Qso Contact() => new()
|
|
{
|
|
Id = "1",
|
|
TimestampUtc = new DateTime(2026, 5, 30, 12, 34, 56, DateTimeKind.Utc),
|
|
Call = Callsign.Parse("JA1XYZ"),
|
|
Frequency = Frequency.FromKilohertz(14_025),
|
|
Mode = Modes.Cw,
|
|
ContestName = "CQWW",
|
|
SentReport = "599",
|
|
ReceivedReport = "599",
|
|
Zone = 25,
|
|
CountryPrefix = "JA",
|
|
Continent = "AS",
|
|
};
|
|
|
|
[Fact]
|
|
public void TheColumnsFollowTheContestExchange()
|
|
{
|
|
IReadOnlyList<QsoField> fields = [.. CqWw().Columns.Select(c => c.Field)];
|
|
|
|
Assert.Contains(QsoField.Zone, fields);
|
|
Assert.DoesNotContain(QsoField.Section, fields);
|
|
Assert.DoesNotContain(QsoField.SentNumber, fields);
|
|
}
|
|
|
|
/// Sweepstakes counts serial numbers, so the log shows the one we sent.
|
|
[Fact]
|
|
public void AContestWithSerialNumbersGetsASentNumberColumn() =>
|
|
Assert.Contains(QsoField.SentNumber, Sweeps().Columns.Select(c => c.Field));
|
|
|
|
[Fact]
|
|
public void ChangingTheCallLooksTheCountryUpAgain()
|
|
{
|
|
QsoEdit edit = CqWw().Apply(Contact(), QsoField.Call, "dl2xyz");
|
|
|
|
Assert.True(edit.IsAccepted);
|
|
Assert.Equal("DL2XYZ", edit.Result!.Call.Text);
|
|
Assert.Equal("DL", edit.Result.CountryPrefix);
|
|
Assert.Equal("EU", edit.Result.Continent);
|
|
Assert.Equal("DL2", edit.Result.WpxPrefix);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("12345")]
|
|
[InlineData("AB")]
|
|
[InlineData("JA1XY Z")]
|
|
public void ATextThatIsNotShapedLikeACallIsRefused(string text)
|
|
{
|
|
QsoEdit edit = CqWw().Apply(Contact(), QsoField.Call, text);
|
|
|
|
Assert.False(edit.IsAccepted);
|
|
Assert.Contains("callsign", edit.Error);
|
|
}
|
|
|
|
[Fact]
|
|
public void ARefusedEditReturnsNoContact() =>
|
|
Assert.Null(CqWw().Apply(Contact(), QsoField.Zone, "41").Result);
|
|
|
|
[Theory]
|
|
[InlineData("40", true)]
|
|
[InlineData("1", true)]
|
|
[InlineData("41", false)]
|
|
[InlineData("0", false)]
|
|
[InlineData("two", false)]
|
|
public void CqZonesRunFromOneToForty(string text, bool accepted) =>
|
|
Assert.Equal(accepted, CqWw().Apply(Contact(), QsoField.Zone, text).IsAccepted);
|
|
|
|
[Fact]
|
|
public void AnUnknownSectionIsRefused() =>
|
|
Assert.False(Sweeps().Apply(Contact(), QsoField.Section, "ZZ").IsAccepted);
|
|
|
|
[Fact]
|
|
public void ASectionIsStoredUpperCase() =>
|
|
Assert.Equal("STX", Sweeps().Apply(Contact(), QsoField.Section, "stx").Result!.Section);
|
|
|
|
[Fact]
|
|
public void ACommentKeepsTheCaseItWasTypedIn() =>
|
|
Assert.Equal("Nice signal", CqWw().Apply(Contact(), QsoField.Comment, "Nice signal").Result!.Comment);
|
|
|
|
[Fact]
|
|
public void ClearingAFieldIsAllowed() =>
|
|
Assert.Equal(0, CqWw().Apply(Contact(), QsoField.Zone, "").Result!.Zone);
|
|
|
|
[Fact]
|
|
public void TheTimeIsReadAsUtc()
|
|
{
|
|
QsoEdit edit = CqWw().Apply(Contact(), QsoField.Time, "2026-05-30 13:00:00");
|
|
|
|
Assert.Equal(new DateTime(2026, 5, 30, 13, 0, 0, DateTimeKind.Utc), edit.Result!.TimestampUtc);
|
|
}
|
|
|
|
[Fact]
|
|
public void AFrequencyIsTypedInKilohertz() =>
|
|
Assert.Equal(
|
|
21_005_000,
|
|
CqWw().Apply(Contact(), QsoField.Frequency, "21005").Result!.Frequency.Hertz);
|
|
|
|
[Fact]
|
|
public void AModeTheLoggerDoesNotKnowIsRefused() =>
|
|
Assert.False(CqWw().Apply(Contact(), QsoField.Mode, "SSTV").IsAccepted);
|
|
|
|
[Fact]
|
|
public void AnEmptyNumberReadsBackAsAnEmptyColumn() =>
|
|
Assert.Equal("", QsoEditor.Read(Contact() with { Zone = 0 }, QsoField.Zone));
|
|
|
|
[Fact]
|
|
public void SeveralFieldsCanBeChangedAtOnce()
|
|
{
|
|
QsoEdit edit = CqWw().ApplyAll(Contact(),
|
|
[
|
|
new(QsoField.Call, "DL2XYZ"),
|
|
new(QsoField.Zone, "14"),
|
|
new(QsoField.Comment, "first QSO"),
|
|
]);
|
|
|
|
Assert.Equal("DL2XYZ", edit.Result!.Call.Text);
|
|
Assert.Equal(14, edit.Result.Zone);
|
|
Assert.Equal("first QSO", edit.Result.Comment);
|
|
Assert.Equal("DL", edit.Result.CountryPrefix);
|
|
}
|
|
|
|
[Fact]
|
|
public void TheFirstValueTheContestWillNotTakeStopsTheWholeChange()
|
|
{
|
|
QsoEdit edit = CqWw().ApplyAll(Contact(),
|
|
[
|
|
new(QsoField.Call, "DL2XYZ"),
|
|
new(QsoField.Zone, "41"),
|
|
]);
|
|
|
|
Assert.Null(edit.Result);
|
|
Assert.Equal(QsoField.Zone, edit.Field);
|
|
}
|
|
|
|
[Fact]
|
|
public void ARefusalNamesTheFieldItCameFrom() =>
|
|
Assert.Equal(QsoField.Call, CqWw().Apply(Contact(), QsoField.Call, "12345").Field);
|
|
|
|
[Fact]
|
|
public void AQsxFrequencyCanBeCleared() =>
|
|
Assert.Equal(
|
|
0,
|
|
CqWw().Apply(Contact() with { QsxFrequency = Frequency.FromKilohertz(14_200) },
|
|
QsoField.QsxFrequency, "").Result!.QsxFrequency.Hertz);
|
|
|
|
/// The working frequency is what the contact was made on, so it cannot be
|
|
/// emptied the way the QSX frequency can.
|
|
[Fact]
|
|
public void TheWorkingFrequencyCannotBeCleared() =>
|
|
Assert.False(CqWw().Apply(Contact(), QsoField.Frequency, "").IsAccepted);
|
|
|
|
[Fact]
|
|
public void ACorrectedCountryPrefixIsKept() =>
|
|
Assert.Equal("DL", CqWw().Apply(Contact(), QsoField.CountryPrefix, "dl").Result!.CountryPrefix);
|
|
}
|